Completed
Push — master ( 12cab7...06984a )
by Alexander
31s queued 27s
created

RepositoryFiller::propagateRevisionDeleted()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
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 Aura\Sql\ExtendedPdoInterface;
15
use ConsoleHelpers\SVNBuddy\Database\DatabaseCache;
16
use ConsoleHelpers\SVNBuddy\Database\StatementProfiler;
17
18
class RepositoryFiller
19
{
20
21
	/**
22
	 * Database.
23
	 *
24
	 * @var ExtendedPdoInterface
25
	 */
26
	protected $database;
27
28
	/**
29
	 * Database cache.
30
	 *
31
	 * @var DatabaseCache
32
	 */
33
	protected $databaseCache;
34
35
	/**
36
	 * RepositoryFiller constructor.
37
	 *
38
	 * @param ExtendedPdoInterface $database       Database.
39
	 * @param DatabaseCache        $database_cache Database cache.
40
	 */
41 162
	public function __construct(ExtendedPdoInterface $database, DatabaseCache $database_cache)
42
	{
43 162
		$this->database = $database;
44 162
		$this->databaseCache = $database_cache;
45
46 162
		$this->databaseCache->cacheTable('Paths');
47 162
	}
48
49
	/**
50
	 * Creates a project.
51
	 *
52
	 * @param string      $path       Path.
53
	 * @param integer     $is_deleted Is Deleted.
54
	 * @param string|null $bug_regexp Bug regexp.
55
	 *
56
	 * @return integer
57
	 */
58 73
	public function addProject($path, $is_deleted = 0, $bug_regexp = null)
59
	{
60 73
		$sql = 'INSERT INTO Projects (Path, IsDeleted, BugRegExp)
61
				VALUES (:path, :is_deleted, :bug_regexp)';
62 73
		$this->database->perform($sql, array(
63 73
			'path' => $path,
64 73
			'is_deleted' => $is_deleted,
65 73
			'bug_regexp' => $bug_regexp,
66
		));
67
68 73
		$project_id = $this->database->lastInsertId();
69
70
		// There are no "0" revision in repository, but we need to bind project path to some revision.
71 73
		if ( $path === '/' ) {
72 3
			$this->addPath($path, '', $path, 0);
73
		}
74
75 73
		return $project_id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $project_id returns the type string which is incompatible with the documented return type integer.
Loading history...
76
	}
77
78
	/**
79
	 * Changes project status.
80
	 *
81
	 * @param integer $project_id Project ID.
82
	 * @param integer $is_deleted Is deleted flag.
83
	 *
84
	 * @return void
85
	 */
86 9
	public function setProjectStatus($project_id, $is_deleted)
87
	{
88 9
		$sql = 'UPDATE Projects
89
				SET IsDeleted = :is_deleted
90
				WHERE Id = :id';
91 9
		$this->database->perform($sql, array(
92 9
			'is_deleted' => (int)$is_deleted,
93 9
			'id' => $project_id,
94
		));
95 9
	}
96
97
	/**
98
	 * Changes project bug regexp.
99
	 *
100
	 * @param integer     $project_id Project ID.
101
	 * @param string|null $bug_regexp Bug regexp.
102
	 *
103
	 * @return void
104
	 */
105 13
	public function setProjectBugRegexp($project_id, $bug_regexp)
106
	{
107 13
		$sql = 'UPDATE Projects
108
				SET BugRegExp = :bug_regexp
109
				WHERE Id = :id';
110 13
		$this->database->perform($sql, array(
111 13
			'bug_regexp' => $bug_regexp,
112 13
			'id' => $project_id,
113
		));
114 13
	}
115
116
	/**
117
	 * Adds commit.
118
	 *
119
	 * @param integer $revision Revision.
120
	 * @param string  $author   Author.
121
	 * @param integer $date     Date.
122
	 * @param string  $message  Message.
123
	 *
124
	 * @return void
125
	 */
126 74
	public function addCommit($revision, $author, $date, $message)
127
	{
128 74
		$sql = 'INSERT INTO Commits (Revision, Author, Date, Message)
129
				VALUES (:revision, :author, :date, :message)';
130 74
		$this->database->perform($sql, array(
131 74
			'revision' => $revision,
132 74
			'author' => $author,
133 74
			'date' => $date,
134 74
			'message' => $message,
135
		));
136 74
	}
137
138
	/**
139
	 * Removes commit.
140
	 *
141
	 * @param integer $revision Revision.
142
	 *
143
	 * @return void
144
	 */
145 3
	public function removeCommit($revision)
146
	{
147 3
		$sql = 'DELETE FROM Commits
148
				WHERE Revision = :revision';
149 3
		$this->database->perform($sql, array(
150 3
			'revision' => $revision,
151
		));
152 3
	}
153
154
	/**
155
	 * Adds commit to project.
156
	 *
157
	 * @param integer $revision   Revision.
158
	 * @param integer $project_id Project ID.
159
	 *
160
	 * @return void
161
	 */
162 63
	public function addCommitToProject($revision, $project_id)
163
	{
164 63
		$sql = 'INSERT INTO CommitProjects (ProjectId, Revision)
165
				VALUES (:project_id, :revision)';
166 63
		$this->database->perform($sql, array('project_id' => $project_id, 'revision' => $revision));
167 63
	}
168
169
	/**
170
	 * Adds path.
171
	 *
172
	 * @param string  $path         Path.
173
	 * @param string  $ref          Ref.
174
	 * @param string  $project_path Project path.
175
	 * @param integer $revision     Revision.
176
	 *
177
	 * @return integer
178
	 */
179 84
	public function addPath($path, $ref, $project_path, $revision)
180
	{
181 84
		$sql = 'INSERT INTO Paths (
182
					Path, PathNestingLevel, PathHash, RefName, ProjectPath, RevisionAdded, RevisionLastSeen
183
				)
184
				VALUES (:path, :path_nesting_level, :path_hash, :ref, :project_path, :revision, :revision)';
185 84
		$this->database->perform($sql, array(
186 84
			'path' => $path,
187 84
			'path_nesting_level' => substr_count($path, '/') - 1,
188 84
			'path_hash' => $this->getPathChecksum($path),
189 84
			'ref' => $ref,
190 84
			'project_path' => $project_path,
191 84
			'revision' => $revision,
192
		));
193 84
		$path_id = $this->database->lastInsertId();
194
195 84
		$this->propagateRevisionLastSeen($path, $revision);
196
197 84
		return $path_id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $path_id returns the type string which is incompatible with the documented return type integer.
Loading history...
198
	}
