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
|
18 |
|
public function __construct( |
67
|
|
|
$repository_url, |
68
|
|
|
Connector $repository_connector, |
69
|
|
|
CacheManager $cache_manager, |
70
|
|
|
ConsoleIO $io = null |
71
|
|
|
) { |
72
|
18 |
|
$this->_repositoryUrl = $repository_url; |
73
|
18 |
|
$this->_repositoryConnector = $repository_connector; |
74
|
18 |
|
$this->_cacheManager = $cache_manager; |
75
|
18 |
|
$this->_io = $io; |
76
|
18 |
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Queries missing revisions. |
80
|
|
|
* |
81
|
|
|
* @return void |
82
|
|
|
* @throws \LogicException When no plugins are registered. |
83
|
|
|
*/ |
84
|
10 |
|
public function refresh() |
85
|
|
|
{ |
86
|
10 |
|
if ( !$this->_plugins ) { |
87
|
1 |
|
throw new \LogicException('Please register at least one revision log plugin.'); |
88
|
|
|
} |
89
|
|
|
|
90
|
9 |
|
$project_url = $this->_repositoryConnector->getProjectUrl($this->_repositoryUrl); |
91
|
|
|
|
92
|
|
|
// Initialize plugins with data from cache. |
93
|
9 |
|
$cache_key = 'log:' . $project_url; |
94
|
9 |
|
$cache = $this->_cacheManager->getCache($cache_key, $this->_getCacheInvalidator()); |
95
|
|
|
|
96
|
9 |
|
if ( is_array($cache) ) { |
97
|
6 |
|
foreach ( $this->_plugins as $plugin_name => $plugin ) { |
98
|
6 |
|
$plugin->setCollectedData($cache[$plugin_name]); |
99
|
6 |
|
} |
100
|
6 |
|
} |
101
|
|
|
|
102
|
9 |
|
$from_revision = $this->_getLastRevision(); |
103
|
9 |
|
$to_revision = $this->_repositoryConnector->getLastRevision($project_url); |
104
|
|
|
|
105
|
9 |
|
if ( $to_revision > $from_revision ) { |
106
|
2 |
|
$this->_queryRevisionData($from_revision, $to_revision); |
107
|
|
|
|
108
|
|
|
// Collect and cache plugin data. |
109
|
2 |
|
$cache = array(); |
110
|
|
|
|
111
|
2 |
|
foreach ( $this->_plugins as $plugin_name => $plugin ) { |
112
|
2 |
|
$cache[$plugin_name] = $plugin->getCollectedData(); |
113
|
2 |
|
} |
114
|
|
|
|
115
|
2 |
|
$this->_cacheManager->setCache($cache_key, $cache, $this->_getCacheInvalidator()); |
116
|
2 |
|
} |
117
|
9 |
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* Returns format version. |
121
|
|
|
* |
122
|
|
|
* @return mixed |
123
|
|
|
*/ |
124
|
9 |
|
private function _getCacheInvalidator() |
125
|
|
|
{ |
126
|
9 |
|
$invalidators = array('main' => 'main:' . self::CACHE_FORMAT_VERSION); |
127
|
|
|
|
128
|
9 |
|
foreach ( $this->_plugins as $plugin_name => $plugin ) { |
129
|
9 |
|
$invalidator_key = 'plugin(' . $plugin_name . ')'; |
130
|
9 |
|
$invalidators[$invalidator_key] = $invalidator_key . ':' . $plugin->getCacheInvalidator(); |
131
|
9 |
|
} |
132
|
|
|
|
133
|
9 |
|
ksort($invalidators); |
134
|
|
|
|
135
|
9 |
|
return implode(';', $invalidators); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Returns last known revision. |
140
|
|
|
* |
141
|
|
|
* @return integer |
142
|
|
|
*/ |
143
|
9 |
|
private function _getLastRevision() |
144
|
|
|
{ |
145
|
|
|
/** @var IRevisionLogPlugin $plugin */ |
146
|
9 |
|
$plugin = reset($this->_plugins); |
147
|
9 |
|
$last_revision = $plugin->getLastRevision(); |
148
|
|
|
|
149
|
9 |
|
if ( $last_revision === null ) { |
150
|
4 |
|
return $this->_repositoryConnector->getFirstRevision( |
151
|
4 |
|
$this->_repositoryConnector->getProjectUrl($this->_repositoryUrl) |
152
|
4 |
|
); |
153
|
|
|
} |
154
|
|
|
|
155
|
5 |
|
return $last_revision; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Queries missing revision data. |
160
|
|
|
* |
161
|
|
|
* @param integer $from_revision From revision. |
162
|
|
|
* @param integer $to_revision To revision. |
163
|
|
|
* |
164
|
|
|
* @return void |
165
|
|
|
*/ |
166
|
2 |
|
private function _queryRevisionData($from_revision, $to_revision) |
167
|
|
|
{ |
168
|
2 |
|
$range_start = $from_revision; |
169
|
2 |
|
$project_url = $this->_repositoryConnector->getProjectUrl($this->_repositoryUrl); |
170
|
|
|
|
171
|
|
|
// The "io" isn't set during autocomplete. |
172
|
2 |
|
if ( isset($this->_io) ) { |
173
|
1 |
|
$progress_bar = $this->_io->createProgressBar(ceil(($to_revision - $from_revision) / 1000)); |
174
|
1 |
|
$progress_bar->setFormat( |
175
|
|
|
' * Reading missing revisions: %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%' |
176
|
1 |
|
); |
177
|
1 |
|
$progress_bar->start(); |
178
|
1 |
|
} |
179
|
|
|
|
180
|
2 |
|
while ( $range_start < $to_revision ) { |
181
|
2 |
|
$range_end = min($range_start + 1000, $to_revision); |
182
|
|
|
|
183
|
2 |
|
$command = $this->_repositoryConnector->getCommand( |
184
|
2 |
|
'log', |
185
|
2 |
|
'-r ' . $range_start . ':' . $range_end . ' --xml --verbose --use-merge-history {' . $project_url . '}' |
186
|
2 |
|
); |
187
|
|
|
|
188
|
2 |
|
$this->_parseLog($command->run()); |
|
|
|
|
189
|
2 |
|
$range_start = $range_end + 1; |
190
|
|
|
|
191
|
2 |
|
if ( isset($progress_bar) ) { |
192
|
1 |
|
$progress_bar->advance(); |
193
|
1 |
|
} |
194
|
2 |
|
} |
195
|
|
|
|
196
|
2 |
|
if ( isset($progress_bar) ) { |
197
|
1 |
|
$progress_bar->finish(); |
198
|
1 |
|
$this->_io->writeln(''); |
199
|
1 |
|
} |
200
|
2 |
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Parses output of "svn log" command. |
204
|
|
|
* |
205
|
|
|
* @param \SimpleXMLElement $log Log. |
206
|
|
|
* |
207
|
|
|
* @return void |
208
|
|
|
*/ |
209
|
2 |
|
private function _parseLog(\SimpleXMLElement $log) |
210
|
|
|
{ |
211
|
2 |
|
foreach ( $this->_plugins as $plugin ) { |
212
|
2 |
|
$plugin->parse($log); |
213
|
2 |
|
} |
214
|
2 |
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Registers a plugin. |
218
|
|
|
* |
219
|
|
|
* @param IRevisionLogPlugin $plugin Plugin. |
220
|
|
|
* |
221
|
|
|
* @return void |
222
|
|
|
* @throws \LogicException When plugin is registered several times. |
223
|
|
|
*/ |
224
|
15 |
|
public function registerPlugin(IRevisionLogPlugin $plugin) |
225
|
|
|
{ |
226
|
15 |
|
$plugin_name = $plugin->getName(); |
227
|
|
|
|
228
|
15 |
|
if ( $this->pluginRegistered($plugin_name) ) { |
229
|
1 |
|
throw new \LogicException('The "' . $plugin_name . '" revision log plugin is already registered.'); |
230
|
|
|
} |
231
|
|
|
|
232
|
15 |
|
$this->_plugins[$plugin_name] = $plugin; |
233
|
15 |
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Finds information using plugin. |
237
|
|
|
* |
238
|
|
|
* @param string $plugin_name Plugin name. |
239
|
|
|
* @param array|string $criteria Search criteria. |
240
|
|
|
* |
241
|
|
|
* @return array |
242
|
|
|
* @throws \InvalidArgumentException When unknown plugin is given. |
243
|
|
|
*/ |
244
|
3 |
|
public function find($plugin_name, $criteria) |
245
|
|
|
{ |
246
|
3 |
|
if ( !$this->pluginRegistered($plugin_name) ) { |
247
|
1 |
|
throw new \InvalidArgumentException('The "' . $plugin_name . '" revision log plugin is unknown.'); |
248
|
|
|
} |
249
|
|
|
|
250
|
2 |
|
return $this->_plugins[$plugin_name]->find((array)$criteria); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Returns information about revision. |
255
|
|
|
* |
256
|
|
|
* @param string $plugin_name Plugin name. |
257
|
|
|
* @param integer $revision Revision. |
258
|
|
|
* |
259
|
|
|
* @return array |
260
|
|
|
* @throws \InvalidArgumentException When unknown plugin is given. |
261
|
|
|
*/ |
262
|
3 |
|
protected function getRevisionData($plugin_name, $revision) |
263
|
|
|
{ |
264
|
3 |
|
if ( !$this->pluginRegistered($plugin_name) ) { |
265
|
1 |
|
throw new \InvalidArgumentException('The "' . $plugin_name . '" revision log plugin is unknown.'); |
266
|
|
|
} |
267
|
|
|
|
268
|
2 |
|
return $this->_plugins[$plugin_name]->getRevisionData($revision); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Returns information about revisions. |
273
|
|
|
* |
274
|
|
|
* @param string $plugin_name Plugin name. |
275
|
|
|
* @param array $revisions Revisions. |
276
|
|
|
* |
277
|
|
|
* @return array |
278
|
17 |
|
* @throws \InvalidArgumentException When unknown plugin is given. |
279
|
|
|
*/ |
280
|
17 |
|
public function getRevisionsData($plugin_name, array $revisions) |
281
|
|
|
{ |
282
|
|
|
if ( !$this->pluginRegistered($plugin_name) ) { |
283
|
|
|
throw new \InvalidArgumentException('The "' . $plugin_name . '" revision log plugin is unknown.'); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
return $this->_plugins[$plugin_name]->getRevisionsData($revisions); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
1 |
|
* Determines if plugin is registered. |
291
|
|
|
* |
292
|
1 |
|
* @param string $plugin_name Plugin name. |
293
|
|
|
* |
294
|
1 |
|
* @return boolean |
295
|
1 |
|
*/ |
296
|
|
|
public function pluginRegistered($plugin_name) |
297
|
1 |
|
{ |
298
|
1 |
|
return array_key_exists($plugin_name, $this->_plugins); |
299
|
1 |
|
} |
300
|
1 |
|
|
301
|
|
|
/** |
302
|
1 |
|
* Returns bugs, from revisions. |
303
|
|
|
* |
304
|
|
|
* @param array $revisions Revisions. |
305
|
|
|
* |
306
|
|
|
* @return array |
307
|
|
|
*/ |
308
|
|
|
public function getBugsFromRevisions(array $revisions) |
309
|
|
|
{ |
310
|
|
|
$bugs = array(); |
311
|
|
|
$revisions_bugs = $this->getRevisionsData('bugs', $revisions); |
312
|
|
|
|
313
|
|
|
foreach ( $revisions as $revision ) { |
314
|
|
|
$revision_bugs = $revisions_bugs[$revision]; |
315
|
|
|
|
316
|
|
|
foreach ( $revision_bugs as $bug_id ) { |
317
|
|
|
$bugs[$bug_id] = true; |
318
|
|
|
} |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
return array_keys($bugs); |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
} |
325
|
|
|
|
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.