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\ArrayConfigSetting; |
||||
17 | use ConsoleHelpers\SVNBuddy\Config\ChoiceConfigSetting; |
||||
18 | use ConsoleHelpers\SVNBuddy\Config\StringConfigSetting; |
||||
19 | use ConsoleHelpers\SVNBuddy\Helper\OutputHelper; |
||||
20 | use ConsoleHelpers\SVNBuddy\MergeSourceDetector\AbstractMergeSourceDetector; |
||||
21 | use ConsoleHelpers\SVNBuddy\Repository\Connector\UrlResolver; |
||||
22 | use ConsoleHelpers\SVNBuddy\Repository\Parser\RevisionListParser; |
||||
23 | use ConsoleHelpers\SVNBuddy\Repository\WorkingCopyConflictTracker; |
||||
24 | use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; |
||||
25 | use Symfony\Component\Console\Helper\Table; |
||||
26 | use Symfony\Component\Console\Input\InputArgument; |
||||
27 | use Symfony\Component\Console\Input\InputInterface; |
||||
28 | use Symfony\Component\Console\Input\InputOption; |
||||
29 | use Symfony\Component\Console\Output\OutputInterface; |
||||
30 | |||||
31 | class MergeCommand extends AbstractCommand implements IAggregatorAwareCommand, IConfigAwareCommand |
||||
32 | { |
||||
33 | |||||
34 | const SETTING_MERGE_SOURCE_URL = 'merge.source-url'; |
||||
35 | |||||
36 | const SETTING_MERGE_RECENT_CONFLICTS = 'merge.recent-conflicts'; |
||||
37 | |||||
38 | const SETTING_MERGE_AUTO_COMMIT = 'merge.auto-commit'; |
||||
39 | |||||
40 | const REVISION_ALL = 'all'; |
||||
41 | |||||
42 | /** |
||||
43 | * Merge source detector. |
||||
44 | * |
||||
45 | * @var AbstractMergeSourceDetector |
||||
46 | */ |
||||
47 | private $_mergeSourceDetector; |
||||
48 | |||||
49 | /** |
||||
50 | * Revision list parser. |
||||
51 | * |
||||
52 | * @var RevisionListParser |
||||
53 | */ |
||||
54 | private $_revisionListParser; |
||||
55 | |||||
56 | /** |
||||
57 | * Usable revisions (either to be merged OR to be unmerged). |
||||
58 | * |
||||
59 | * @var array |
||||
60 | */ |
||||
61 | private $_usableRevisions = array(); |
||||
62 | |||||
63 | /** |
||||
64 | * Url resolver. |
||||
65 | * |
||||
66 | * @var UrlResolver |
||||
67 | */ |
||||
68 | private $_urlResolver; |
||||
69 | |||||
70 | /** |
||||
71 | * Working copy conflict tracker. |
||||
72 | * |
||||
73 | * @var WorkingCopyConflictTracker |
||||
74 | */ |
||||
75 | private $_workingCopyConflictTracker; |
||||
76 | |||||
77 | /** |
||||
78 | * Prepare dependencies. |
||||
79 | * |
||||
80 | * @return void |
||||
81 | */ |
||||
82 | protected function prepareDependencies() |
||||
83 | { |
||||
84 | parent::prepareDependencies(); |
||||
85 | |||||
86 | $container = $this->getContainer(); |
||||
87 | |||||
88 | $this->_mergeSourceDetector = $container['merge_source_detector']; |
||||
89 | $this->_revisionListParser = $container['revision_list_parser']; |
||||
90 | $this->_urlResolver = $container['repository_url_resolver']; |
||||
91 | $this->_workingCopyConflictTracker = $container['working_copy_conflict_tracker']; |
||||
92 | } |
||||
93 | |||||
94 | /** |
||||
95 | * {@inheritdoc} |
||||
96 | */ |
||||
97 | protected function configure() |
||||
98 | { |
||||
99 | $this |
||||
100 | ->setName('merge') |
||||
101 | ->setDescription('Merge changes from another project or ref within same project into a working copy') |
||||
102 | ->addArgument( |
||||
103 | 'path', |
||||
104 | InputArgument::OPTIONAL, |
||||
105 | 'Working copy path', |
||||
106 | '.' |
||||
107 | ) |
||||
108 | ->addOption( |
||||
109 | 'source-url', |
||||
110 | null, |
||||
111 | InputOption::VALUE_REQUIRED, |
||||
112 | 'Merge source url (absolute or relative) or ref name, e.g. <comment>branches/branch-name</comment>' |
||||
113 | ) |
||||
114 | ->addOption( |
||||
115 | 'revisions', |
||||
116 | 'r', |
||||
117 | InputOption::VALUE_REQUIRED, |
||||
118 | 'List of revision(-s) and/or revision range(-s) to merge, e.g. <comment>53324</comment>, <comment>1224-4433</comment> or <comment>all</comment>' |
||||
119 | ) |
||||
120 | ->addOption( |
||||
121 | 'exclude-revisions', |
||||
122 | null, |
||||
123 | InputOption::VALUE_REQUIRED, |
||||
124 | 'List of revision(-s) and/or revision range(-s) not to merge, e.g. <comment>53324</comment>, <comment>1224-4433</comment>' |
||||
125 | ) |
||||
126 | ->addOption( |
||||
127 | 'bugs', |
||||
128 | 'b', |
||||
129 | InputOption::VALUE_REQUIRED, |
||||
130 | 'List of bug(-s) to merge, e.g. <comment>JRA-1234</comment>, <comment>43644</comment>' |
||||
131 | ) |
||||
132 | ->addOption( |
||||
133 | 'exclude-bugs', |
||||
134 | null, |
||||
135 | InputOption::VALUE_REQUIRED, |
||||
136 | 'List of bug(-s) not to merge, e.g. <comment>JRA-1234</comment>, <comment>43644</comment>' |
||||
137 | ) |
||||
138 | ->addOption( |
||||
139 | 'merges', |
||||
140 | null, |
||||
141 | InputOption::VALUE_NONE, |
||||
142 | 'Show merge revisions only' |
||||
143 | ) |
||||
144 | ->addOption( |
||||
145 | 'no-merges', |
||||
146 | null, |
||||
147 | InputOption::VALUE_NONE, |
||||
148 | 'Hide merge revisions' |
||||
149 | ) |
||||
150 | ->addOption( |
||||
151 | 'with-full-message', |
||||
152 | 'f', |
||||
153 | InputOption::VALUE_NONE, |
||||
154 | 'Shows non-truncated commit messages' |
||||
155 | ) |
||||
156 | ->addOption( |
||||
157 | 'with-details', |
||||
158 | 'd', |
||||
159 | InputOption::VALUE_NONE, |
||||
160 | 'Shows detailed revision information, e.g. paths affected' |
||||
161 | ) |
||||
162 | ->addOption( |
||||
163 | 'with-summary', |
||||
164 | 's', |
||||
165 | InputOption::VALUE_NONE, |
||||
166 | 'Shows number of added/changed/removed paths in the revision' |
||||
167 | ) |
||||
168 | ->addOption( |
||||
169 | 'update-revision', |
||||
170 | null, |
||||
171 | InputOption::VALUE_REQUIRED, |
||||
172 | 'Update working copy to given revision before performing a merge' |
||||
173 | ) |
||||
174 | ->addOption( |
||||
175 | 'auto-commit', |
||||
176 | null, |
||||
177 | InputOption::VALUE_REQUIRED, |
||||
178 | 'Automatically perform commit on successful merge, e.g. <comment>yes</comment> or <comment>no</comment>' |
||||
179 | ) |
||||
180 | ->addOption( |
||||
181 | 'auto-deploy', |
||||
182 | null, |
||||
183 | InputOption::VALUE_REQUIRED, |
||||
184 | 'Automatically perform remote deployment on successful merge commit, e.g. <comment>yes</comment> or <comment>no</comment>' |
||||
185 | ) |
||||
186 | ->addOption( |
||||
187 | 'record-only', |
||||
188 | null, |
||||
189 | InputOption::VALUE_NONE, |
||||
190 | 'Mark revisions as merged without actually merging them' |
||||
191 | ) |
||||
192 | ->addOption( |
||||
193 | 'reverse', |
||||
194 | null, |
||||
195 | InputOption::VALUE_NONE, |
||||
196 | 'Rollback previously merged revisions' |
||||
197 | ) |
||||
198 | ->addOption( |
||||
199 | 'aggregate', |
||||
200 | 'a', |
||||
201 | InputOption::VALUE_NONE, |
||||
202 | 'Aggregate displayed revisions by bugs' |
||||
203 | ) |
||||
204 | ->addOption( |
||||
205 | 'preview', |
||||
206 | 'p', |
||||
207 | InputOption::VALUE_NONE, |
||||
208 | 'Preview revisions to be merged' |
||||
209 | ); |
||||
210 | |||||
211 | parent::configure(); |
||||
212 | } |
||||
213 | |||||
214 | /** |
||||
215 | * Return possible values for the named option |
||||
216 | * |
||||
217 | * @param string $optionName Option name. |
||||
218 | * @param CompletionContext $context Completion context. |
||||
219 | * |
||||
220 | * @return array |
||||
221 | */ |
||||
222 | public function completeOptionValues($optionName, CompletionContext $context) |
||||
223 | { |
||||
224 | $ret = parent::completeOptionValues($optionName, $context); |
||||
225 | |||||
226 | if ( $optionName === 'revisions' ) { |
||||
227 | return array('all'); |
||||
228 | } |
||||
229 | |||||
230 | if ( $optionName === 'source-url' ) { |
||||
231 | return $this->getAllRefs(); |
||||
232 | } |
||||
233 | |||||
234 | if ( $optionName === 'auto-commit' || $optionName === 'auto-deploy' ) { |
||||
235 | return array('yes', 'no'); |
||||
236 | } |
||||
237 | |||||
238 | return $ret; |
||||
239 | } |
||||
240 | |||||
241 | /** |
||||
242 | * {@inheritdoc} |
||||
243 | * |
||||
244 | * @throws CommandException When everything is merged. |
||||
245 | * @throws CommandException When manually specified revisions are already merged. |
||||
246 | * @throws CommandException When bugs from "--bugs" option are not found. |
||||
247 | */ |
||||
248 | protected function execute(InputInterface $input, OutputInterface $output) |
||||
249 | { |
||||
250 | $bugs = $this->getList($this->io->getOption('bugs')); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
251 | $revisions = $this->getList($this->io->getOption('revisions')); |
||||
252 | |||||
253 | $wc_path = $this->getWorkingCopyPath(); |
||||
254 | |||||
255 | $this->ensureLatestWorkingCopy($wc_path); |
||||
256 | |||||
257 | $source_url = $this->getSourceUrl($wc_path); |
||||
258 | $this->printSourceAndTarget($source_url, $wc_path); |
||||
259 | $this->_usableRevisions = $this->getUsableRevisions($source_url, $wc_path); |
||||
260 | |||||
261 | if ( ($bugs || $revisions) && !$this->_usableRevisions ) { |
||||
0 ignored issues
–
show
The expression
$revisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() The expression
$bugs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() The expression
$this->_usableRevisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
262 | throw new CommandException(\sprintf( |
||||
263 | 'Nothing to %s.', |
||||
264 | $this->isReverseMerge() ? 'reverse-merge' : 'merge' |
||||
265 | )); |
||||
266 | } |
||||
267 | |||||
268 | $this->ensureWorkingCopyWithoutConflicts($source_url, $wc_path); |
||||
269 | |||||
270 | if ( $this->shouldUseAll($revisions) ) { |
||||
271 | $revisions = $this->filterMergeableRevisions($this->_usableRevisions, $source_url); |
||||
272 | } |
||||
273 | else { |
||||
274 | if ( $revisions ) { |
||||
0 ignored issues
–
show
The expression
$revisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
275 | $revisions = $this->getDirectRevisions($revisions, $source_url); |
||||
276 | } |
||||
277 | |||||
278 | if ( $bugs ) { |
||||
0 ignored issues
–
show
The expression
$bugs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
279 | $revisions_from_bugs = $this->getRevisionLog($source_url)->find('bugs', $bugs); |
||||
280 | |||||
281 | if ( !$revisions_from_bugs ) { |
||||
0 ignored issues
–
show
The expression
$revisions_from_bugs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
282 | throw new CommandException('Specified bugs aren\'t mentioned in any of revisions'); |
||||
283 | } |
||||
284 | |||||
285 | $revisions = array_merge($revisions, $revisions_from_bugs); |
||||
286 | } |
||||
287 | |||||
288 | $revisions = $this->filterMergeableRevisions($revisions, $source_url); |
||||
289 | |||||
290 | if ( $revisions ) { |
||||
0 ignored issues
–
show
The expression
$revisions of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
291 | $revisions = array_intersect($revisions, $this->_usableRevisions); |
||||
292 | |||||
293 | // Auto-commit instead of failing. |
||||
294 | if ( !$revisions && $this->performCommit() ) { |
||||
0 ignored issues
–
show
The expression
$revisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
295 | return; |
||||
296 | } |
||||
297 | |||||
298 | if ( !$revisions ) { |
||||
0 ignored issues
–
show
The expression
$revisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
299 | throw new CommandException(\sprintf( |
||||
300 | 'Requested revisions are %s', |
||||
301 | $this->isReverseMerge() ? 'not yet merged' : 'already merged' |
||||
302 | )); |
||||
303 | } |
||||
304 | } |
||||
305 | } |
||||
306 | |||||
307 | if ( $revisions ) { |
||||
308 | if ( $this->io->getOption('preview') ) { |
||||
309 | // Display mergeable revisions according to user-specified filters. |
||||
310 | $this->printRevisions($source_url, $revisions); |
||||
311 | } |
||||
312 | else { |
||||
313 | // Perform merge using user-specified filters. |
||||
314 | $this->performMerge($source_url, $wc_path, $revisions); |
||||
315 | } |
||||
316 | } |
||||
317 | elseif ( $this->_usableRevisions ) { |
||||
0 ignored issues
–
show
The expression
$this->_usableRevisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
318 | // Display all mergeable revisions, because user haven't specified any revisions for merging. |
||||
319 | $this->printRevisions($source_url, $this->_usableRevisions); |
||||
320 | } |
||||
321 | } |
||||
322 | |||||
323 | /** |
||||
324 | * Filters mergeable revision list. |
||||
325 | * |
||||
326 | * @param array $revisions Revisions. |
||||
327 | * @param string $source_url Source URL. |
||||
328 | * |
||||
329 | * @return integer[] |
||||
330 | * @throws CommandException When bugs from "--exclude-bugs" option are not found. |
||||
331 | */ |
||||
332 | protected function filterMergeableRevisions(array $revisions, $source_url) |
||||
333 | { |
||||
334 | $exclude_bugs = $this->getList($this->io->getOption('exclude-bugs')); |
||||
0 ignored issues
–
show
It seems like
$this->io->getOption('exclude-bugs') can also be of type string[] ; however, parameter $string of ConsoleHelpers\SVNBuddy\...tractCommand::getList() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
335 | $exclude_revisions = $this->getList($this->io->getOption('exclude-revisions')); |
||||
336 | |||||
337 | if ( $exclude_revisions ) { |
||||
0 ignored issues
–
show
The expression
$exclude_revisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
338 | $revisions = array_diff( |
||||
339 | $revisions, |
||||
340 | $this->getDirectRevisions($exclude_revisions, $source_url) |
||||
341 | ); |
||||
342 | } |
||||
343 | |||||
344 | if ( $exclude_bugs ) { |
||||
0 ignored issues
–
show
The expression
$exclude_bugs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
345 | $exclude_revisions_from_bugs = $this->getRevisionLog($source_url)->find('bugs', $exclude_bugs); |
||||
346 | |||||
347 | if ( !$exclude_revisions_from_bugs ) { |
||||
0 ignored issues
–
show
The expression
$exclude_revisions_from_bugs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
348 | throw new CommandException('Specified exclude-bugs aren\'t mentioned in any of revisions'); |
||||
349 | } |
||||
350 | |||||
351 | $revisions = array_diff($revisions, $exclude_revisions_from_bugs); |
||||
352 | } |
||||
353 | |||||
354 | if ( $this->io->getOption('merges') ) { |
||||
355 | $revisions = array_intersect($revisions, $this->getRevisionLog($source_url)->find('merges', 'all_merges')); |
||||
356 | } |
||||
357 | elseif ( $this->io->getOption('no-merges') ) { |
||||
358 | $revisions = array_diff($revisions, $this->getRevisionLog($source_url)->find('merges', 'all_merges')); |
||||
359 | } |
||||
360 | |||||
361 | return $revisions; |
||||
362 | } |
||||
363 | |||||
364 | /** |
||||
365 | * Determines if all usable revisions should be processed. |
||||
366 | * |
||||
367 | * @param array $revisions Revisions. |
||||
368 | * |
||||
369 | * @return boolean |
||||
370 | */ |
||||
371 | protected function shouldUseAll(array $revisions) |
||||
372 | { |
||||
373 | return $revisions === array(self::REVISION_ALL); |
||||
374 | } |
||||
375 | |||||
376 | /** |
||||
377 | * Ensures, that working copy is up to date. |
||||
378 | * |
||||
379 | * @param string $wc_path Working copy path. |
||||
380 | * |
||||
381 | * @return void |
||||
382 | */ |
||||
383 | protected function ensureLatestWorkingCopy($wc_path) |
||||
384 | { |
||||
385 | $this->io->write(' * Working Copy Status ... '); |
||||
386 | $update_revision = $this->io->getOption('update-revision'); |
||||
387 | |||||
388 | if ( $this->repositoryConnector->getWorkingCopyMissing($wc_path) ) { |
||||
389 | $this->io->writeln('<error>Locally deleted files found</error>'); |
||||
390 | $this->updateWorkingCopy($wc_path, $update_revision); |
||||
391 | |||||
392 | return; |
||||
393 | } |
||||
394 | |||||
395 | $working_copy_revisions = $this->repositoryConnector->getWorkingCopyRevisions($wc_path); |
||||
396 | |||||
397 | if ( count($working_copy_revisions) > 1 ) { |
||||
398 | $this->io->writeln( |
||||
399 | '<error>Mixed revisions: ' . implode(', ', $working_copy_revisions) . '</error>' |
||||
400 | ); |
||||
401 | $this->updateWorkingCopy($wc_path, $update_revision); |
||||
402 | |||||
403 | return; |
||||
404 | } |
||||
405 | |||||
406 | $update_revision = $this->getWorkingCopyUpdateRevision($wc_path); |
||||
407 | |||||
408 | if ( isset($update_revision) ) { |
||||
409 | $this->io->writeln('<error>Not at ' . $update_revision . ' revision</error>'); |
||||
410 | $this->updateWorkingCopy($wc_path, $update_revision); |
||||
411 | |||||
412 | return; |
||||
413 | } |
||||
414 | |||||
415 | $this->io->writeln('<info>Up to date</info>'); |
||||
416 | } |
||||
417 | |||||
418 | /** |
||||
419 | * Returns revision, that working copy needs to be updated to. |
||||
420 | * |
||||
421 | * @param string $wc_path Working copy path. |
||||
422 | * |
||||
423 | * @return integer|null |
||||
424 | */ |
||||
425 | protected function getWorkingCopyUpdateRevision($wc_path) |
||||
426 | { |
||||
427 | $update_revision = $this->io->getOption('update-revision'); |
||||
428 | $actual_revision = $this->repositoryConnector->getLastRevision($wc_path); |
||||
429 | |||||
430 | if ( isset($update_revision) ) { |
||||
431 | if ( is_numeric($update_revision) ) { |
||||
432 | return (int)$update_revision === (int)$actual_revision ? null : $update_revision; |
||||
0 ignored issues
–
show
|
|||||
433 | } |
||||
434 | |||||
435 | return $update_revision; |
||||
0 ignored issues
–
show
|
|||||
436 | } |
||||
437 | |||||
438 | $repository_revision = $this->repositoryConnector->getLastRevision( |
||||
439 | $this->repositoryConnector->getWorkingCopyUrl($wc_path) |
||||
440 | ); |
||||
441 | |||||
442 | return $repository_revision > $actual_revision ? $repository_revision : null; |
||||
443 | } |
||||
444 | |||||
445 | /** |
||||
446 | * Updates working copy. |
||||
447 | * |
||||
448 | * @param string $wc_path Working copy path. |
||||
449 | * @param mixed|null $revision Revision. |
||||
450 | * |
||||
451 | * @return void |
||||
452 | */ |
||||
453 | protected function updateWorkingCopy($wc_path, $revision = null) |
||||
454 | { |
||||
455 | $arguments = array('path' => $wc_path, '--ignore-externals' => true); |
||||
456 | |||||
457 | if ( isset($revision) ) { |
||||
458 | $arguments['--revision'] = $revision; |
||||
459 | } |
||||
460 | |||||
461 | $this->runOtherCommand('update', $arguments); |
||||
462 | } |
||||
463 | |||||
464 | /** |
||||
465 | * Returns source url for merge. |
||||
466 | * |
||||
467 | * @param string $wc_path Working copy path. |
||||
468 | * |
||||
469 | * @return string |
||||
470 | * @throws CommandException When source path is invalid. |
||||
471 | */ |
||||
472 | protected function getSourceUrl($wc_path) |
||||
473 | { |
||||
474 | $source_url = $this->io->getOption('source-url'); |
||||
475 | |||||
476 | if ( $source_url === null ) { |
||||
477 | $source_url = $this->getSetting(self::SETTING_MERGE_SOURCE_URL); |
||||
478 | } |
||||
479 | elseif ( !$this->repositoryConnector->isUrl($source_url) ) { |
||||
0 ignored issues
–
show
It seems like
$source_url can also be of type string[] ; however, parameter $path of ConsoleHelpers\SVNBuddy\...ctor\Connector::isUrl() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
480 | $wc_url = $this->repositoryConnector->getWorkingCopyUrl($wc_path); |
||||
481 | $source_url = $this->_urlResolver->resolve($wc_url, $source_url); |
||||
0 ignored issues
–
show
It seems like
$source_url can also be of type string[] ; however, parameter $url_to_resolve of ConsoleHelpers\SVNBuddy\...\UrlResolver::resolve() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
482 | } |
||||
483 | |||||
484 | if ( !$source_url ) { |
||||
485 | $wc_url = $this->repositoryConnector->getWorkingCopyUrl($wc_path); |
||||
486 | $source_url = $this->_mergeSourceDetector->detect($wc_url); |
||||
487 | |||||
488 | if ( $source_url ) { |
||||
489 | $this->setSetting(self::SETTING_MERGE_SOURCE_URL, $source_url); |
||||
490 | } |
||||
491 | } |
||||
492 | |||||
493 | if ( !$source_url ) { |
||||
494 | $wc_url = $this->repositoryConnector->getWorkingCopyUrl($wc_path); |
||||
495 | $error_msg = 'Unable to guess "--source-url" option value. Please specify it manually.' . PHP_EOL; |
||||
496 | $error_msg .= 'Working Copy URL: ' . $wc_url . '.'; |
||||
497 | throw new CommandException($error_msg); |
||||
498 | } |
||||
499 | |||||
500 | return $source_url; |
||||
501 | } |
||||
502 | |||||
503 | /** |
||||
504 | * Prints information about merge source & target. |
||||
505 | * |
||||
506 | * @param string $source_url Merge source: url. |
||||
507 | * @param string $wc_path Merge target: working copy path. |
||||
508 | * |
||||
509 | * @return void |
||||
510 | */ |
||||
511 | protected function printSourceAndTarget($source_url, $wc_path) |
||||
512 | { |
||||
513 | $relative_source_url = $this->repositoryConnector->getRelativePath($source_url); |
||||
514 | $relative_target_url = $this->repositoryConnector->getRelativePath($wc_path); |
||||
515 | |||||
516 | $this->io->writeln(' * Merge Source ... <info>' . $relative_source_url . '</info>'); |
||||
517 | $this->io->writeln(' * Merge Target ... <info>' . $relative_target_url . '</info>'); |
||||
518 | } |
||||
519 | |||||
520 | /** |
||||
521 | * Ensures, that there are some usable revisions. |
||||
522 | * |
||||
523 | * @param string $source_url Merge source: url. |
||||
524 | * @param string $wc_path Merge target: working copy path. |
||||
525 | * |
||||
526 | * @return array |
||||
527 | */ |
||||
528 | protected function getUsableRevisions($source_url, $wc_path) |
||||
529 | { |
||||
530 | // Avoid missing revision query progress bar overwriting following output. |
||||
531 | $revision_log = $this->getRevisionLog($source_url); |
||||
532 | |||||
533 | $this->io->write(sprintf( |
||||
534 | ' * Upcoming %s Status (no filters) ... ', |
||||
535 | $this->isReverseMerge() ? 'Reverse-merge' : 'Merge' |
||||
536 | )); |
||||
537 | $usable_revisions = $this->calculateUsableRevisions($source_url, $wc_path); |
||||
538 | |||||
539 | if ( $usable_revisions ) { |
||||
0 ignored issues
–
show
The expression
$usable_revisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
540 | $usable_bugs = $revision_log->getBugsFromRevisions($usable_revisions); |
||||
541 | $error_msg = '<error>%d revision(-s) or %d bug(-s) %s</error>'; |
||||
542 | $this->io->writeln(sprintf( |
||||
543 | $error_msg, |
||||
544 | count($usable_revisions), |
||||
545 | count($usable_bugs), |
||||
546 | $this->isReverseMerge() ? 'merged' : 'not merged' |
||||
547 | )); |
||||
548 | } |
||||
549 | else { |
||||
550 | $this->io->writeln('<info>Up to date</info>'); |
||||
551 | } |
||||
552 | |||||
553 | return $usable_revisions; |
||||
554 | } |
||||
555 | |||||
556 | /** |
||||
557 | * Returns usable revisions. |
||||
558 | * |
||||
559 | * @param string $source_url Merge source: url. |
||||
560 | * @param string $wc_path Merge target: working copy path. |
||||
561 | * |
||||
562 | * @return array |
||||
563 | */ |
||||
564 | protected function calculateUsableRevisions($source_url, $wc_path) |
||||
565 | { |
||||
566 | $source_url = $this->repositoryConnector->removeCredentials($source_url); |
||||
567 | |||||
568 | $command = $this->repositoryConnector->getCommand( |
||||
569 | 'mergeinfo', |
||||
570 | array( |
||||
571 | '--show-revs', |
||||
572 | $this->isReverseMerge() ? 'merged' : 'eligible', |
||||
573 | $source_url, |
||||
574 | $wc_path, |
||||
575 | ) |
||||
576 | ); |
||||
577 | |||||
578 | $merge_info = $this->repositoryConnector->getProperty('svn:mergeinfo', $wc_path); |
||||
579 | |||||
580 | $cache_invalidator = array( |
||||
581 | 'source:' . $this->repositoryConnector->getLastRevision($source_url), |
||||
582 | 'merged_hash:' . crc32($merge_info), |
||||
583 | ); |
||||
584 | $command->setCacheInvalidator(implode(';', $cache_invalidator)); |
||||
585 | |||||
586 | $merge_info = $command->run(); |
||||
587 | $merge_info = explode(PHP_EOL, $merge_info); |
||||
588 | |||||
589 | foreach ( $merge_info as $index => $revision ) { |
||||
590 | $merge_info[$index] = ltrim($revision, 'r'); |
||||
591 | } |
||||
592 | |||||
593 | return array_filter($merge_info); |
||||
594 | } |
||||
595 | |||||
596 | /** |
||||
597 | * Parses information from "svn:mergeinfo" property. |
||||
598 | * |
||||
599 | * @param string $source_path Merge source: path in repository. |
||||
600 | * @param string $wc_path Merge target: working copy path. |
||||
601 | * |
||||
602 | * @return array |
||||
603 | */ |
||||
604 | protected function getMergedRevisions($source_path, $wc_path) |
||||
605 | { |
||||
606 | $merge_info = $this->repositoryConnector->getProperty('svn:mergeinfo', $wc_path); |
||||
607 | $merge_info = array_filter(explode("\n", $merge_info)); |
||||
608 | |||||
609 | foreach ( $merge_info as $merge_info_line ) { |
||||
610 | list($path, $revisions) = explode(':', $merge_info_line, 2); |
||||
611 | |||||
612 | if ( $path === $source_path ) { |
||||
613 | return $this->_revisionListParser->expandRanges(explode(',', $revisions)); |
||||
614 | } |
||||
615 | } |
||||
616 | |||||
617 | return array(); |
||||
618 | } |
||||
619 | |||||
620 | /** |
||||
621 | * Validates revisions to actually exist. |
||||
622 | * |
||||
623 | * @param array $revisions Revisions. |
||||
624 | * @param string $repository_url Repository url. |
||||
625 | * |
||||
626 | * @return array |
||||
627 | * @throws CommandException When revision doesn't exist. |
||||
628 | */ |
||||
629 | protected function getDirectRevisions(array $revisions, $repository_url) |
||||
630 | { |
||||
631 | $revision_log = $this->getRevisionLog($repository_url); |
||||
632 | |||||
633 | try { |
||||
634 | $revisions = $this->_revisionListParser->expandRanges($revisions); |
||||
635 | $revision_log->getRevisionsData('summary', $revisions); |
||||
636 | } |
||||
637 | catch ( \InvalidArgumentException $e ) { |
||||
638 | throw new CommandException($e->getMessage()); |
||||
639 | } |
||||
640 | |||||
641 | return $revisions; |
||||
642 | } |
||||
643 | |||||
644 | /** |
||||
645 | * Performs merge. |
||||
646 | * |
||||
647 | * @param string $source_url Merge source: url. |
||||
648 | * @param string $wc_path Merge target: working copy path. |
||||
649 | * @param array $revisions Revisions to merge. |
||||
650 | * |
||||
651 | * @return void |
||||
652 | */ |
||||
653 | protected function performMerge($source_url, $wc_path, array $revisions) |
||||
654 | { |
||||
655 | if ( $this->isReverseMerge() ) { |
||||
656 | rsort($revisions, SORT_NUMERIC); |
||||
657 | } |
||||
658 | else { |
||||
659 | sort($revisions, SORT_NUMERIC); |
||||
660 | } |
||||
661 | |||||
662 | $revision_count = count($revisions); |
||||
663 | |||||
664 | $used_revision_count = 0; |
||||
665 | $used_revisions = $this->repositoryConnector->getMergedRevisionChanges($wc_path, !$this->isReverseMerge()); |
||||
666 | |||||
667 | if ( $used_revisions ) { |
||||
668 | $used_revisions = call_user_func_array('array_merge', $used_revisions); |
||||
669 | $used_revision_count = count($used_revisions); |
||||
670 | $revision_count += $used_revision_count; |
||||
671 | } |
||||
672 | |||||
673 | $revision_log = $this->getRevisionLog($source_url); |
||||
674 | $revisions_data = $revision_log->getRevisionsData('summary', $revisions); |
||||
675 | |||||
676 | $revision_title_mask = $revision_log->getRevisionURLBuilder()->getMask('fg=white;options=bold,underscore'); |
||||
677 | |||||
678 | // Added " revision" text, when URL wasn't detected. |
||||
679 | if ( strpos($revision_title_mask, '://') === false ) { |
||||
680 | $revision_title_mask .= ' revision'; |
||||
681 | } |
||||
682 | |||||
683 | $merge_command_arguments = $this->getMergeCommandArguments($source_url, $wc_path); |
||||
684 | |||||
685 | foreach ( $revisions as $index => $revision ) { |
||||
686 | $command_arguments = str_replace('{revision}', $revision, $merge_command_arguments); |
||||
687 | $command = $this->repositoryConnector->getCommand('merge', $command_arguments); |
||||
688 | |||||
689 | $progress_bar = $this->createMergeProgressBar($used_revision_count + $index + 1, $revision_count); |
||||
690 | |||||
691 | // 1. Add revision link with a progress bar. |
||||
692 | $merge_heading = PHP_EOL . '<fg=white;options=bold>'; |
||||
693 | $merge_heading .= '--- $1 ' . \str_replace('{revision}', $revision, $revision_title_mask); |
||||
694 | $merge_heading .= " into '$2' " . $progress_bar . ':</>'; |
||||
695 | |||||
696 | // 2. Add a commit message. |
||||
697 | $commit_message = trim($revisions_data[$revision]['msg']); |
||||
698 | $commit_message = wordwrap($commit_message, 68); // FIXME: Not UTF-8 safe solution. |
||||
699 | $merge_heading .= PHP_EOL . $commit_message; |
||||
700 | $merge_heading .= PHP_EOL; |
||||
701 | $merge_heading .= PHP_EOL . '<fg=white;options=bold>Changed Paths:</>'; |
||||
702 | |||||
703 | $command->runLive(array( |
||||
704 | $wc_path => '.', |
||||
705 | '/--- (Merging|Reverse-merging) r' . $revision . " into '([^']*)':/" => $merge_heading, |
||||
706 | )); |
||||
707 | |||||
708 | $this->_usableRevisions = array_diff($this->_usableRevisions, array($revision)); |
||||
709 | $this->ensureWorkingCopyWithoutConflicts($source_url, $wc_path, $revision); |
||||
710 | } |
||||
711 | |||||
712 | $this->performCommit(); |
||||
713 | } |
||||
714 | |||||
715 | /** |
||||
716 | * Returns merge command arguments. |
||||
717 | * |
||||
718 | * @param string $source_url Merge source: url. |
||||
719 | * @param string $wc_path Merge target: working copy path. |
||||
720 | * |
||||
721 | * @return array |
||||
722 | */ |
||||
723 | protected function getMergeCommandArguments($source_url, $wc_path) |
||||
724 | { |
||||
725 | $ret = array( |
||||
726 | '-c', |
||||
727 | $this->isReverseMerge() ? '-{revision}' : '{revision}', |
||||
728 | ); |
||||
729 | |||||
730 | if ( $this->io->getOption('record-only') ) { |
||||
731 | $ret[] = '--record-only'; |
||||
732 | } |
||||
733 | |||||
734 | $ret[] = $source_url; |
||||
735 | $ret[] = $wc_path; |
||||
736 | |||||
737 | return $ret; |
||||
738 | } |
||||
739 | |||||
740 | /** |
||||
741 | * Create merge progress bar. |
||||
742 | * |
||||
743 | * @param integer $current Current. |
||||
744 | * @param integer $total Total. |
||||
745 | * |
||||
746 | * @return string |
||||
747 | */ |
||||
748 | protected function createMergeProgressBar($current, $total) |
||||
749 | { |
||||
750 | $total_length = 28; |
||||
751 | $percent_used = floor(($current / $total) * 100); |
||||
752 | $length_used = floor(($total_length * $percent_used) / 100); |
||||
753 | $length_free = $total_length - $length_used; |
||||
754 | |||||
755 | $ret = $length_used > 0 ? str_repeat('=', $length_used - 1) : ''; |
||||
0 ignored issues
–
show
$length_used - 1 of type double is incompatible with the type integer expected by parameter $times of str_repeat() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
756 | $ret .= '>' . str_repeat('-', $length_free); |
||||
757 | |||||
758 | return '[' . $ret . '] ' . $percent_used . '% (' . $current . ' of ' . $total . ')'; |
||||
759 | } |
||||
760 | |||||
761 | /** |
||||
762 | * Ensures, that there are no unresolved conflicts in working copy. |
||||
763 | * |
||||
764 | * @param string $source_url Source url. |
||||
765 | * @param string $wc_path Working copy path. |
||||
766 | * @param integer $largest_suggested_revision Largest revision, that is suggested in error message. |
||||
767 | * |
||||
768 | * @return void |
||||
769 | * @throws CommandException When merge conflicts detected. |
||||
770 | */ |
||||
771 | protected function ensureWorkingCopyWithoutConflicts($source_url, $wc_path, $largest_suggested_revision = null) |
||||
772 | { |
||||
773 | $this->io->write(' * Previous Merge Status ... '); |
||||
774 | |||||
775 | $conflicts = $this->_workingCopyConflictTracker->getNewConflicts($wc_path); |
||||
776 | |||||
777 | if ( !$conflicts ) { |
||||
778 | $this->io->writeln('<info>Successful</info>'); |
||||
779 | |||||
780 | return; |
||||
781 | } |
||||
782 | |||||
783 | $this->_workingCopyConflictTracker->add($wc_path); |
||||
784 | $this->io->writeln('<error>' . count($conflicts) . ' conflict(-s)</error>'); |
||||
785 | |||||
786 | $table = new Table($this->io->getOutput()); |
||||
787 | |||||
788 | if ( $largest_suggested_revision ) { |
||||
0 ignored issues
–
show
The expression
$largest_suggested_revision of type integer|null is loosely compared to true ; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||||
789 | $table->setHeaders(array( |
||||
790 | 'Path', |
||||
791 | 'Associated Revisions (before ' . $largest_suggested_revision . ')', |
||||
792 | )); |
||||
793 | } |
||||
794 | else { |
||||
795 | $table->setHeaders(array( |
||||
796 | 'Path', |
||||
797 | 'Associated Revisions', |
||||
798 | )); |
||||
799 | } |
||||
800 | |||||
801 | $revision_log = $this->getRevisionLog($source_url); |
||||
802 | $source_path = $this->repositoryConnector->getRelativePath($source_url) . '/'; |
||||
803 | |||||
804 | /** @var OutputHelper $output_helper */ |
||||
805 | $output_helper = $this->getHelper('output'); |
||||
806 | |||||
807 | foreach ( $conflicts as $conflict_path ) { |
||||
808 | $path_revisions = $revision_log->find('paths', $source_path . $conflict_path); |
||||
809 | $path_revisions = array_intersect($this->_usableRevisions, $path_revisions); |
||||
810 | |||||
811 | if ( $path_revisions && isset($largest_suggested_revision) ) { |
||||
0 ignored issues
–
show
The expression
$path_revisions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
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 ![]() |
|||||
812 | $path_revisions = $this->limitRevisions($path_revisions, $largest_suggested_revision); |
||||
813 | } |
||||
814 | |||||
815 | $table->addRow(array( |
||||
816 | $conflict_path, |
||||
817 | $path_revisions ? $output_helper->formatArray($path_revisions, 4) : '-', |
||||
818 | )); |
||||
819 | } |
||||
820 | |||||
821 | $table->render(); |
||||
822 | |||||
823 | throw new CommandException('Working copy contains unresolved merge conflicts.'); |
||||
824 | } |
||||
825 | |||||
826 | /** |
||||
827 | * Returns revisions not larger, then given one. |
||||
828 | * |
||||
829 | * @param array $revisions Revisions. |
||||
830 | * @param integer $max_revision Maximal revision. |
||||
831 | * |
||||
832 | * @return array |
||||
833 | */ |
||||
834 | protected function limitRevisions(array $revisions, $max_revision) |
||||
835 | { |
||||
836 | $ret = array(); |
||||
837 | |||||
838 | foreach ( $revisions as $revision ) { |
||||
839 | if ( $revision < $max_revision ) { |
||||
840 | $ret[] = $revision; |
||||
841 | } |
||||
842 | } |
||||
843 | |||||
844 | return $ret; |
||||
845 | } |
||||
846 | |||||
847 | /** |
||||
848 | * Performs commit unless user doesn't want it. |
||||
849 | * |
||||
850 | * @return boolean |
||||
851 | */ |
||||
852 | protected function performCommit() |
||||
853 | { |
||||
854 | $auto_commit = $this->io->getOption('auto-commit'); |
||||
855 | |||||
856 | if ( $auto_commit !== null ) { |
||||
857 | $auto_commit = $auto_commit === 'yes'; |
||||
858 | } |
||||
859 | else { |
||||
860 | $auto_commit = (boolean)$this->getSetting(self::SETTING_MERGE_AUTO_COMMIT); |
||||
861 | } |
||||
862 | |||||
863 | if ( $auto_commit ) { |
||||
864 | $auto_deploy = $this->io->getOption('auto-deploy'); |
||||
865 | |||||
866 | $commit_arguments = array( |
||||
867 | 'path' => $this->io->getArgument('path'), |
||||
868 | ); |
||||
869 | |||||
870 | if ( $auto_deploy !== null ) { |
||||
871 | $commit_arguments['--auto-deploy'] = $auto_deploy; |
||||
872 | } |
||||
873 | |||||
874 | $this->io->writeln(array('', 'Commencing automatic commit after merge ...')); |
||||
875 | $this->runOtherCommand('commit', $commit_arguments); |
||||
876 | } |
||||
877 | |||||
878 | return $auto_commit; |
||||
879 | } |
||||
880 | |||||
881 | /** |
||||
882 | * Prints revisions. |
||||
883 | * |
||||
884 | * @param string $source_url Merge source: url. |
||||
885 | * @param array $revisions Revisions. |
||||
886 | * |
||||
887 | * @return void |
||||
888 | */ |
||||
889 | protected function printRevisions($source_url, array $revisions) |
||||
890 | { |
||||
891 | $this->runOtherCommand('log', array( |
||||
892 | 'path' => $source_url, |
||||
893 | '--revisions' => implode(',', $revisions), |
||||
894 | '--merges' => $this->io->getOption('merges'), |
||||
895 | '--no-merges' => $this->io->getOption('no-merges'), |
||||
896 | '--with-full-message' => $this->io->getOption('with-full-message'), |
||||
897 | '--with-details' => $this->io->getOption('with-details'), |
||||
898 | '--with-summary' => $this->io->getOption('with-summary'), |
||||
899 | '--aggregate' => $this->io->getOption('aggregate'), |
||||
900 | '--with-merge-oracle' => true, |
||||
901 | )); |
||||
902 | } |
||||
903 | |||||
904 | /** |
||||
905 | * Returns list of config settings. |
||||
906 | * |
||||
907 | * @return AbstractConfigSetting[] |
||||
908 | */ |
||||
909 | public function getConfigSettings() |
||||
910 | { |
||||
911 | return array( |
||||
912 | new StringConfigSetting(self::SETTING_MERGE_SOURCE_URL, ''), |
||||
913 | new ArrayConfigSetting(self::SETTING_MERGE_RECENT_CONFLICTS, array()), |
||||
914 | new ChoiceConfigSetting( |
||||
915 | self::SETTING_MERGE_AUTO_COMMIT, |
||||
916 | array(1 => 'Yes', 0 => 'No'), |
||||
917 | 1 |
||||
918 | ), |
||||
919 | ); |
||||
920 | } |
||||
921 | |||||
922 | /** |
||||
923 | * Returns option names, that makes sense to use in aggregation mode. |
||||
924 | * |
||||
925 | * @return array |
||||
926 | */ |
||||
927 | public function getAggregatedOptions() |
||||
928 | { |
||||
929 | return array('with-full-message', 'with-details', 'with-summary'); |
||||
930 | } |
||||
931 | |||||
932 | /** |
||||
933 | * Determines if merge should be done in opposite direction (unmerge). |
||||
934 | * |
||||
935 | * @return boolean |
||||
936 | */ |
||||
937 | protected function isReverseMerge() |
||||
938 | { |
||||
939 | return $this->io->getOption('reverse'); |
||||
0 ignored issues
–
show
|
|||||
940 | } |
||||
941 | |||||
942 | } |
||||
943 |