Completed
Push — master ( da9393...b01d0f )
by Alexander
03:10
created

PathsPlugin::doParse()   B

Complexity

Conditions 10
Paths 37

Size

Total Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 10

Importance

Changes 0
Metric Value
dl 0
loc 57
ccs 34
cts 34
cp 1
rs 7.0715
c 0
b 0
f 0
cc 10
nc 37
nop 2
crap 10

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Plugin;
12
13
14
use Aura\Sql\ExtendedPdoInterface;
15
use ConsoleHelpers\SVNBuddy\Database\DatabaseCache;
16
use ConsoleHelpers\SVNBuddy\Repository\Connector\Connector;
17
use ConsoleHelpers\SVNBuddy\Repository\RevisionLog\PathCollisionDetector;
18
use ConsoleHelpers\SVNBuddy\Repository\RevisionLog\RepositoryFiller;
19
use ConsoleHelpers\SVNBuddy\Repository\RevisionLog\RevisionLog;
20
21
class PathsPlugin extends AbstractRepositoryCollectorPlugin
22
{
23
24
	const TYPE_NEW = 'new';
25
26
	const TYPE_EXISTING = 'existing';
27
28
	const STATISTIC_PATH_ADDED = 'path_added';
29
30
	const STATISTIC_PATH_FOUND = 'path_found';
31
32
	const STATISTIC_PROJECT_ADDED = 'project_added';
33
34
	const STATISTIC_PROJECT_FOUND = 'project_found';
35
36
	const STATISTIC_PROJECT_COLLISION_FOUND = 'project_collision_found';
37
38
	const STATISTIC_REF_ADDED = 'ref_added';
39
40
	const STATISTIC_REF_FOUND = 'ref_found';
41
42
	const STATISTIC_COMMIT_ADDED_TO_PROJECT = 'commit_added_to_project';
43
44
	const STATISTIC_COMMIT_ADDED_TO_REF = 'commit_added_to_ref';
45
46
	const STATISTIC_EMPTY_COMMIT = 'empty_commit';
47
48
	/**
49
	 * Database cache.
50
	 *
51
	 * @var DatabaseCache
52
	 */
53
	private $_databaseCache;
54
55
	/**
56
	 * Projects.
57
	 *
58
	 * @var array
59
	 */
60
	private $_projects = array(
61
		self::TYPE_NEW => array(),
62
		self::TYPE_EXISTING => array(),
63
	);
64
65
	/**
66
	 * Refs.
67
	 *
68
	 * @var array
69
	 */
70
	private $_refs = array();
71
72
	/**
73
	 * Repository connector.
74
	 *
75
	 * @var Connector
76
	 */
77
	private $_repositoryConnector;
78
79
	/**
80
	 * Path collision detector.
81
	 *
82
	 * @var PathCollisionDetector
83
	 */
84
	private $_pathCollisionDetector;
85
86
	/**
87
	 * Create paths revision log plugin.
88
	 *
89
	 * @param ExtendedPdoInterface  $database                Database.
90
	 * @param RepositoryFiller      $repository_filler       Repository filler.
91
	 * @param DatabaseCache         $database_cache          Database cache.
92
	 * @param Connector             $repository_connector    Repository connector.
93
	 * @param PathCollisionDetector $path_collision_detector Path collision detector.
94
	 */
95 46
	public function __construct(
96
		ExtendedPdoInterface $database,
97
		RepositoryFiller $repository_filler,
98
		DatabaseCache $database_cache,
99
		Connector $repository_connector,
100
		PathCollisionDetector $path_collision_detector
101
	) {
102 46
		parent::__construct($database, $repository_filler);
103
104 46
		$this->_databaseCache = $database_cache;
105 46
		$this->_repositoryConnector = $repository_connector;
106 46
		$this->_pathCollisionDetector = $path_collision_detector;
107
108 46
		$this->initDatabaseCache();
109 46
	}
110
111
	/**
112
	 * Hook, that is called before "RevisionLog::refresh" method call.
113
	 *
114
	 * @return void
115
	 */
116 46
	public function whenDatabaseReady()
117
	{
118 46
		$sql = 'SELECT Path
119
				FROM Projects';
120 46
		$project_paths = $this->database->fetchCol($sql);
121
122 46
		$this->_pathCollisionDetector->addPaths($project_paths);
123 46
	}
124
125
	/**
126
	 * Initializes database cache.
127
	 *
128
	 * @return void
129
	 */
130 46
	protected function initDatabaseCache()
131
	{
132 46
		$this->_databaseCache->cacheTable('Projects');
133 46
		$this->_databaseCache->cacheTable('ProjectRefs');
134 46
		$this->_databaseCache->cacheTable('Paths');
135 46
	}
136
137
	/**
138
	 * Returns plugin name.
139
	 *
140
	 * @return string
141
	 */
142 27
	public function getName()
143
	{
144 27
		return 'paths';
145
	}
146
147
	/**
148
	 * Returns revision query flags.
149
	 *
150
	 * @return array
151
	 */
152 1
	public function getRevisionQueryFlags()
153
	{
154 1
		return array(RevisionLog::FLAG_VERBOSE);
155
	}
156
157
	/**
158
	 * Defines parsing statistic types.
159
	 *
160
	 * @return array
161
	 */
162 46
	public function defineStatisticTypes()
163
	{
164
		return array(
165 46
			self::STATISTIC_PATH_ADDED,
166 46
			self::STATISTIC_PATH_FOUND,
167 46
			self::STATISTIC_PROJECT_ADDED,
168 46
			self::STATISTIC_PROJECT_FOUND,
169 46
			self::STATISTIC_PROJECT_COLLISION_FOUND,
170 46
			self::STATISTIC_REF_ADDED,
171 46
			self::STATISTIC_REF_FOUND,
172 46
			self::STATISTIC_COMMIT_ADDED_TO_PROJECT,
173 46
			self::STATISTIC_COMMIT_ADDED_TO_REF,
174 46
			self::STATISTIC_EMPTY_COMMIT,
175
		);
176
	}
177
178
	/**
179
	 * Does actual parsing.
180
	 *
181
	 * @param integer           $revision  Revision.
182
	 * @param \SimpleXMLElement $log_entry Log Entry.
183
	 *
184
	 * @return void
185
	 */
186 20
	protected function doParse($revision, \SimpleXMLElement $log_entry)
187
	{
188
		// Reset cached info after previous revision processing.
189 20
		$this->_projects = array(
190 20
			self::TYPE_NEW => array(),
191 20
			self::TYPE_EXISTING => array(),
192
		);
193 20
		$this->_refs = array();
194
195
		// This is empty revision.
196 20
		if ( !isset($log_entry->paths) ) {
197 1
			$this->recordStatistic(self::STATISTIC_EMPTY_COMMIT);
198
199 1
			return;
200
		}
201
202 19
		foreach ( $this->sortPaths($log_entry->paths) as $path_node ) {
203 19
			$kind = (string)$path_node['kind'];
204 19
			$action = (string)$path_node['action'];
205 19
			$path = $this->adaptPathToKind((string)$path_node, $kind);
206
207 19
			if ( $path_node['copyfrom-rev'] !== null ) {
208 2
				$copy_revision = (int)$path_node['copyfrom-rev'];
209 2
				$copy_path = $this->adaptPathToKind((string)$path_node['copyfrom-path'], $kind);
210 2
				$copy_path_id = $this->processPath($copy_path, $copy_revision, '', false);
211
			}
212
			else {
213 19
				$copy_revision = null;
214 19
				$copy_path_id = null;
215
			}
216
217 19
			$this->repositoryFiller->addPathToCommit(
218 19
				$revision,
219 19
				$action,
220 19
				$kind,
221 19
				$this->processPath($path, $revision, $action),
222 19
				isset($copy_revision) ? $copy_revision : null,
223 19
				isset($copy_path_id) ? $copy_path_id : null
224
			);
225
		}
226
227 19
		foreach ( array_keys($this->_projects[self::TYPE_EXISTING]) as $project_id ) {
228 2
			$this->addCommitToProject($revision, $project_id);
229
		}
230
231 19
		foreach ( $this->_projects[self::TYPE_NEW] as $project_id => $project_path ) {
232 6
			$associated_revisions = $this->addMissingCommitsToProject($project_id, $project_path);
233
234 6
			if ( !in_array($revision, $associated_revisions) ) {
235 4
				$this->addCommitToProject($revision, $project_id);
236
			}
237
		}
238
239 19
		foreach ( array_keys($this->_refs) as $ref_id ) {
240 6
			$this->addCommitToRef($revision, $ref_id);
241
		}
242 19
	}
243
244
	/**
245
	 * Sorts paths to move parent folders above their sub-folders.
246
	 *
247
	 * @param \SimpleXMLElement $paths Paths.
248
	 *
249
	 * @return \SimpleXMLElement[]
250
	 */
251 19
	protected function sortPaths(\SimpleXMLElement $paths)
252
	{
253 19
		$sorted_paths = array();
254
255 19
		foreach ( $paths->path as $path_node ) {
256 19
			$sorted_paths[(string)$path_node] = $path_node;
257
		}
258
259 19
		ksort($sorted_paths, defined('SORT_NATURAL') ? SORT_NATURAL : SORT_STRING);
260
261 19
		return $sorted_paths;
262
	}
263
264
	/**
265
	 * Processes path.
266
	 *
267
	 * @param string  $path     Path.
268
	 * @param integer $revision Revision.
269
	 * @param string  $action   Action.
270
	 * @param boolean $is_usage This is usage.
271
	 *
272
	 * @return integer
273
	 */
274 19
	protected function processPath($path, $revision, $action, $is_usage = true)
275
	{
276 19
		$path_hash = $this->repositoryFiller->getPathChecksum($path);
277
278 19
		$sql = 'SELECT Id, ProjectPath, RefName, RevisionAdded, RevisionDeleted, RevisionLastSeen
279
				FROM Paths
280
				WHERE PathHash = :path_hash';
281 19
		$path_data = $this->_databaseCache->getFromCache(
282 19
			'Paths',
283 19
			$path_hash,
284 19
			$sql,
285 19
			array('path_hash' => $path_hash)
286
		);
287
288 19
		if ( $path_data !== false ) {
289 8
			if ( $action ) {
290 7
				$fields_hash = $this->repositoryFiller->getPathTouchFields($action, $revision, $path_data);
0 ignored issues
show
Bug introduced by
It seems like $path_data defined by $this->_databaseCache->g...h_hash' => $path_hash)) on line 281 can also be of type boolean; however, ConsoleHelpers\SVNBuddy\...r::getPathTouchFields() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
291
292 7
				if ( $fields_hash ) {
293 7
					$touched_paths = $this->repositoryFiller->touchPath($path, $revision, $fields_hash);
294
295 7
					foreach ( $touched_paths as $touched_path_hash => $touched_path_fields_hash ) {
296 7
						if ( $this->_databaseCache->getFromCache('Paths', $touched_path_hash) !== false ) {
297 7
							$this->_databaseCache->setIntoCache('Paths', $touched_path_hash, $touched_path_fields_hash);
298
						}
299
					}
300
				}
301
			}
302
303 8
			if ( $path_data['ProjectPath'] && $path_data['RefName'] ) {
304 1
				$project_id = $this->processProject($path_data['ProjectPath'], $is_usage);
305
306
				// There is no project for missing copy source paths.
307 1
				if ( $project_id ) {
308 1
					$this->processRef($project_id, $path_data['RefName'], $is_usage);
309
				}
310
			}
311
312 8
			$this->recordStatistic(self::STATISTIC_PATH_FOUND);
313
314 8
			return $path_data['Id'];
315
		}
316
317 19
		$ref = $this->_repositoryConnector->getRefByPath($path);
318
319 19
		if ( $ref !== false ) {
320 7
			$project_path = substr($path, 0, strpos($path, $ref));
321
322 7
			if ( $this->_pathCollisionDetector->isCollision($project_path) ) {
323 2
				$project_path = $ref = '';
324 7
				$this->recordStatistic(self::STATISTIC_PROJECT_COLLISION_FOUND);
325
			}
326
		}
327
		else {
328 14
			$project_path = '';
329
		}
330
331 19
		$path_id = $this->repositoryFiller->addPath($path, (string)$ref, $project_path, $revision);
332 19
		$this->_databaseCache->setIntoCache('Paths', $path_hash, array(
333 19
			'Id' => $path_id,
334 19
			'ProjectPath' => $project_path,
335 19
			'RefName' => (string)$ref,
336 19
			'RevisionAdded' => $revision,
337
			'RevisionDeleted' => null,
338 19
			'RevisionLastSeen' => $revision,
339
		));
340
341 19
		if ( $project_path && $ref ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ref of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false 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...
342 6
			$project_id = $this->processProject($project_path, $is_usage);
343
344
			// There is no project for missing copy source paths.
345 6
			if ( $project_id ) {
346 6
				$this->processRef($project_id, $ref, $is_usage);
347
			}
348
		}
349
350 19
		$this->recordStatistic(self::STATISTIC_PATH_ADDED);
351
352 19
		return $path_id;
353
	}
354
355
	/**
356
	 * Adapts path to kind.
357
	 *
358
	 * @param string $path Path.
359
	 * @param string $kind Kind.
360
	 *
361
	 * @return string
362
	 */
363 19
	protected function adaptPathToKind($path, $kind)
364
	{
365 19
		if ( $kind === 'dir' ) {
366 12
			$path .= '/';
367
		}
368
369 19
		return $path;
370
	}
371
372
	/**
373
	 * Processes project.
374
	 *
375
	 * @param string  $project_path Project path.
376
	 * @param boolean $is_usage     This is usage.
377
	 *
378
	 * @return integer
379
	 */
380 6
	protected function processProject($project_path, $is_usage = true)
381
	{
382 6
		$sql = 'SELECT Id
383
				FROM Projects
384
				WHERE Path = :path';
385 6
		$project_data = $this->_databaseCache->getFromCache(
386 6
			'Projects',
387 6
			$project_path,
388 6
			$sql,
389 6
			array('path' => $project_path)
390
		);
391
392 6
		if ( $project_data !== false ) {
393 3
			$project_id = $project_data['Id'];
394
395
			// Don't consider project both new & existing (e.g. when single commit adds several branches).
396 3
			if ( $is_usage && !isset($this->_projects[self::TYPE_NEW][$project_id]) ) {
397 2
				$this->_projects[self::TYPE_EXISTING][$project_id] = $project_path;
398 2
				$this->recordStatistic(self::STATISTIC_PROJECT_FOUND);
399
			}
400
401 3
			return $project_id;
402
		}
403
404
		// Ignore fact, that project of non-used (copied) path doesn't exist.
405 6
		if ( !$is_usage ) {
406 1
			return null;
407
		}
408
409 6
		$project_id = $this->repositoryFiller->addProject($project_path);
410 6
		$this->_databaseCache->setIntoCache('Projects', $project_path, array('Id' => $project_id));
411 6
		$this->_pathCollisionDetector->addPaths(array($project_path));
412
413 6
		if ( $is_usage ) {
414 6
			$this->_projects[self::TYPE_NEW][$project_id] = $project_path;
415 6
			$this->recordStatistic(self::STATISTIC_PROJECT_ADDED);
416
		}
417
418 6
		return $project_id;
419
	}
420
421
	/**
422
	 * Processes ref.
423
	 *
424
	 * @param integer $project_id Project ID.
425
	 * @param string  $ref        Ref.
426
	 * @param boolean $is_usage   This is usage.
427
	 *
428
	 * @return integer
429
	 */
430 6
	protected function processRef($project_id, $ref, $is_usage = true)
431
	{
432 6
		$cache_key = $project_id . ':' . $ref;
433
434 6
		$sql = 'SELECT Id
435
				FROM ProjectRefs
436
				WHERE ProjectId = :project_id AND Name = :ref';
437 6
		$ref_data = $this->_databaseCache->getFromCache(
438 6
			'ProjectRefs',
439 6
			$cache_key,
440 6
			$sql,
441 6
			array('project_id' => $project_id, 'ref' => $ref)
442
		);
443
444 6
		if ( $ref_data !== false ) {
445 2
			$ref_id = $ref_data['Id'];
446
447 2
			if ( $is_usage ) {
448 2
				$this->_refs[$ref_id] = true;
449
			}
450
451 2
			$this->recordStatistic(self::STATISTIC_REF_FOUND);
452
453 2
			return $ref_id;
454
		}
455
456 6
		$ref_id = $this->repositoryFiller->addRefToProject($ref, $project_id);
457 6
		$this->_databaseCache->setIntoCache('ProjectRefs', $cache_key, array('Id' => $ref_id));
458
459 6
		if ( $is_usage ) {
460 6
			$this->_refs[$ref_id] = true;
461
		}
462
463 6
		$this->recordStatistic(self::STATISTIC_REF_ADDED);
464
465 6
		return $ref_id;
466
	}
467
468
	/**
469
	 * Retroactively map paths/commits to project, where path doesn't contain ref.
470
	 *
471
	 * @param integer $project_id   Project ID.
472
	 * @param string  $project_path Project path.
473
	 *
474
	 * @return array
475
	 */
476 6
	protected function addMissingCommitsToProject($project_id, $project_path)
477
	{
478 6
		$sql = "SELECT Id
479
				FROM Paths
480
				WHERE ProjectPath = '' AND Path LIKE :path_matcher";
481 6
		$paths_without_project = $this->database->fetchCol($sql, array('path_matcher' => $project_path . '%'));
482
483 6
		if ( !$paths_without_project ) {
484 4
			return array();
485
		}
486
487 2
		$this->repositoryFiller->movePathsIntoProject($paths_without_project, $project_path);
488
489 2
		$sql = 'SELECT Revision
490
				FROM CommitPaths
491
				WHERE PathId IN (:path_ids)';
492 2
		$commit_revisions = $this->database->fetchCol($sql, array('path_ids' => $paths_without_project));
493
494 2
		foreach ( array_unique($commit_revisions) as $commit_revision ) {
495 2
			$this->addCommitToProject($commit_revision, $project_id);
496
		}
497
498 2
		return $commit_revisions;
499
	}
500
501
	/**
502
	 * Associates revision with project.
503
	 *
504
	 * @param integer $revision   Revision.
505
	 * @param integer $project_id Project.
506
	 *
507
	 * @return void
508
	 */
509 6
	protected function addCommitToProject($revision, $project_id)
510
	{
511 6
		$this->repositoryFiller->addCommitToProject($revision, $project_id);
512 6
		$this->recordStatistic(self::STATISTIC_COMMIT_ADDED_TO_PROJECT);
513 6
	}
514
515
	/**
516
	 * Associates revision with ref.
517
	 *
518
	 * @param integer $revision Revision.
519
	 * @param integer $ref_id   Ref.
520
	 *
521
	 * @return void
522
	 */
523 6
	protected function addCommitToRef($revision, $ref_id)
524
	{
525 6
		$this->repositoryFiller->addCommitToRef($revision, $ref_id);
526 6
		$this->recordStatistic(self::STATISTIC_COMMIT_ADDED_TO_REF);
527 6
	}
528
529
	/**
530
	 * Find revisions by collected data.
531
	 *
532
	 * @param array       $criteria     Criteria.
533
	 * @param string|null $project_path Project path.
534
	 *
535
	 * @return array
536
	 */
537 18
	public function find(array $criteria, $project_path)
538
	{
539 18
		if ( !$criteria ) {
540 1
			return array();
541
		}
542
543 17
		$project_id = $this->getProject($project_path);
544
545 16
		if ( reset($criteria) === '' ) {
546
			// Include revisions from all paths.
547 1
			$path_revisions = $this->findAllRevisions($project_id);
548
		}
549
		else {
550
			// Include revisions from given sub-path only.
551 15
			$path_revisions = array();
552
553 15
			foreach ( $criteria as $criterion ) {
554 15
				if ( strpos($criterion, ':') === false ) {
555
					// Guess $field based on path itself.
556 7
					$field = substr($criterion, -1, 1) === '/' ? 'sub-match' : 'exact';
557 7
					$value = $criterion;
558
				}
559
				else {
560
					// Use $field, that is given.
561 8
					list ($field, $value) = explode(':', $criterion, 2);
562
				}
563
564 15
				$tmp_revisions = $this->processFieldCriterion($project_id, $field, $value);
565
566 14
				foreach ( $tmp_revisions as $revision ) {
567 9
					$path_revisions[$revision] = true;
568
				}
569
			}
570
		}
571
572 15
		$path_revisions = array_keys($path_revisions);
573 15
		sort($path_revisions, SORT_NUMERIC);
574
575 15
		return $path_revisions;
576
	}
577
578
	/**
579
	 * Finds all revisions in a project.
580
	 *
581
	 * @param integer $project_id Project ID.
582
	 *
583
	 * @return array
584
	 */
585 1
	protected function findAllRevisions($project_id)
586
	{
587
		// Same as getting all revisions from "projects" plugin.
588 1
		$sql = 'SELECT Revision
589
				FROM CommitProjects
590
				WHERE ProjectId = :project_id';
591
592 1
		return array_flip($this->database->fetchCol($sql, array('project_id' => $project_id)));
593
	}
594
595
	/**
596
	 * Processes search request, when field is specified in criterion.
597
	 *
598
	 * @param integer $project_id Project ID.
599
	 * @param string  $field      Field.
600
	 * @param mixed   $value      Value.
601
	 *
602
	 * @return array
603
	 * @throws \InvalidArgumentException When non-supported search field is given.
604
	 */
605 15
	protected function processFieldCriterion($project_id, $field, $value)
606
	{
607 15
		if ( $field === 'action' ) {
608 2
			return $this->findByAction($project_id, $value);
609
		}
610
611 14
		if ( $field === 'kind' ) {
612 1
			return $this->findByKind($project_id, $value);
613
		}
614
615 13
		if ( $field === 'exact' ) {
616 5
			return $this->findByExactMatch($project_id, $value);
617
		}
618
619 8
		if ( $field === 'sub-match' ) {
620 7
			return $this->findBySubMatch($project_id, $value);
621
		}
622
623 1
		$error_msg = 'Searching by "%s" is not supported by "%s" plugin.';
624 1
		throw new \InvalidArgumentException(sprintf($error_msg, $field, $this->getName()));
625
	}
626
627
	/**
628
	 * Finds revisions by action.
629
	 *
630
	 * @param integer $project_id Project id.
631
	 * @param string  $action     Action.
632
	 *
633
	 * @return array
634
	 */
635 2
	protected function findByAction($project_id, $action)
636
	{
637 2
		$sql = 'SELECT DISTINCT cpr.Revision
638
				FROM CommitPaths cpa
639
				JOIN CommitProjects cpr ON cpr.Revision = cpa.Revision
640
				WHERE cpr.ProjectId = :project_id AND cpa.Action LIKE :action';
641 2
		$tmp_revisions = $this->database->fetchCol($sql, array(
642 2
			'project_id' => $project_id,
643 2
			'action' => $action,
644
		));
645
646 2
		return $tmp_revisions;
647
	}
648
649
	/**
650
	 * Finds revisions by kind.
651
	 *
652
	 * @param integer $project_id Project ID.
653
	 * @param string  $kind       Kind.
654
	 *
655
	 * @return array
656
	 */
657 1
	protected function findByKind($project_id, $kind)
658
	{
659 1
		$sql = 'SELECT DISTINCT cpr.Revision
660
				FROM CommitPaths cpa
661
				JOIN CommitProjects cpr ON cpr.Revision = cpa.Revision
662
				WHERE cpr.ProjectId = :project_id AND cpa.Kind LIKE :kind';
663 1
		$tmp_revisions = $this->database->fetchCol($sql, array(
664 1
			'project_id' => $project_id,
665 1
			'kind' => $kind,
666
		));
667
668 1
		return $tmp_revisions;
669
	}
670
671
	/**
672
	 * Finds revisions by sub-match.
673
	 *
674
	 * @param integer      $project_id   Project ID.
675
	 * @param string       $path         Path.
676
	 * @param integer|null $max_revision Max revision.
677
	 *
678
	 * @return array
679
	 */
680 7
	protected function findBySubMatch($project_id, $path, $max_revision = null)
681
	{
682 7
		$path_id = $this->getPathId($path);
683
684 7
		if ( $path_id === false ) {
685 3
			return array();
686
		}
687
688 4
		$copy_data = $this->getPathCopyData($path_id, $max_revision);
689
690
		if ( $this->_repositoryConnector->isRefRoot($path) ) {
691
			$where_clause = array(
692
				'RefId = :ref_id',
693 4
			);
694 4
695
			$bind_params = array(
696
				'ref_id' => $this->getRefId($project_id, $this->_repositoryConnector->getRefByPath($path)),
0 ignored issues
show
Security Bug introduced by
It seems like $this->_repositoryConnector->getRefByPath($path) targeting ConsoleHelpers\SVNBuddy\...nnector::getRefByPath() can also be of type false; however, ConsoleHelpers\SVNBuddy\...PathsPlugin::getRefId() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
697 4
			);
698
699 2
			// Revisions, made after copy revision.
700
			if ( $copy_data ) {
701
				$where_clause[] = 'Revision >= :min_revision';
702
				$bind_params['min_revision'] = $copy_data['Revision'];
703 2
			}
704
705
			// Revisions made before copy revision.
706
			if ( isset($max_revision) ) {
707 2
				$where_clause[] = 'Revision < :max_revision';
708 2
				$bind_params['max_revision'] = $max_revision;
709 2
			}
710
711
			$sql = 'SELECT DISTINCT Revision
712
					FROM CommitRefs
713 2
					WHERE (' . implode(') AND (', $where_clause) . ')';
714 2
			$results = $this->database->fetchCol($sql, $bind_params);
715 2
		}
716
		else {
717
			$where_clause = array(
718
				'cpr.ProjectId = :project_id',
719
				'p.Path LIKE :path',
720 2
			);
721 2
722
			$bind_params = array(
723
				'project_id' => $project_id,
724
				'path' => $path . '%',
725 2
			);
726
727
			// Revisions, made after copy revision.
728
			if ( $copy_data ) {
729
				$where_clause[] = 'cpr.Revision >= :min_revision';
730 2
				$bind_params['min_revision'] = $copy_data['Revision'];
731 2
			}
732
733
			// Revisions made before copy revision.
734
			if ( isset($max_revision) ) {
735 2
				$where_clause[] = 'cpr.Revision < :max_revision';
736 2
				$bind_params['max_revision'] = $max_revision;
737 2
			}
738
739
			$sql = 'SELECT DISTINCT cpr.Revision
740
					FROM CommitProjects cpr
741 2
					JOIN CommitPaths cpa ON cpa.Revision = cpr.Revision
742 2
					JOIN Paths p ON p.Id = cpa.PathId
743 2
					WHERE (' . implode(') AND (', $where_clause) . ')';
744
			$results = $this->database->fetchCol($sql, $bind_params);
745
		}
746
747
		if ( !$copy_data ) {
748
			return $results;
749
		}
750 2
751 2
		return array_merge(
752
			$results,
753
			$this->findBySubMatch($project_id, $this->getPathFromId($copy_data['CopyPathId']), $copy_data['Revision'])
754 4
		);
755 4
	}
756
757
	/**
758 4
	 * Returns ref ID.
759 4
	 *
760 4
	 * @param integer $project_id Project ID.
761
	 * @param string  $ref_name   Ref name.
762
	 *
763
	 * @return integer
764
	 */
765
	protected function getRefId($project_id, $ref_name)
766
	{
767
		$sql = 'SELECT Id
768
				FROM ProjectRefs
769
				WHERE ProjectId = :project_id AND Name = :ref_name';
770
771
		return $this->database->fetchValue($sql, array(
772 2
			'project_id' => $project_id,
773
			'ref_name' => $ref_name,
774 2
		));
775
	}
776
777
	/**
778 2
	 * Finds revisions by exact match.
779 2
	 *
780 2
	 * @param integer      $project_id   Project ID.
781
	 * @param string       $path         Path.
782
	 * @param integer|null $max_revision Max revision.
783
	 *
784
	 * @return array
785
	 */
786
	protected function findByExactMatch($project_id, $path, $max_revision = null)
787
	{
788
		$path_id = $this->getPathId($path);
789
790
		if ( $path_id === false ) {
791
			return array();
792
		}
793 5
794
		$copy_data = $this->getPathCopyData($path_id, $max_revision);
795 5
796
		$where_clause = array(
797 5
			'cpr.ProjectId = :project_id',
798 2
			'p.PathHash = :path_hash',
799
		);
800
801 3
		$bind_params = array(
802
			'project_id' => $project_id,
803
			'path_hash' => $this->repositoryFiller->getPathChecksum($path),
804
		);
805
806 3
		// Revisions, made after copy revision.
807 3
		if ( $copy_data ) {
808
			$where_clause[] = 'cpr.Revision >= :min_revision';
809
			$bind_params['min_revision'] = $copy_data['Revision'];
810
		}
811 3
812
		// Revisions made before copy revision.
813
		if ( isset($max_revision) ) {
814
			$where_clause[] = 'cpr.Revision < :max_revision';
815
			$bind_params['max_revision'] = $max_revision;
816 3
		}
817 3
818
		$sql = 'SELECT DISTINCT cpr.Revision
819
				FROM CommitProjects cpr
820
				JOIN CommitPaths cpa ON cpa.Revision = cpr.Revision
821 3
				JOIN Paths p ON p.Id = cpa.PathId
822 2
				WHERE (' . implode(') AND (', $where_clause) . ')';
823 2
		$results = $this->database->fetchCol($sql, $bind_params);
824
825
		if ( !$copy_data ) {
826
			return $results;
827 3
		}
828 2
829 2
		return array_merge(
830
			$results,
831
			$this->findByExactMatch($project_id, $this->getPathFromId($copy_data['CopyPathId']), $copy_data['Revision'])
832
		);
833
	}
834
835
	/**
836 3
	 * Returns path copy data.
837 3
	 *
838
	 * @param integer      $path_id      Path ID.
839 3
	 * @param integer|null $max_revision Max revision.
840 3
	 *
841
	 * @return array
842
	 */
843 2
	protected function getPathCopyData($path_id, $max_revision = null)
844 2
	{
845 2
		$where_clause = array(
846
			'PathId = :path_id',
847
			'CopyPathId IS NOT NULL',
848
		);
849
850
		$bind_params = array(
851
			'path_id' => $path_id,
852
		);
853
854
		// Copy made before, maximal revision.
855
		if ( isset($max_revision) ) {
856 12
			$where_clause[] = 'Revision <= :max_revision';
857
			$bind_params['max_revision'] = $max_revision;
858 12
		}
859
860
		$sql = 'SELECT Revision, CopyPathId
861
				FROM CommitPaths
862 12
				WHERE (' . implode(') AND (', $where_clause) . ')
863 12
				ORDER BY Revision DESC
864
				LIMIT 1';
865
866
		return $this->database->fetchOne($sql, $bind_params);
867
	}
868
869
	/**
870
	 * Returns path id.
871
	 *
872
	 * @param string $path Path.
873
	 *
874 6
	 * @return integer
875
	 */
876 6
	protected function getPathId($path)
877
	{
878
		$sql = 'SELECT Id
879
				FROM Paths
880 6
				WHERE PathHash = :path_hash';
881 6
882
		return $this->database->fetchValue($sql, array(
883
			'path_hash' => $this->repositoryFiller->getPathChecksum($path),
884
		));
885
	}
886
887
	/**
888
	 * Returns path by id.
889
	 *
890
	 * @param integer $path_id Path ID.
891
	 *
892 2
	 * @return string
893
	 */
894 2
	protected function getPathFromId($path_id)
895
	{
896 2
		$sql = 'SELECT Path
897
				FROM Paths
898
				WHERE Id = :path_id';
899
900
		return $this->database->fetchValue($sql, array(
901 2
			'path_id' => $path_id,
902
		));
903 2
	}
904 1
905
	/**
906 1
	 * Returns information about revisions.
907 1
	 *
908
	 * @param array $revisions Revisions.
909
	 *
910 1
	 * @return array
911 1
	 */
912 1
	public function getRevisionsData(array $revisions)
913 1
	{
914 1
		$results = array();
915 1
916
		$sql = 'SELECT cp.Revision, p1.Path, cp.Kind, cp.Action, p2.Path AS CopyPath, cp.CopyRevision
917
				FROM CommitPaths cp
918
				JOIN Paths p1 ON p1.Id = cp.PathId
919 2
				LEFT JOIN Paths p2 ON p2.Id = cp.CopyPathId
920
				WHERE cp.Revision IN (:revision_ids)';
921 1
		$revisions_data = $this->database->fetchAll($sql, array('revision_ids' => $revisions));
922
923
		foreach ( $revisions_data as $revision_data ) {
924
			$revision = $revision_data['Revision'];
925
926
			if ( !isset($results[$revision]) ) {
927
				$results[$revision] = array();
928
			}
929
930
			$results[$revision][] = array(
931
				'path' => $revision_data['Path'],
932
				'kind' => $revision_data['Kind'],
933
				'action' => $revision_data['Action'],
934
				'copyfrom-path' => $revision_data['CopyPath'],
935
				'copyfrom-rev' => $revision_data['CopyRevision'],
936
			);
937
		}
938
939
		$this->assertNoMissingRevisions($revisions, $results);
940
941
		return $results;
942
	}
943
944
	/**
945
	 * Frees consumed memory.
946
	 *
947
	 * @return void
948
	 *
949
	 * @codeCoverageIgnore
950
	 */
951
	protected function freeMemoryManually()
952
	{
953
		parent::freeMemoryManually();
954
955
		$this->_databaseCache->clear();
956
	}
957
958
}
959