199
200
	/**
201
	 * Adds path to commit.
202
	 *
203
	 * @param integer      $revision      Revision.
204
	 * @param string       $action        Action.
205
	 * @param string       $kind          Kind.
206
	 * @param integer      $path_id       Path ID.
207
	 * @param integer|null $copy_revision Copy revision.
208
	 * @param integer|null $copy_path_id  Copy path ID.
209
	 *
210
	 * @return void
211
	 */
212 77
	public function addPathToCommit($revision, $action, $kind, $path_id, $copy_revision = null, $copy_path_id = null)
213
	{
214 77
		$sql = 'INSERT INTO CommitPaths (Revision, Action, Kind, PathId, CopyRevision, CopyPathId)
215
				VALUES (:revision, :action, :kind, :path_id, :copy_revision, :copy_path_id)';
216 77
		$this->database->perform($sql, array(
217 77
			'revision' => $revision,
218 77
			'action' => $action,
219 77
			'kind' => $kind,
220 77
			'path_id' => $path_id,
221 77
			'copy_revision' => $copy_revision,
222 77
			'copy_path_id' => $copy_path_id,
223
		));
224 77
	}
225
226
	/**
227
	 * Touches given path.
228
	 *
229
	 * @param string  $path        Path.
230
	 * @param integer $revision    Revision.
231
	 * @param array   $fields_hash Fields hash.
232
	 *
233
	 * @return array
234
	 * @throws \InvalidArgumentException When "$fields_hash" is empty.
235
	 */
236 36
	public function touchPath($path, $revision, array $fields_hash)
237
	{
238 36
		if ( !$fields_hash ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields_hash 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...
239 1
			throw new \InvalidArgumentException('The "$fields_hash" variable can\'t be empty.');
240
		}
241
242
		// This is $revision, where $path was deleted.
243 35
		if ( array_key_exists('RevisionDeleted', $fields_hash)
244 35
			&& $fields_hash['RevisionDeleted'] !== null
245 35
			&& substr($path, -1) === '/'
246
		) {
247 4
			$this->propagateRevisionDeleted($path, $fields_hash['RevisionDeleted']);
248
		}
249
250 35
		$path_hash = $this->getPathChecksum($path);
251 35
		$to_update = $this->propagateRevisionLastSeen($path, $revision);
252 35
		$to_update[$path_hash] = $fields_hash;
253
254 35
		$bind_params = array_values($fields_hash);
255 35
		$bind_params[] = $path_hash;
256
257
		$sql = 'UPDATE Paths
258 35
				SET ' . implode(' = ?, ', array_keys($fields_hash)) . ' = ?
259
				WHERE PathHash = ?';
260 35
		$this->database->perform($sql, $bind_params);
261
262 35
		return $to_update;
263
	}
