1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of the SVN-Buddy library. |
4
|
|
|
* For the full copyright and license information, please view |
5
|
|
|
* the LICENSE file that was distributed with this source code. |
6
|
|
|
* |
7
|
|
|
* @copyright Alexander Obuhovich <[email protected]> |
8
|
|
|
* @link https://github.com/console-helpers/svn-buddy |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace ConsoleHelpers\SVNBuddy\Command; |
12
|
|
|
|
13
|
|
|
|
14
|
|
|
use ConsoleHelpers\ConsoleKit\Exception\CommandException; |
15
|
|
|
use ConsoleHelpers\SVNBuddy\Config\AbstractConfigSetting; |
16
|
|
|
use ConsoleHelpers\SVNBuddy\Config\ChoiceConfigSetting; |
17
|
|
|
use ConsoleHelpers\SVNBuddy\InteractiveEditor; |
18
|
|
|
use ConsoleHelpers\SVNBuddy\Repository\CommitMessage\AbstractMergeTemplate; |
19
|
|
|
use ConsoleHelpers\SVNBuddy\Repository\CommitMessage\CommitMessageBuilder; |
20
|
|
|
use ConsoleHelpers\SVNBuddy\Repository\CommitMessage\MergeTemplateFactory; |
21
|
|
|
use ConsoleHelpers\SVNBuddy\Repository\WorkingCopyConflictTracker; |
22
|
|
|
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; |
23
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
24
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
25
|
|
|
use Symfony\Component\Console\Input\InputOption; |
26
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
27
|
|
|
|
28
|
|
|
class CommitCommand extends AbstractCommand implements IConfigAwareCommand |
29
|
|
|
{ |
30
|
|
|
|
31
|
|
|
const SETTING_COMMIT_MERGE_TEMPLATE = 'commit.merge-template'; |
32
|
|
|
|
33
|
|
|
const STOP_LINE = '--This line, and those below, will be ignored--'; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Editor. |
37
|
|
|
* |
38
|
|
|
* @var InteractiveEditor |
39
|
|
|
*/ |
40
|
|
|
private $_editor; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Commit message builder. |
44
|
|
|
* |
45
|
|
|
* @var CommitMessageBuilder |
46
|
|
|
*/ |
47
|
|
|
private $_commitMessageBuilder; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Merge template factory. |
51
|
|
|
* |
52
|
|
|
* @var MergeTemplateFactory |
53
|
|
|
*/ |
54
|
|
|
private $_mergeTemplateFactory; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Working copy conflict tracker. |
58
|
|
|
* |
59
|
|
|
* @var WorkingCopyConflictTracker |
60
|
|
|
*/ |
61
|
|
|
private $_workingCopyConflictTracker; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* {@inheritdoc} |
65
|
|
|
*/ |
66
|
|
|
protected function configure() |
67
|
|
|
{ |
68
|
|
|
$this |
69
|
|
|
->setName('commit') |
70
|
|
|
->setDescription( |
71
|
|
|
'Send changes from your working copy to the repository' |
72
|
|
|
) |
73
|
|
|
->setAliases(array('ci')) |
74
|
|
|
->addArgument( |
75
|
|
|
'path', |
76
|
|
|
InputArgument::OPTIONAL, |
77
|
|
|
'Working copy path', |
78
|
|
|
'.' |
79
|
|
|
) |
80
|
|
|
->addOption( |
81
|
|
|
'cl', |
82
|
|
|
null, |
83
|
|
|
InputOption::VALUE_NONE, |
84
|
|
|
'Operate only on members of selected changelist' |
85
|
|
|
) |
86
|
|
|
->addOption( |
87
|
|
|
'merge-template', |
88
|
|
|
null, |
89
|
|
|
InputOption::VALUE_REQUIRED, |
90
|
|
|
'Use alternative merge template for this commit' |
91
|
|
|
); |
92
|
|
|
|
93
|
|
|
parent::configure(); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Return possible values for the named option |
98
|
|
|
* |
99
|
|
|
* @param string $optionName Option name. |
100
|
|
|
* @param CompletionContext $context Completion context. |
101
|
|
|
* |
102
|
|
|
* @return array |
103
|
|
|
*/ |
104
|
|
|
public function completeOptionValues($optionName, CompletionContext $context) |
105
|
|
|
{ |
106
|
|
|
$ret = parent::completeOptionValues($optionName, $context); |
107
|
|
|
|
108
|
|
|
if ( $optionName === 'merge-template' ) { |
109
|
|
|
return $this->getMergeTemplateNames(); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
return $ret; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Prepare dependencies. |
117
|
|
|
* |
118
|
|
|
* @return void |
119
|
|
|
*/ |
120
|
|
|
protected function prepareDependencies() |
121
|
|
|
{ |
122
|
|
|
parent::prepareDependencies(); |
123
|
|
|
|
124
|
|
|
$container = $this->getContainer(); |
125
|
|
|
|
126
|
|
|
$this->_editor = $container['editor']; |
127
|
|
|
$this->_commitMessageBuilder = $container['commit_message_builder']; |
128
|
|
|
$this->_mergeTemplateFactory = $container['merge_template_factory']; |
129
|
|
|
$this->_workingCopyConflictTracker = $container['working_copy_conflict_tracker']; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* {@inheritdoc} |
134
|
|
|
* |
135
|
|
|
* @throws CommandException When conflicts are detected. |
136
|
|
|
* @throws CommandException Working copy has no changes. |
137
|
|
|
* @throws CommandException User decides not to perform a commit. |
138
|
|
|
*/ |
139
|
|
|
protected function execute(InputInterface $input, OutputInterface $output) |
140
|
|
|
{ |
141
|
|
|
$wc_path = $this->getWorkingCopyPath(); |
142
|
|
|
$conflicts = $this->_workingCopyConflictTracker->getNewConflicts($wc_path); |
143
|
|
|
|
144
|
|
|
if ( $conflicts ) { |
145
|
|
|
throw new CommandException('Conflicts detected. Please resolve them before committing.'); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
$changelist = $this->getChangelist($wc_path); |
149
|
|
|
$compact_working_copy_status = $this->repositoryConnector->getCompactWorkingCopyStatus($wc_path, $changelist); |
150
|
|
|
|
151
|
|
|
if ( !$compact_working_copy_status ) { |
152
|
|
|
throw new CommandException('Nothing to commit.'); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$commit_message = $this->_commitMessageBuilder->build($wc_path, $this->getMergeTemplate(), $changelist); |
156
|
|
|
$commit_message .= PHP_EOL . PHP_EOL . self::STOP_LINE . PHP_EOL . PHP_EOL . $compact_working_copy_status; |
157
|
|
|
|
158
|
|
|
$edited_commit_message = $this->_editor |
159
|
|
|
->setDocumentName('commit_message') |
160
|
|
|
->setContent($commit_message) |
161
|
|
|
->launch(); |
162
|
|
|
|
163
|
|
|
$stop_line_pos = strpos($edited_commit_message, self::STOP_LINE); |
164
|
|
|
|
165
|
|
|
if ( $stop_line_pos !== false ) { |
166
|
|
|
$edited_commit_message = trim(substr($edited_commit_message, 0, $stop_line_pos)); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
$this->io->writeln(array('<fg=white;options=bold>Commit message:</>', $edited_commit_message, '')); |
170
|
|
|
|
171
|
|
|
if ( !$this->io->askConfirmation('Run "svn commit"', false) ) { |
172
|
|
|
throw new CommandException('Commit aborted by user.'); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
$tmp_file = tempnam(sys_get_temp_dir(), 'commit_message_'); |
176
|
|
|
file_put_contents($tmp_file, $edited_commit_message); |
177
|
|
|
|
178
|
|
|
$arguments = array( |
179
|
|
|
'-F {' . $tmp_file . '}', |
180
|
|
|
); |
181
|
|
|
|
182
|
|
|
if ( strlen($changelist) ) { |
183
|
|
|
$arguments[] = '--depth empty'; |
184
|
|
|
|
185
|
|
|
// Relative path used to make command line shorter. |
186
|
|
|
foreach ( array_keys($this->repositoryConnector->getWorkingCopyStatus($wc_path, $changelist)) as $path ) { |
187
|
|
|
$arguments[] = '{' . $path . '}'; |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
else { |
191
|
|
|
$arguments[] = '{' . $wc_path . '}'; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
$this->repositoryConnector->getCommand('commit', implode(' ', $arguments))->runLive(); |
195
|
|
|
$this->_workingCopyConflictTracker->erase($wc_path); |
196
|
|
|
unlink($tmp_file); |
197
|
|
|
|
198
|
|
|
$this->io->writeln('<info>Done</info>'); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Returns user selected changelist. |
203
|
|
|
* |
204
|
|
|
* @param string $wc_path Working copy path. |
205
|
|
|
* |
206
|
|
|
* @return string|null |
207
|
|
|
* @throws CommandException When no changelists found. |
208
|
|
|
*/ |
209
|
|
|
protected function getChangelist($wc_path) |
210
|
|
|
{ |
211
|
|
|
if ( !$this->io->getOption('cl') ) { |
212
|
|
|
return null; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
$changelists = $this->repositoryConnector->getWorkingCopyChangelists($wc_path); |
216
|
|
|
|
217
|
|
|
if ( !$changelists ) { |
|
|
|
|
218
|
|
|
throw new CommandException('No changelists detected.'); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
return $this->io->choose( |
222
|
|
|
'Pick changelist by number [0]:', |
223
|
|
|
$changelists, |
224
|
|
|
0, |
225
|
|
|
'Changelist "%s" is invalid.' |
226
|
|
|
); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Returns merge template to use. |
231
|
|
|
* |
232
|
|
|
* @return AbstractMergeTemplate |
233
|
|
|
*/ |
234
|
|
|
protected function getMergeTemplate() |
235
|
|
|
{ |
236
|
|
|
$merge_template_name = $this->io->getOption('merge-template'); |
237
|
|
|
|
238
|
|
|
if ( !isset($merge_template_name) ) { |
239
|
|
|
$merge_template_name = $this->getSetting(self::SETTING_COMMIT_MERGE_TEMPLATE); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
return $this->_mergeTemplateFactory->get($merge_template_name); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Returns merge template names. |
247
|
|
|
* |
248
|
|
|
* @return array |
249
|
|
|
*/ |
250
|
|
|
protected function getMergeTemplateNames() |
251
|
|
|
{ |
252
|
|
|
if ( isset($this->_mergeTemplateFactory) ) { |
253
|
|
|
return $this->_mergeTemplateFactory->getNames(); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
// When used from "getConfigSettings" method. |
257
|
|
|
$container = $this->getContainer(); |
258
|
|
|
|
259
|
|
|
return $container['merge_template_factory']->getNames(); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Returns list of config settings. |
264
|
|
|
* |
265
|
|
|
* @return AbstractConfigSetting[] |
266
|
|
|
*/ |
267
|
|
|
public function getConfigSettings() |
268
|
|
|
{ |
269
|
|
|
$merge_template_names = array(); |
270
|
|
|
|
271
|
|
|
foreach ( $this->getMergeTemplateNames() as $merge_template_name ) { |
272
|
|
|
$merge_template_names[$merge_template_name] = str_replace('_', ' ', ucfirst($merge_template_name)); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
return array( |
276
|
|
|
new ChoiceConfigSetting( |
277
|
|
|
self::SETTING_COMMIT_MERGE_TEMPLATE, |
278
|
|
|
$merge_template_names, |
279
|
|
|
reset($merge_template_names) |
280
|
|
|
), |
281
|
|
|
); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
} |
285
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.