Completed
Push — master ( 99bc8c...dcfce3 )
by Alexander
02:56
created

RevisionLog::find()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 2
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\Repository\RevisionLog;
12
13
14
use ConsoleHelpers\ConsoleKit\ConsoleIO;
15
use ConsoleHelpers\SVNBuddy\Cache\CacheManager;
16
use ConsoleHelpers\SVNBuddy\Repository\Connector\Connector;
17
18
class RevisionLog
19
{
20
21
	const CACHE_FORMAT_VERSION = 1;
22
23
	/**
24
	 * Repository path.
25
	 *
26
	 * @var string
27
	 */
28
	private $_repositoryUrl;
29
30
	/**
31
	 * Repository connector.
32
	 *
33
	 * @var Connector
34
	 */
35
	private $_repositoryConnector;
36
37
	/**
38
	 * Cache manager.
39
	 *
40
	 * @var CacheManager
41
	 */
42
	private $_cacheManager;
43
44
	/**
45
	 * Console IO.
46
	 *
47
	 * @var ConsoleIO
48
	 */
49
	private $_io;
50
51
	/**
52
	 * Installed plugins.
53
	 *
54
	 * @var IRevisionLogPlugin[]
55
	 */
56
	private $_plugins = array();
57
58
	/**
59
	 * Create revision log.
60
	 *
61
	 * @param string       $repository_url       Repository url.
62
	 * @param Connector    $repository_connector Repository connector.
63
	 * @param CacheManager $cache_manager        Cache.
64
	 * @param ConsoleIO    $io                   Console IO.
65
	 */
66 34
	public function __construct(
67
		$repository_url,
68
		Connector $repository_connector,
69
		CacheManager $cache_manager,
70
		ConsoleIO $io = null
71
	) {
72 34
		$this->_repositoryUrl = $repository_url;
73 34
		$this->_repositoryConnector = $repository_connector;
74 34
		$this->_cacheManager = $cache_manager;
75 34
		$this->_io = $io;
76 34
	}
77
78
	/**
79
	 * Queries missing revisions.
80
	 *
81
	 * @return void
82
	 * @throws \LogicException When no plugins are registered.
83
	 */
84 26
	public function refresh()
85
	{
86 26
		if ( !$this->_plugins ) {
87 1
			throw new \LogicException('Please register at least one revision log plugin.');
88
		}
89
90 25
		$project_url = $this->_repositoryConnector->getProjectUrl($this->_repositoryUrl);
91 25
		$url_regexp = '#^([^:\s]*)://([^@\s]+@)?([^/:\s]+)(:\d+)?([^\s]*)?$#';
92
93 25
		if ( preg_match($url_regexp, $project_url, $regs) ) {
94 25
			$cache_key = $regs[2] . $regs[3] . $regs[4] . '/log:' . $project_url;
95 25
		}
96
		else {
97
			$cache_key = 'misc/log:' . $project_url;
98
		}
99
100
		// Initialize plugins with data from cache.
101 25
		$cache = $this->_cacheManager->getCache($cache_key, $this->_getCacheInvalidator());
102
103 25
		if ( is_array($cache) ) {
104 6
			foreach ( $this->_plugins as $plugin_name => $plugin ) {
105 6
				$plugin->setCollectedData($cache[$plugin_name]);
106 6
			}
107 6
		}
108
109 25
		$from_revision = $this->_getLastRevision();
110 25
		$to_revision = $this->_repositoryConnector->getLastRevision($project_url);
111
112 25
		if ( $to_revision > $from_revision ) {
113 2
			$this->_queryRevisionData($from_revision, $to_revision);
114
115
			// Collect and cache plugin data.
116 2
			$cache = array();
117
118 2
			foreach ( $this->_plugins as $plugin_name => $plugin ) {
119 2
				$cache[$plugin_name] = $plugin->getCollectedData();
120 2
			}
121
122 2
			$this->_cacheManager->setCache($cache_key, $cache, $this->_getCacheInvalidator());
123 2
		}
124 25
	}
125
126
	/**
127
	 * Returns format version.
128
	 *
129
	 * @return mixed
130
	 */
131 25
	private function _getCacheInvalidator()
132
	{
133 25
		$invalidators = array('main' => 'main:' . self::CACHE_FORMAT_VERSION);
134
135 25
		foreach ( $this->_plugins as $plugin_name => $plugin ) {
136 25
			$invalidator_key = 'plugin(' . $plugin_name . ')';
137 25
			$invalidators[$invalidator_key] = $invalidator_key . ':' . $plugin->getCacheInvalidator();
138 25
		}
139
140 25
		ksort($invalidators);
141
142 25
		return implode(';', $invalidators);
143
	}
144
145
	/**
146
	 * Returns last known revision.
147
	 *
148
	 * @return integer
149
	 */
150 25
	private function _getLastRevision()
151
	{
152
		/** @var IRevisionLogPlugin $plugin */
153 25
		$plugin = reset($this->_plugins);
154 25
		$last_revision = $plugin->getLastRevision();
155
156 25
		if ( $last_revision === null ) {
157 20
			return $this->_repositoryConnector->getFirstRevision(
158 20
				$this->_repositoryConnector->getProjectUrl($this->_repositoryUrl)
159 20
			);
160
		}
161
162 5
		return $last_revision;
163
	}
164
165
	/**
166
	 * Queries missing revision data.
167
	 *
168
	 * @param integer $from_revision From revision.
169
	 * @param integer $to_revision   To revision.
170
	 *
171
	 * @return void
172
	 */
173 2
	private function _queryRevisionData($from_revision, $to_revision)
174
	{
175 2
		$range_start = $from_revision;
176 2
		$project_url = $this->_repositoryConnector->getProjectUrl($this->_repositoryUrl);
177
178
		// The "io" isn't set during autocomplete.
179 2
		if ( isset($this->_io) ) {
180 1
			$progress_bar = $this->_io->createProgressBar(ceil(($to_revision - $from_revision) / 200));
181 1
			$progress_bar->setFormat(
182
				' * Reading missing revisions: %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%'
183 1
			);
184 1
			$progress_bar->start();
185 1
		}
186
187 2
		while ( $range_start <= $to_revision ) {
188 2
			$range_end = min($range_start + 199, $to_revision);
189
190 2
			$command = $this->_repositoryConnector->getCommand(
191 2
				'log',
192 2
				'-r ' . $range_start . ':' . $range_end . ' --xml --verbose --use-merge-history {' . $project_url . '}'
193 2
			);
194 2
			$command->setCacheDuration('10 years');
195
196 2
			$this->_parseLog($command->run());
0 ignored issues
show
Bug introduced by
It seems like $command->run() targeting ConsoleHelpers\SVNBuddy\...onnector\Command::run() can also be of type string; however, ConsoleHelpers\SVNBuddy\...evisionLog::_parseLog() does only seem to accept object<SimpleXMLElement>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
197 2
			$range_start = $range_end + 1;
198
199 2
			if ( isset($progress_bar) ) {
200 1
				$progress_bar->advance();
201 1
			}
202 2
		}
203
204 2
		if ( isset($progress_bar) ) {
205 1
			$progress_bar->finish();
206 1
			$this->_io->writeln('');
207 1
		}
208 2
	}
209
210
	/**
211
	 * Parses output of "svn log" command.
212
	 *
213
	 * @param \SimpleXMLElement $log Log.
214
	 *
215
	 * @return void
216
	 */
217 2
	private function _parseLog(\SimpleXMLElement $log)
218
	{
219 2
		foreach ( $this->_plugins as $plugin ) {
220 2
			$plugin->parse($log);
221 2
		}
222 2
	}
223
224
	/**
225
	 * Registers a plugin.
226
	 *
227
	 * @param IRevisionLogPlugin $plugin Plugin.
228
	 *
229
	 * @return void
230
	 * @throws \LogicException When plugin is registered several times.
231
	 */
232 31
	public function registerPlugin(IRevisionLogPlugin $plugin)
233
	{
234 31
		$plugin_name = $plugin->getName();
235
236 31
		if ( $this->pluginRegistered($plugin_name) ) {
237 1
			throw new \LogicException('The "' . $plugin_name . '" revision log plugin is already registered.');
238
		}
239
240 31
		$this->_plugins[$plugin_name] = $plugin;
241 31
	}
242
243
	/**
244
	 * Finds information using plugin.
245
	 *
246
	 * @param string       $plugin_name Plugin name.
247
	 * @param array|string $criteria    Search criteria.
248
	 *
249
	 * @return array
250
	 * @throws \InvalidArgumentException When unknown plugin is given.
251
	 */
252 3
	public function find($plugin_name, $criteria)
253
	{
254 3
		if ( !$this->pluginRegistered($plugin_name) ) {
255 1
			throw new \InvalidArgumentException('The "' . $plugin_name . '" revision log plugin is unknown.');
256
		}
257
258 2
		return $this->_plugins[$plugin_name]->find((array)$criteria);
259
	}
260
261
	/**
262
	 * Returns information about revisions.
263
	 *
264
	 * @param string $plugin_name Plugin name.
265
	 * @param array  $revisions   Revisions.
266
	 *
267
	 * @return array
268
	 * @throws \InvalidArgumentException When unknown plugin is given.
269
	 */
270 3
	public function getRevisionsData($plugin_name, array $revisions)
271
	{
272 3
		if ( !$this->pluginRegistered($plugin_name) ) {
273 1
			throw new \InvalidArgumentException('The "' . $plugin_name . '" revision log plugin is unknown.');
274
		}
275
276 2
		return $this->_plugins[$plugin_name]->getRevisionsData($revisions);
277
	}
278
279
	/**
280
	 * Determines if plugin is registered.
281
	 *
282
	 * @param string $plugin_name Plugin name.
283
	 *
284
	 * @return boolean
285
	 */
286 33
	public function pluginRegistered($plugin_name)
287
	{
288 33
		return array_key_exists($plugin_name, $this->_plugins);
289
	}
290
291
	/**
292
	 * Returns bugs, from revisions.
293
	 *
294
	 * @param array $revisions Revisions.
295
	 *
296
	 * @return array
297
	 */
298 1
	public function getBugsFromRevisions(array $revisions)
299
	{
300 1
		$bugs = array();
301 1
		$revisions_bugs = $this->getRevisionsData('bugs', $revisions);
302
303 1
		foreach ( $revisions as $revision ) {
304 1
			$revision_bugs = $revisions_bugs[$revision];
305
306 1
			foreach ( $revision_bugs as $bug_id ) {
307 1
				$bugs[$bug_id] = true;
308 1
			}
309 1
		}
310
311 1
		return array_keys($bugs);
312
	}
313
314
}
315