264
265
	/**
266
	 * Propagates revision last seen upwards in path hierarchy.
267
	 *
268
	 * @param string $path     Path.
269
	 * @param string $revision Revision.
270
	 *
271
	 * @return array
272
	 */
273 84
	protected function propagateRevisionLastSeen($path, $revision)
274
	{
275 84
		$to_update = array();
276 84
		$update_path = $path;
277
278 84
		$select_sql = 'SELECT RevisionLastSeen FROM Paths WHERE PathHash = :path_hash';
279 84
		$update_sql = 'UPDATE Paths SET RevisionLastSeen = :revision WHERE PathHash = :path_hash';
280
281 84
		while ( ($update_path = dirname($update_path) . '/') !== '//' ) {
282 79
			$update_path_hash = $this->getPathChecksum($update_path);
283
284 79
			$fields_hash = $this->databaseCache->getFromCache(
285 79
				'Paths',
286 79
				$update_path_hash . '/' . __METHOD__,
287 79
				$select_sql,
288 79
				array('path_hash' => $update_path_hash)
289
			);
290
291
			// Missing parent path. Can happen for example, when repository was created via "cvs2svn".
292 79
			if ( $fields_hash === false ) {
293 54
				$profiler = $this->database->getProfiler();
294
295 54
				if ( $profiler instanceof StatementProfiler ) {
296 12
					$profiler->removeProfile($select_sql, array('path_hash' => $update_path_hash));
297
				}
298 54
				break;
299
			}
300
301
			// TODO: Collect these paths and issue single update after cycle finishes.
302 40
			if ( (int)$fields_hash['RevisionLastSeen'] < $revision ) {
303 31
				$this->database->perform(
304 31
					$update_sql,
305 31
					array('revision' => $revision, 'path_hash' => $update_path_hash)
306
				);
307
308 31
				$fields_hash = array('RevisionLastSeen' => $revision);
309 31
				$this->databaseCache->setIntoCache('Paths', $update_path_hash . '/' . __METHOD__, $fields_hash);
310 31
				$to_update[$update_path_hash] = $fields_hash;
311
			}
312
		}
313
314 84
		return $to_update;
315
	}
316
317
	/**
318
	 * Propagates revision deleted downwards in path hierarchy.
319
	 *
320
	 * @param string $path     Path.
321
	 * @param string $revision Revision.
322
	 *
323
	 * @return void
324
	 */
325 4
	protected function propagateRevisionDeleted($path, $revision)
326
	{
327 4
		$sql = 'UPDATE Paths
328
				SET RevisionDeleted = :revision
329
				WHERE Path LIKE :path AND RevisionDeleted IS NULL';
330 4
		$this->database->perform($sql, array('revision' => $revision, 'path' => $path . '%'));
331 4
	}
332
333
	/**
334
	 * Returns fields, that needs to be changed for given path.
335
	 *
336
	 * @param string  $action    Action.
337
	 * @param integer $revision  Revision.
338
	 * @param array   $path_data Path data.
339
	 *
340
	 * @return array
341
	 */
342 41
	public function getPathTouchFields($action, $revision, array $path_data)
343
	{
344 41
		$fields_hash = array();
345
346 41
		if ( $action === 'D' ) {
347 7
			$fields_hash['RevisionDeleted'] = $revision;
348
		}
349
		else {
350 37
			if ( $path_data['RevisionDeleted'] > 0 ) {
351 4
				$fields_hash['RevisionDeleted'] = null;
352
			}
353
354 37
			if ( $action === 'A' && $path_data['RevisionAdded'] > $revision ) {
355 2
				$fields_hash['RevisionAdded'] = $revision;
356
			}
357
358 37
			if ( $path_data['RevisionLastSeen'] < $revision ) {
359 34
				$fields_hash['RevisionLastSeen'] = $revision;
360
			}
361
		}
362
363 41
		return $fields_hash;
364
	}
365
366
	/**
367
	 * Sets project path for given paths.
368
	 *
369
	 * @param array  $path_ids     Path IDs.
370
	 * @param string $project_path Project path.
371
	 *
372
	 * @return void
373
	 */
