Failed Conditions
Push — master ( acad7e...bfa4e7 )
by Alexander
02:33
created

MergeCommand::rememberConflicts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
ccs 0
cts 5
cp 0
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
crap 2
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\SVNBuddy\Config\AbstractConfigSetting;
15
use ConsoleHelpers\SVNBuddy\Config\ArrayConfigSetting;
16
use ConsoleHelpers\SVNBuddy\Config\ChoiceConfigSetting;
17
use ConsoleHelpers\SVNBuddy\Config\StringConfigSetting;
18
use ConsoleHelpers\ConsoleKit\Exception\CommandException;
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
	 * Unmerged revisions.
58
	 *
59
	 * @var array
60
	 */
61
	private $_unmergedRevisions = 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
				'bugs',
122
				'b',
123
				InputOption::VALUE_REQUIRED,
124
				'List of bug(-s) to merge, e.g. <comment>JRA-1234</comment>, <comment>43644</comment>'
125
			)
126
			->addOption(
127
				'with-details',
128
				'd',
129
				InputOption::VALUE_NONE,
130
				'Shows detailed revision information, e.g. paths affected'
131
			)
132
			->addOption(
133
				'with-summary',
134
				's',
135
				InputOption::VALUE_NONE,
136
				'Shows number of added/changed/removed paths in the revision'
137
			)
138
			->addOption(
139
				'auto-commit',
140
				null,
141
				InputOption::VALUE_REQUIRED,
142
				'Automatically perform commit on successful merge, e.g. <comment>yes</comment> or <comment>no</comment>'
143
			);
144
145
		parent::configure();
146
	}
147
148
	/**
149
	 * Return possible values for the named option
150
	 *
151
	 * @param string            $optionName Option name.
152
	 * @param CompletionContext $context    Completion context.
153
	 *
154
	 * @return array
155
	 */
156
	public function completeOptionValues($optionName, CompletionContext $context)
157
	{
158
		$ret = parent::completeOptionValues($optionName, $context);
159
160
		if ( $optionName === 'revisions' ) {
161
			return array('all');
162
		}
163
164
		if ( $optionName === 'source-url' ) {
165
			return $this->getAllRefs();
166
		}
167
168
		if ( $optionName === 'auto-commit' ) {
169
			return array('yes', 'no');
170
		}
171
172
		return $ret;
173
	}
174
175
	/**
176
	 * {@inheritdoc}
177
	 *
178
	 * @throws \RuntimeException When both "--bugs" and "--revisions" options were specified.
179
	 * @throws CommandException When everything is merged.
180
	 * @throws CommandException When manually specified revisions are already merged.
181
	 */
182
	protected function execute(InputInterface $input, OutputInterface $output)
183
	{
184
		$bugs = $this->getList($this->io->getOption('bugs'));
185
		$revisions = $this->getList($this->io->getOption('revisions'));
186
187
		if ( $bugs && $revisions ) {
2 ignored issues
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
188
			throw new \RuntimeException('The "--bugs" and "--revisions" options are mutually exclusive.');
189
		}
190
191
		$wc_path = $this->getWorkingCopyPath();
192
193
		$this->ensureLatestWorkingCopy($wc_path);
194
195
		$source_url = $this->getSourceUrl($wc_path);
196
		$this->printSourceAndTarget($source_url, $wc_path);
197
		$this->_unmergedRevisions = $this->getUnmergedRevisions($source_url, $wc_path);
198
199
		if ( ($bugs || $revisions) && !$this->_unmergedRevisions ) {
3 ignored issues
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->_unmergedRevisions 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 empty(..) or ! empty(...) instead.

Loading history...
200
			throw new CommandException('Nothing to merge.');
201
		}
202
203
		$this->ensureWorkingCopyWithoutConflicts($source_url, $wc_path);
204
205
		if ( $this->shouldMergeAll($revisions) ) {
206
			$revisions = $this->_unmergedRevisions;
207
		}
208
		else {
209
			if ( $revisions ) {
1 ignored issue
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
210
				$revisions = $this->getDirectRevisions($revisions, $source_url);
211
			}
212
			elseif ( $bugs ) {
1 ignored issue
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
213
				$revisions = $this->getRevisionLog($source_url)->find('bugs', $bugs);
214
			}
215
216
			if ( $revisions ) {
1 ignored issue
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
217
				$revisions = array_intersect($revisions, $this->_unmergedRevisions);
218
219
				if ( !$revisions ) {
1 ignored issue
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
220
					throw new CommandException('Requested revisions are already merged');
221
				}
222
			}
223
		}
224
225
		if ( $revisions ) {
1 ignored issue
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
226
			$this->performMerge($source_url, $wc_path, $revisions);
227
		}
228
		elseif ( $this->_unmergedRevisions ) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $this->_unmergedRevisions 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 empty(..) or ! empty(...) instead.

Loading history...
229
			$this->runOtherCommand('log', array(
230
				'path' => $source_url,
231
				'--revisions' => implode(',', $this->_unmergedRevisions),
232
				'--with-details' => $this->io->getOption('with-details'),
233
				'--with-summary' => $this->io->getOption('with-summary'),
234
				'--with-merge-oracle' => true,
235
			));
236
		}
