Completed
Push — master ( 2abca0...5a82c2 )
by Alexander
02:30
created

CommitCommand   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 20
c 3
b 0
f 0
lcom 1
cbo 11
dl 0
loc 257
ccs 0
cts 107
cp 0
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
B configure() 0 29 1
A completeOptionValues() 0 10 2
A prepareDependencies() 0 11 1
B execute() 0 61 7
A getChangelist() 0 19 3
A getMergeTemplate() 0 10 2
A getMergeTemplateNames() 0 11 2
A getConfigSettings() 0 16 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\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 ) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $changelists 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...
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