374 4
	public function movePathsIntoProject(array $path_ids, $project_path)
375
	{
376 4
		$sql = 'UPDATE Paths
377
				SET ProjectPath = :path
378
				WHERE Id IN (:path_ids)';
379 4
		$this->database->perform($sql, array(
380 4
			'path' => $project_path,
381 4
			'path_ids' => $path_ids,
382
		));
383 4
	}
384
385
	/**
386
	 * Adds commit with bugs.
387
	 *
388
	 * @param array   $bugs     Bugs.
389
	 * @param integer $revision Revision.
390
	 *
391
	 * @return void
392
	 */
393 64
	public function addBugsToCommit(array $bugs, $revision)
394
	{
395 64
		foreach ( $bugs as $bug ) {
396 11
			$sql = 'INSERT INTO CommitBugs (Revision, Bug)
397
					VALUES (:revision, :bug)';
398 11
			$this->database->perform($sql, array(
399 11
				'revision' => $revision,
400 11
				'bug' => $bug,
401
			));
402
		}
403 64
	}
404
405
	/**
406
	 * Removes commit with bugs.
407
	 *
408
	 * @param integer $revision Revision.
409
	 *
410
	 * @return integer
411
	 */
412 2
	public function removeBugsFromCommit($revision)
413
	{
414 2
		$sql = 'DELETE FROM CommitBugs
415
				WHERE Revision = :revision';
416
417 2
		return $this->database->fetchAffected($sql, array('revision' => $revision));
418
	}
419
420
	/**
421
	 * Adds merge commit.
422
	 *
423
	 * @param integer $revision         Revision.
424
	 * @param array   $merged_revisions Merged revisions.
425
	 *
426
	 * @return void
427
	 */
428 66
	public function addMergeCommit($revision, array $merged_revisions)
429
	{
430 66
		foreach ( $merged_revisions as $merged_revision ) {
431 12
			$sql = 'INSERT INTO Merges (MergeRevision, MergedRevision)
432
					VALUES (:merge_revision, :merged_revision)';
433 12
			$this->database->perform($sql, array(
434 12
				'merge_revision' => $revision,
435 12
				'merged_revision' => $merged_revision,
436
			));
437
		}
438 66
	}
439
440
	/**
441
	 * Removes merge commit.
442
	 *
443
	 * @param integer $revision Revision.
444
	 *
445
	 * @return integer
446
	 */
447 3
	public function removeMergeCommit($revision)
448
	{
449 3
		$sql = 'DELETE FROM Merges
450
				WHERE MergeRevision = :merge_revision';
451
452 3
		return $this->database->fetchAffected($sql, array(
453 3
			'merge_revision' => $revision,
454
		));
455
	}
456
457
	/**
458
	 * Adds ref to project.
459
	 *
460
	 * @param string  $ref        Ref.
461
	 * @param integer $project_id Project ID.
462
	 *
463
	 * @return integer
464
	 */
465 41
	public function addRefToProject($ref, $project_id)
466
	{
467 41
		$sql = 'INSERT INTO ProjectRefs (ProjectId, Name)
468
				VALUES (:project_id, :name)';
469 41
		$this->database->perform($sql, array('project_id' => $project_id, 'name' => $ref));
470
471 41
		return $this->database->lastInsertId();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->database->lastInsertId() returns the type string which is incompatible with the documented return type integer.
Loading history...
472
	}
473
474
	/**
475
	 * Adds ref to commit and commit to project.
476
	 *
477
	 * @param integer $revision Revision.
478
	 * @param integer $ref_id   Ref ID.
479
	 *
480
	 * @return void
481
	 */
482 40
	public function addCommitToRef($revision, $ref_id)
483
	{
484 40
		$sql = 'INSERT INTO CommitRefs (Revision, RefId)
485
				VALUES (:revision, :ref_id)';
486 40
		$this->database->perform($sql, array('revision' => $revision, 'ref_id' => $ref_id));
487 40
	}
488
489
	/**
490
	 * Returns unsigned checksum of the path.
491
	 *
492
	 * @param string $path Path.
493
	 *
494
	 * @return integer
495
	 */
496 84
	public function getPathChecksum($path)
497
	{
498 84
		return sprintf('%u', crc32($path));
0 ignored issues
show
Bug Best Practice introduced by
The expression return sprintf('%u', crc32($path)) returns the type string which is incompatible with the documented return type integer.
Loading history...
499
	}
500
501
}
502