237
	}
238
239
	/**
240
	 * Determines if all unmerged revisions should be merged.
241
	 *
242
	 * @param array $revisions Revisions.
243
	 *
244
	 * @return boolean
245
	 */
246
	protected function shouldMergeAll(array $revisions)
247
	{
248
		return $revisions === array(self::REVISION_ALL);
249
	}
250
251
	/**
252
	 * Ensures, that working copy is up to date.
253
	 *
254
	 * @param string $wc_path Working copy path.
255
	 *
256
	 * @return void
257
	 */
258
	protected function ensureLatestWorkingCopy($wc_path)
259
	{
260
		$this->io->write(' * Working Copy Status ... ');
261
262
		if ( $this->repositoryConnector->isMixedRevisionWorkingCopy($wc_path) ) {
263
			$this->io->writeln('<error>Mixed revisions</error>');
264
			$this->updateWorkingCopy($wc_path);
265
266
			return;
267
		}
268
269
		$working_copy_revision = $this->repositoryConnector->getLastRevision($wc_path);
270
		$repository_revision = $this->repositoryConnector->getLastRevision(
271
			$this->repositoryConnector->getWorkingCopyUrl($wc_path)
272
		);
273
274
		if ( $repository_revision > $working_copy_revision ) {
275
			$this->io->writeln('<error>Out of date</error>');
276
			$this->updateWorkingCopy($wc_path);
277
278
			return;
279
		}
280
281
		$this->io->writeln('<info>Up to date</info>');
282
	}
283
284
	/**
285
	 * Updates working copy.
286
	 *
287
	 * @param string $wc_path Working copy path.
288
	 *
289
	 * @return void
290
	 */
291
	protected function updateWorkingCopy($wc_path)
292
	{
293
		$this->runOtherCommand('update', array('path' => $wc_path));
294
	}
295
296
	/**
297
	 * Returns source url for merge.
298
	 *
299
	 * @param string $wc_path Working copy path.
300
	 *
301
	 * @return string
302
	 * @throws CommandException When source path is invalid.
303
	 */
304
	protected function getSourceUrl($wc_path)
305
	{
306
		$source_url = $this->io->getOption('source-url');
307
308
		if ( $source_url === null ) {
309
			$source_url = $this->getSetting(self::SETTING_MERGE_SOURCE_URL);
310
		}
311
		elseif ( !$this->repositoryConnector->isUrl($source_url) ) {
312
			$wc_url = $this->repositoryConnector->getWorkingCopyUrl($wc_path);
313
			$source_url = $this->_urlResolver->resolve($wc_url, $source_url);
314
		}
315
316
		if ( !$source_url ) {
317
			$wc_url = $this->repositoryConnector->getWorkingCopyUrl($wc_path);
318
			$source_url = $this->_mergeSourceDetector->detect($wc_url);
319
320
			if ( $source_url ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $source_url of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
321
				$this->setSetting(self::SETTING_MERGE_SOURCE_URL, $source_url);
322
			}
323
		}
324
325
		if ( !$source_url ) {
326
			$wc_url = $this->repositoryConnector->getWorkingCopyUrl($wc_path);
327
			$error_msg = 'Unable to guess "--source-url" option value. Please specify it manually.' . PHP_EOL;
328
			$error_msg .= 'Working Copy URL: ' . $wc_url . '.';
329
			throw new CommandException($error_msg);
330
		}
331
332
		return $source_url;
333
	}
334
335
	/**
336
	 * Prints information about merge source & target.
337
	 *
338
	 * @param string $source_url Merge source: url.
339
	 * @param string $wc_path    Merge target: working copy path.
340
	 *
341
	 * @return void
342
	 */
343
	protected function printSourceAndTarget($source_url, $wc_path)
344
	{
345
		$relative_source_url = $this->repositoryConnector->getRelativePath($source_url);
346
		$relative_target_url = $this->repositoryConnector->getRelativePath($wc_path);
347
348
		$this->io->writeln(' * Merge Source ... <info>' . $relative_source_url . '</info>');
349
		$this->io->writeln(' * Merge Target ... <info>' . $relative_target_url . '</info>');
350
	}
351
352
	/**
353
	 * Ensures, that there are some unmerged revisions.
354
	 *
355
	 * @param string $source_url Merge source: url.
356
	 * @param string $wc_path    Merge target: working copy path.
357
	 *
358
	 * @return array
359
	 */
