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
|
149 |
|
public function __construct(ExtendedPdoInterface $database, DatabaseCache $database_cache) |
42
|
|
|
{ |
43
|
149 |
|
$this->database = $database; |
44
|
149 |
|
$this->databaseCache = $database_cache; |
45
|
|
|
|
46
|
149 |
|
$this->databaseCache->cacheTable('Paths'); |
47
|
149 |
|
} |
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
|
71 |
|
public function addProject($path, $is_deleted = 0, $bug_regexp = null) |
59
|
|
|
{ |
60
|
71 |
|
$sql = 'INSERT INTO Projects (Path, IsDeleted, BugRegExp) |
61
|
|
|
VALUES (:path, :is_deleted, :bug_regexp)'; |
62
|
71 |
|
$this->database->perform($sql, array( |
63
|
71 |
|
'path' => $path, |
64
|
71 |
|
'is_deleted' => $is_deleted, |
65
|
71 |
|
'bug_regexp' => $bug_regexp, |
66
|
|
|
)); |
67
|
|
|
|
68
|
71 |
|
$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
|
71 |
|
if ( $path === '/' ) { |
72
|
3 |
|
$this->addPath($path, '', $path, 0); |
73
|
|
|
} |
74
|
|
|
|
75
|
71 |
|
return $project_id; |
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
|
11 |
|
public function setProjectBugRegexp($project_id, $bug_regexp) |
106
|
|
|
{ |
107
|
11 |
|
$sql = 'UPDATE Projects |
108
|
|
|
SET BugRegExp = :bug_regexp |
109
|
|
|
WHERE Id = :id'; |
110
|
11 |
|
$this->database->perform($sql, array( |
111
|
11 |
|
'bug_regexp' => $bug_regexp, |
112
|
11 |
|
'id' => $project_id, |
113
|
|
|
)); |
114
|
11 |
|
} |
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
|
65 |
|
public function addCommit($revision, $author, $date, $message) |
127
|
|
|
{ |
128
|
65 |
|
$sql = 'INSERT INTO Commits (Revision, Author, Date, Message) |
129
|
|
|
VALUES (:revision, :author, :date, :message)'; |
130
|
65 |
|
$this->database->perform($sql, array( |
131
|
65 |
|
'revision' => $revision, |
132
|
65 |
|
'author' => $author, |
133
|
65 |
|
'date' => $date, |
134
|
65 |
|
'message' => $message, |
135
|
|
|
)); |
136
|
65 |
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Adds commit to project. |
140
|
|
|
* |
141
|
|
|
* @param integer $revision Revision. |
142
|
|
|
* @param integer $project_id Project ID. |
143
|
|
|
* |
144
|
|
|
* @return void |
145
|
|
|
*/ |
146
|
61 |
|
public function addCommitToProject($revision, $project_id) |
147
|
|
|
{ |
148
|
61 |
|
$sql = 'INSERT INTO CommitProjects (ProjectId, Revision) |
149
|
|
|
VALUES (:project_id, :revision)'; |
150
|
61 |
|
$this->database->perform($sql, array('project_id' => $project_id, 'revision' => $revision)); |
151
|
61 |
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Adds path. |
155
|
|
|
* |
156
|
|
|
* @param string $path Path. |
157
|
|
|
* @param string $ref Ref. |
158
|
|
|
* @param string $project_path Project path. |
159
|
|
|
* @param integer $revision Revision. |
160
|
|
|
* |
161
|
|
|
* @return integer |
162
|
|
|
*/ |
163
|
81 |
|
public function addPath($path, $ref, $project_path, $revision) |
164
|
|
|
{ |
165
|
81 |
|
$sql = 'INSERT INTO Paths ( |
166
|
|
|
Path, PathNestingLevel, PathHash, RefName, ProjectPath, RevisionAdded, RevisionLastSeen |
167
|
|
|
) |
168
|
|
|
VALUES (:path, :path_nesting_level, :path_hash, :ref, :project_path, :revision, :revision)'; |
169
|
81 |
|
$this->database->perform($sql, array( |
170
|
81 |
|
'path' => $path, |
171
|
81 |
|
'path_nesting_level' => substr_count($path, '/') - 1, |
172
|
81 |
|
'path_hash' => $this->getPathChecksum($path), |
173
|
81 |
|
'ref' => $ref, |
174
|
81 |
|
'project_path' => $project_path, |
175
|
81 |
|
'revision' => $revision, |
176
|
|
|
)); |
177
|
81 |
|
$path_id = $this->database->lastInsertId(); |
178
|
|
|
|
179
|
81 |
|
$this->propagateRevisionLastSeen($path, $revision); |
180
|
|
|
|
181
|
81 |
|
return $path_id; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Adds path to commit. |
186
|
|
|
* |
187
|
|
|
* @param integer $revision Revision. |
188
|
|
|
* @param string $action Action. |
189
|
|
|
* @param string $kind Kind. |
190
|
|
|
* @param integer $path_id Path ID. |
191
|
|
|
* @param integer|null $copy_revision Copy revision. |
192
|
|
|
* @param integer|null $copy_path_id Copy path ID. |
193
|
|
|
* |
194
|
|
|
* @return void |
195
|
|
|
*/ |
196
|
74 |
|
public function addPathToCommit($revision, $action, $kind, $path_id, $copy_revision = null, $copy_path_id = null) |
197
|
|
|
{ |
198
|
74 |
|
$sql = 'INSERT INTO CommitPaths (Revision, Action, Kind, PathId, CopyRevision, CopyPathId) |
199
|
|
|
VALUES (:revision, :action, :kind, :path_id, :copy_revision, :copy_path_id)'; |
200
|
74 |
|
$this->database->perform($sql, array( |
201
|
74 |
|
'revision' => $revision, |
202
|
74 |
|
'action' => $action, |
203
|
74 |
|
'kind' => $kind, |
204
|
74 |
|
'path_id' => $path_id, |
205
|
74 |
|
'copy_revision' => $copy_revision, |
206
|
74 |
|
'copy_path_id' => $copy_path_id, |
207
|
|
|
)); |
208
|
74 |
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Touches given path. |
212
|
|
|
* |
213
|
|
|
* @param string $path Path. |
214
|
|
|
* @param integer $revision Revision. |
215
|
|
|
* @param array $fields_hash Fields hash. |
216
|
|
|
* |
217
|
|
|
* @return array |
218
|
|
|
* @throws \InvalidArgumentException When "$fields_hash" is empty. |
219
|
|
|
*/ |
220
|
35 |
|
public function touchPath($path, $revision, array $fields_hash) |
221
|
|
|
{ |
222
|
35 |
|
if ( !$fields_hash ) { |
|
|
|
|
223
|
1 |
|
throw new \InvalidArgumentException('The "$fields_hash" variable can\'t be empty.'); |
224
|
|
|
} |
225
|
|
|
|
226
|
34 |
|
$path_hash = $this->getPathChecksum($path); |
227
|
34 |
|
$to_update = $this->propagateRevisionLastSeen($path, $revision); |
228
|
34 |
|
$to_update[$path_hash] = $fields_hash; |
229
|
|
|
|
230
|
34 |
|
$bind_params = array_values($fields_hash); |
231
|
34 |
|
$bind_params[] = $path_hash; |
232
|
|
|
|
233
|
|
|
$sql = 'UPDATE Paths |
234
|
34 |
|
SET ' . implode(' = ?, ', array_keys($fields_hash)) . ' = ? |
235
|
|
|
WHERE PathHash = ?'; |
236
|
34 |
|
$this->database->perform($sql, $bind_params); |
237
|
|
|
|
238
|
34 |
|
return $to_update; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Propagates revision last seen. |
243
|
|
|
* |
244
|
|
|
* @param string $path Path. |
245
|
|
|
* @param string $revision Revision. |
246
|
|
|
* |
247
|
|
|
* @return array |
248
|
|
|
*/ |
249
|
81 |
|
protected function propagateRevisionLastSeen($path, $revision) |
250
|
|
|
{ |
251
|
81 |
|
$to_update = array(); |
252
|
81 |
|
$update_path = $path; |
253
|
|
|
|
254
|
81 |
|
$select_sql = 'SELECT RevisionLastSeen FROM Paths WHERE PathHash = :path_hash'; |
255
|
81 |
|
$update_sql = 'UPDATE Paths SET RevisionLastSeen = :revision WHERE PathHash = :path_hash'; |
256
|
|
|
|
257
|
81 |
|
while ( ($update_path = dirname($update_path) . '/') !== '//' ) { |
258
|
76 |
|
$update_path_hash = $this->getPathChecksum($update_path); |
259
|
|
|
|
260
|
76 |
|
$fields_hash = $this->databaseCache->getFromCache( |
261
|
76 |
|
'Paths', |
262
|
76 |
|
$update_path_hash . '/' . __METHOD__, |
263
|
|
|
$select_sql, |
264
|
76 |
|
array('path_hash' => $update_path_hash) |
265
|
|
|
); |
266
|
|
|
|
267
|
|
|
// Missing parent path. Can happen for example, when repository was created via "cvs2svn". |
268
|
76 |
|
if ( $fields_hash === false ) { |
269
|
52 |
|
$profiler = $this->database->getProfiler(); |
270
|
|
|
|
271
|
52 |
|
if ( $profiler instanceof StatementProfiler ) { |
272
|
12 |
|
$profiler->removeProfile($select_sql, array('path_hash' => $update_path_hash)); |
273
|
|
|
} |
274
|
52 |
|
break; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
// TODO: Collect these paths and issue single update after cycle finishes. |
278
|
37 |
|
if ( (int)$fields_hash['RevisionLastSeen'] < $revision ) { |
279
|
30 |
|
$this->database->perform( |
280
|
30 |
|
$update_sql, |
281
|
30 |
|
array('revision' => $revision, 'path_hash' => $update_path_hash) |
282
|
|
|
); |
283
|
|
|
|
284
|
30 |
|
$fields_hash = array('RevisionLastSeen' => $revision); |
285
|
30 |
|
$this->databaseCache->setIntoCache('Paths', $update_path_hash . '/' . __METHOD__, $fields_hash); |
286
|
30 |
|
$to_update[$update_path_hash] = $fields_hash; |
287
|
|
|
} |
288
|
|
|
}; |
289
|
|
|
|
290
|
81 |
|
return $to_update; |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Returns fields, that needs to be changed for given path. |
295
|
|
|
* |
296
|
|
|
* @param string $action Action. |
297
|
|
|
* @param integer $revision Revision. |
298
|
|
|
* @param array $path_data Path data. |
299
|
|
|
* |
300
|
|
|
* @return array |
301
|
|
|
*/ |
302
|
40 |
|
public function getPathTouchFields($action, $revision, array $path_data) |
303
|
|
|
{ |
304
|
40 |
|
$fields_hash = array(); |
305
|
|
|
|
306
|
40 |
|
if ( $action === 'D' ) { |
307
|
6 |
|
$fields_hash['RevisionDeleted'] = $revision; |
308
|
|
|
} |
309
|
|
|
else { |
310
|
37 |
|
if ( $path_data['RevisionDeleted'] > 0 ) { |
311
|
4 |
|
$fields_hash['RevisionDeleted'] = null; |
312
|
|
|
} |
313
|
|
|
|
314
|
37 |
|
if ( $action === 'A' && $path_data['RevisionAdded'] > $revision ) { |
315
|
2 |
|
$fields_hash['RevisionAdded'] = $revision; |
316
|
|
|
} |
317
|
|
|
|
318
|
37 |
|
if ( $path_data['RevisionLastSeen'] < $revision ) { |
319
|
34 |
|
$fields_hash['RevisionLastSeen'] = $revision; |
320
|
|
|
} |
321
|
|
|
} |
322
|
|
|
|
323
|
40 |
|
return $fields_hash; |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* Sets project path for given paths. |
328
|
|
|
* |
329
|
|
|
* @param array $path_ids Path IDs. |
330
|
|
|
* @param string $project_path Project path. |
331
|
|
|
* |
332
|
|
|
* @return void |
333
|
|
|
*/ |
334
|
4 |
|
public function movePathsIntoProject(array $path_ids, $project_path) |
335
|
|
|
{ |
336
|
4 |
|
$sql = 'UPDATE Paths |
337
|
|
|
SET ProjectPath = :path |
338
|
|
|
WHERE Id IN (:path_ids)'; |
339
|
4 |
|
$this->database->perform($sql, array( |
340
|
4 |
|
'path' => $project_path, |
341
|
4 |
|
'path_ids' => $path_ids, |
342
|
|
|
)); |
343
|
4 |
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Adds commit with bugs. |
347
|
|
|
* |
348
|
|
|
* @param array $bugs Bugs. |
349
|
|
|
* @param integer $revision Revision. |
350
|
|
|
* |
351
|
|
|
* @return void |
352
|
|
|
*/ |
353
|
57 |
|
public function addBugsToCommit(array $bugs, $revision) |
354
|
|
|
{ |
355
|
57 |
|
foreach ( $bugs as $bug ) { |
356
|
8 |
|
$sql = 'INSERT INTO CommitBugs (Revision, Bug) |
357
|
|
|
VALUES (:revision, :bug)'; |
358
|
8 |
|
$this->database->perform($sql, array( |
359
|
8 |
|
'revision' => $revision, |
360
|
8 |
|
'bug' => $bug, |
361
|
|
|
)); |
362
|
|
|
} |
363
|
57 |
|
} |
364
|
|
|
|
365
|
|
|
/** |
366
|
|
|
* Adds merge commit. |
367
|
|
|
* |
368
|
|
|
* @param integer $revision Revision. |
369
|
|
|
* @param array $merged_revisions Merged revisions. |
370
|
|
|
* |
371
|
|
|
* @return void |
372
|
|
|
*/ |
373
|
59 |
|
public function addMergeCommit($revision, array $merged_revisions) |
374
|
|
|
{ |
375
|
59 |
|
foreach ( $merged_revisions as $merged_revision ) { |
376
|
9 |
|
$sql = 'INSERT INTO Merges (MergeRevision, MergedRevision) |
377
|
|
|
VALUES (:merge_revision, :merged_revision)'; |
378
|
9 |
|
$this->database->perform($sql, array( |
379
|
9 |
|
'merge_revision' => $revision, |
380
|
9 |
|
'merged_revision' => $merged_revision, |
381
|
|
|
)); |
382
|
|
|
} |
383
|
59 |
|
} |
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* Adds ref to project. |
387
|
|
|
* |
388
|
|
|
* @param string $ref Ref. |
389
|
|
|
* @param integer $project_id Project ID. |
390
|
|
|
* |
391
|
|
|
* @return integer |
392
|
|
|
*/ |
393
|
39 |
|
public function addRefToProject($ref, $project_id) |
394
|
|
|
{ |
395
|
39 |
|
$sql = 'INSERT INTO ProjectRefs (ProjectId, Name) |
396
|
|
|
VALUES (:project_id, :name)'; |
397
|
39 |
|
$this->database->perform($sql, array('project_id' => $project_id, 'name' => $ref)); |
398
|
|
|
|
399
|
39 |
|
return $this->database->lastInsertId(); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Adds ref to commit and commit to project. |
404
|
|
|
* |
405
|
|
|
* @param integer $revision Revision. |
406
|
|
|
* @param integer $ref_id Ref ID. |
407
|
|
|
* |
408
|
|
|
* @return void |
409
|
|
|
*/ |
410
|
38 |
|
public function addCommitToRef($revision, $ref_id) |
411
|
|
|
{ |
412
|
38 |
|
$sql = 'INSERT INTO CommitRefs (Revision, RefId) |
413
|
|
|
VALUES (:revision, :ref_id)'; |
414
|
38 |
|
$this->database->perform($sql, array('revision' => $revision, 'ref_id' => $ref_id)); |
415
|
38 |
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Returns unsigned checksum of the path. |
419
|
|
|
* |
420
|
|
|
* @param string $path Path. |
421
|
|
|
* |
422
|
|
|
* @return integer |
423
|
|
|
*/ |
424
|
81 |
|
public function getPathChecksum($path) |
425
|
|
|
{ |
426
|
81 |
|
return sprintf('%u', crc32($path)); |
|
|
|
|
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
} |
430
|
|
|
|
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.