360
	protected function getUnmergedRevisions($source_url, $wc_path)
361
	{
362
		// Avoid missing revision query progress bar overwriting following output.
363
		$revision_log = $this->getRevisionLog($source_url);
364
365
		$this->io->write(' * Upcoming Merge Status ... ');
366
		$unmerged_revisions = $this->calculateUnmergedRevisions($source_url, $wc_path);
367
368
		if ( $unmerged_revisions ) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $unmerged_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 empty(..) or ! empty(...) instead.

Loading history...
369
			$unmerged_bugs = $revision_log->getBugsFromRevisions($unmerged_revisions);
370
			$error_msg = '<error>%d revision(-s) or %d bug(-s) not merged</error>';
371
			$this->io->writeln(sprintf($error_msg, count($unmerged_revisions), count($unmerged_bugs)));
372
		}
373
		else {
374
			$this->io->writeln('<info>Up to date</info>');
375
		}
376
377
		return $unmerged_revisions;
378
	}
379
380
	/**
381
	 * Returns not merged revisions.
382
	 *
383
	 * @param string $source_url Merge source: url.
384
	 * @param string $wc_path    Merge target: working copy path.
385
	 *
386
	 * @return array
387
	 */
388
	protected function calculateUnmergedRevisions($source_url, $wc_path)
389
	{
390
		$command = $this->repositoryConnector->getCommand(
391
			'mergeinfo',
392
			'--show-revs eligible {' . $source_url . '} {' . $wc_path . '}'
393
		);
394
395
		$merge_info = $this->repositoryConnector->getProperty('svn:mergeinfo', $wc_path);
396
397
		$cache_invalidator = array(
398
			'source:' . $this->repositoryConnector->getLastRevision($source_url),
399
			'merged_hash:' . crc32($merge_info),
400
		);
401
		$command->setCacheInvalidator(implode(';', $cache_invalidator));
402
403
		$merge_info = $command->run();
404
		$merge_info = explode(PHP_EOL, $merge_info);
405
406
		foreach ( $merge_info as $index => $revision ) {
407
			$merge_info[$index] = ltrim($revision, 'r');
408
		}
409
410
		return array_filter($merge_info);
411
	}
412
413
	/**
414
	 * Parses information from "svn:mergeinfo" property.
415
	 *
416
	 * @param string $source_path Merge source: path in repository.
417
	 * @param string $wc_path     Merge target: working copy path.
418
	 *
419
	 * @return array
420
	 */
421
	protected function getMergedRevisions($source_path, $wc_path)
422
	{
423
		$merge_info = $this->repositoryConnector->getProperty('svn:mergeinfo', $wc_path);
424
		$merge_info = array_filter(explode("\n", $merge_info));
425
426
		foreach ( $merge_info as $merge_info_line ) {
427
			list($path, $revisions) = explode(':', $merge_info_line, 2);
428
429
			if ( $path === $source_path ) {
430
				return $this->_revisionListParser->expandRanges(explode(',', $revisions));
431
			}
432
		}
433
434
		return array();
435
	}
436
437
	/**
438
	 * Validates revisions to actually exist.
439
	 *
440
	 * @param array  $revisions      Revisions.
441
	 * @param string $repository_url Repository url.
442
	 *
443
	 * @return array
444
	 * @throws CommandException When revision doesn't exist.
445
	 */
446
	protected function getDirectRevisions(array $revisions, $repository_url)
447
	{
448
		$revision_log = $this->getRevisionLog($repository_url);
449
450
		try {
451
			$revisions = $this->_revisionListParser->expandRanges($revisions);
452
			$revision_log->getRevisionsData('summary', $revisions);
453
		}
454
		catch ( \InvalidArgumentException $e ) {
455
			throw new CommandException($e->getMessage());
456
		}
457
458
		return $revisions;
459
	}
460
461
	/**
462
	 * Performs merge.
463
	 *
464
	 * @param string $source_url Merge source: url.
465
	 * @param string $wc_path    Merge target: working copy path.
466
	 * @param array  $revisions  Revisions to merge.
467
	 *
468
	 * @return void
469
	 */
470
	protected function performMerge($source_url, $wc_path, array $revisions)
471
	{
472
		sort($revisions, SORT_NUMERIC);
473
474
		foreach ( $revisions as $revision ) {
475
			$command = $this->repositoryConnector->getCommand(
476
				'merge',
477
				'-c ' . $revision . ' {' . $source_url . '} {' . $wc_path . '}'
478
			);
479
480
			$merge_line = '--- Merging r' . $revision . " into '.':";
481
			$command->runLive(array(
482
				$wc_path => '.',
483
				$merge_line => '<fg=white;options=bold>' . $merge_line . '</>',
484
			));
485
486
			$this->_unmergedRevisions = array_diff($this->_unmergedRevisions, array($revision));
487
			$this->ensureWorkingCopyWithoutConflicts($source_url, $wc_path, $revision);
488
		}
489
490
		$this->performCommit();
491
	}
492
493
	/**
494
	 * Ensures, that there are no unresolved conflicts in working copy.
495
	 *
496
	 * @param string  $source_url                 Source url.
497
	 * @param string  $wc_path                    Working copy path.
498
	 * @param integer $largest_suggested_revision Largest revision, that is suggested in error message.
499
	 *
500
	 * @return void
501
	 * @throws CommandException When merge conflicts detected.
502
	 */
503
	protected function ensureWorkingCopyWithoutConflicts($source_url, $wc_path, $largest_suggested_revision = null)
504
	{
505
		$this->io->write(' * Previous Merge Status ... ');
506
507
		$conflicts = $this->_workingCopyConflictTracker->getNewConflicts($wc_path);
508
509
		if ( !$conflicts ) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $conflicts 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 empty(..) or ! empty(...) instead.

Loading history...
510
			$this->io->writeln('<info>Successful</info>');
511
512
			return;
513
		}
514
515
		$this->_workingCopyConflictTracker->add($wc_path);
516
		$this->io->writeln('<error>' . count($conflicts) . ' conflict(-s)</error>');
517
518
		$table = new Table($this->io->getOutput());
519
520
		if ( $largest_suggested_revision ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $largest_suggested_revision of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

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
Loading history...
521
			$table->setHeaders(array(
522
				'Path',
523
				'Associated Revisions (before ' . $largest_suggested_revision . ')',
524
			));
525
		}
526
		else {
527
			$table->setHeaders(array(
528
				'Path',
529
				'Associated Revisions',
530
			));
531
		}
532
533
		$revision_log = $this->getRevisionLog($source_url);
534
		$source_path = $this->repositoryConnector->getRelativePath($source_url) . '/';
535
536
		/** @var OutputHelper $output_helper */
537
		$output_helper = $this->getHelper('output');
538
539
		foreach ( $conflicts as $conflict_path ) {
540
			$path_revisions = $revision_log->find('paths', $source_path . $conflict_path);
541
			$path_revisions = array_intersect($this->_unmergedRevisions, $path_revisions);
542
543
			if ( $path_revisions && isset($largest_suggested_revision) ) {
1 ignored issue
show
Bug Best Practice introduced by
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 empty(..) or ! empty(...) instead.

Loading history...
544
				$path_revisions = $this->limitRevisions($path_revisions, $largest_suggested_revision);
545
			}
546
547
			$table->addRow(array(
548
				$conflict_path,
549
				$path_revisions ? $output_helper->formatArray($path_revisions, 4) : '-',
550
			));
551
		}
552
553
		$table->render();
554
555
		throw new CommandException('Working copy contains unresolved merge conflicts.');
556
	}
557
558
	/**
559
	 * Returns revisions not larger, then given one.
560
	 *
561
	 * @param array   $revisions    Revisions.
562
	 * @param integer $max_revision Maximal revision.
563
	 *
564
	 * @return array
565
	 */
566
	protected function limitRevisions(array $revisions, $max_revision)
567
	{
568
		$ret = array();
569
570
		foreach ( $revisions as $revision ) {
571
			if ( $revision < $max_revision ) {
572
				$ret[] = $revision;
573
			}
574
		}
575
576
		return $ret;
577
	}
578
579
	/**
580
	 * Performs commit unless user doesn't want it.
581
	 *
582
	 * @return void
583
	 */
584
	protected function performCommit()
585
	{
586
		$auto_commit = $this->io->getOption('auto-commit');
587
588
		if ( $auto_commit !== null ) {
589
			$auto_commit = $auto_commit === 'yes';
590
		}
591
		else {
592
			$auto_commit = (boolean)$this->getSetting(self::SETTING_MERGE_AUTO_COMMIT);
593
		}
594
595
		if ( $auto_commit ) {
596
			$this->io->writeln(array('', 'Commencing automatic commit after merge ...'));
597
			$this->runOtherCommand('commit');
598
		}
599
	}
600
601
	/**
602
	 * Returns list of config settings.
603
	 *
604
	 * @return AbstractConfigSetting[]
605
	 */
606
	public function getConfigSettings()
607
	{
608
		return array(
609
			new StringConfigSetting(self::SETTING_MERGE_SOURCE_URL, ''),
610
			new ArrayConfigSetting(self::SETTING_MERGE_RECENT_CONFLICTS, array()),
611
			new ChoiceConfigSetting(
612
				self::SETTING_MERGE_AUTO_COMMIT,
613
				array(1 => 'Yes', 0 => 'No'),
614
				1
615
			),
616
		);
617
	}
618
619
}
620