Total Complexity | 73 |
Total Lines | 632 |
Duplicated Lines | 0 % |
Coverage | 4.15% |
Changes | 10 | ||
Bugs | 0 | Features | 0 |
Complex classes like RevisionPrinter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use RevisionPrinter, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
21 | class RevisionPrinter |
||
22 | { |
||
23 | |||
24 | const COLUMN_FULL_MESSAGE = 1; |
||
25 | |||
26 | const COLUMN_DETAILS = 2; |
||
27 | |||
28 | const COLUMN_SUMMARY = 3; |
||
29 | |||
30 | const COLUMN_REFS = 4; |
||
31 | |||
32 | const COLUMN_MERGE_ORACLE = 5; |
||
33 | |||
34 | const COLUMN_MERGE_STATUS = 6; |
||
35 | |||
36 | const COLUMN_REVISION_URL = 7; |
||
37 | |||
38 | /** |
||
39 | * Date helper. |
||
40 | * |
||
41 | * @var DateHelper |
||
42 | */ |
||
43 | private $_dateHelper; |
||
44 | |||
45 | /** |
||
46 | * Output helper. |
||
47 | * |
||
48 | * @var OutputHelper |
||
49 | */ |
||
50 | private $_outputHelper; |
||
51 | |||
52 | /** |
||
53 | * Columns. |
||
54 | * |
||
55 | * @var array |
||
56 | */ |
||
57 | private $_columns = array(); |
||
58 | |||
59 | /** |
||
60 | * Aggregate by bug. |
||
61 | * |
||
62 | * @var boolean |
||
63 | */ |
||
64 | private $_aggregateByBug = false; |
||
65 | |||
66 | /** |
||
67 | * Merge conflict regexps. |
||
68 | * |
||
69 | * @var array |
||
70 | */ |
||
71 | private $_mergeConflictRegExps = array(); |
||
72 | |||
73 | /** |
||
74 | * Log message limit. |
||
75 | * |
||
76 | * @var integer |
||
77 | */ |
||
78 | private $_logMessageLimit = 68; |
||
79 | |||
80 | /** |
||
81 | * Current revision (e.g. in a working copy). |
||
82 | * |
||
83 | * @var integer|null |
||
84 | */ |
||
85 | private $_currentRevision; |
||
86 | |||
87 | /** |
||
88 | * Creates instance of revision printer. |
||
89 | * |
||
90 | * @param DateHelper $date_helper Date helper. |
||
91 | * @param OutputHelper $output_helper Output helper. |
||
92 | */ |
||
93 | 1 | public function __construct(DateHelper $date_helper, OutputHelper $output_helper) |
|
94 | { |
||
95 | 1 | $this->_dateHelper = $date_helper; |
|
96 | 1 | $this->_outputHelper = $output_helper; |
|
97 | |||
98 | 1 | $this->_resetState(); |
|
99 | } |
||
100 | |||
101 | /** |
||
102 | * Resets state. |
||
103 | * |
||
104 | * @return void |
||
105 | */ |
||
106 | 1 | private function _resetState() |
|
107 | { |
||
108 | 1 | $this->_columns = array(); |
|
109 | 1 | $this->_mergeConflictRegExps = array(); |
|
110 | 1 | $this->_logMessageLimit = 68; |
|
111 | 1 | $this->_aggregateByBug = false; |
|
112 | } |
||
113 | |||
114 | /** |
||
115 | * Adds column to the output. |
||
116 | * |
||
117 | * @param integer $column Column. |
||
118 | * |
||
119 | * @return self |
||
120 | */ |
||
121 | public function withColumn($column) |
||
122 | { |
||
123 | $this->_columns[] = $column; |
||
124 | |||
125 | return $this; |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * Sets aggregate by bug. |
||
130 | * |
||
131 | * @param boolean $aggregate_by_bug Aggregate by bug. |
||
132 | * |
||
133 | * @return void |
||
134 | */ |
||
135 | public function setAggregateByBug($aggregate_by_bug) |
||
136 | { |
||
137 | $this->_aggregateByBug = $aggregate_by_bug; |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * Sets merge conflict regexps. |
||
142 | * |
||
143 | * @param array $merge_conflict_regexps Merge conflict regexps. |
||
144 | * |
||
145 | * @return void |
||
146 | */ |
||
147 | public function setMergeConflictRegExps(array $merge_conflict_regexps) |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Sets log message limit. |
||
154 | * |
||
155 | * @param integer $log_message_limit Log message limit. |
||
156 | * |
||
157 | * @return void |
||
158 | */ |
||
159 | public function setLogMessageLimit($log_message_limit) |
||
160 | { |
||
161 | $this->_logMessageLimit = $log_message_limit; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Sets current revision. |
||
166 | * |
||
167 | * @param integer $revision Revision. |
||
168 | * |
||
169 | * @return void |
||
170 | */ |
||
171 | public function setCurrentRevision($revision) |
||
172 | { |
||
173 | $this->_currentRevision = $revision; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * Prints revisions. |
||
178 | * |
||
179 | * @param RevisionLog $revision_log Revision log. |
||
180 | * @param array $revisions Revisions. |
||
181 | * @param OutputInterface $output Output. |
||
182 | * |
||
183 | * @return void |
||
184 | */ |
||
185 | public function printRevisions(RevisionLog $revision_log, array $revisions, OutputInterface $output) |
||
186 | { |
||
187 | $table = new Table($output); |
||
188 | $headers = array('Revision', 'Author', 'Date', 'Bug-ID', 'Log Message'); |
||
189 | |||
190 | $with_full_message = in_array(self::COLUMN_FULL_MESSAGE, $this->_columns); |
||
191 | $with_details = in_array(self::COLUMN_DETAILS, $this->_columns); |
||
192 | |||
193 | // Add "Summary" header. |
||
194 | $with_summary = in_array(self::COLUMN_SUMMARY, $this->_columns); |
||
195 | |||
196 | if ( $with_summary ) { |
||
197 | $headers[] = 'Summary'; |
||
198 | } |
||
199 | |||
200 | // Add "Refs" header. |
||
201 | $with_refs = in_array(self::COLUMN_REFS, $this->_columns); |
||
202 | |||
203 | if ( $with_refs ) { |
||
204 | $headers[] = 'Refs'; |
||
205 | } |
||
206 | |||
207 | $with_merge_oracle = in_array(self::COLUMN_MERGE_ORACLE, $this->_columns); |
||
208 | |||
209 | // Add "M.O." header. |
||
210 | if ( $with_merge_oracle ) { |
||
211 | $headers[] = 'M.O.'; |
||
212 | } |
||
213 | |||
214 | // Add "Merged Via" header. |
||
215 | $with_merge_status = in_array(self::COLUMN_MERGE_STATUS, $this->_columns); |
||
216 | |||
217 | if ( $with_merge_status ) { |
||
218 | $headers[] = 'Merged Via'; |
||
219 | } |
||
220 | |||
221 | // Add "Revision URL" header. |
||
222 | $with_revision_url = in_array(self::COLUMN_REVISION_URL, $this->_columns); |
||
223 | |||
224 | if ( $with_revision_url ) { |
||
225 | $revision_url_mask = $revision_log->getRevisionURLBuilder()->getMask(); |
||
226 | |||
227 | if ( strpos($revision_url_mask, '://') !== false ) { |
||
228 | // Add column only, when it will contain a URL. |
||
229 | $headers[] = 'Revision URL'; |
||
230 | } |
||
231 | else { |
||
232 | // Don't add the column, when it won't contain a URL. |
||
233 | $with_revision_url = false; |
||
234 | } |
||
235 | } |
||
236 | |||
237 | $table->setHeaders($headers); |
||
238 | |||
239 | $prev_bugs = null; |
||
240 | $last_color = 'yellow'; |
||
241 | |||
242 | if ( $this->_aggregateByBug ) { |
||
243 | $aggregated_revisions = $this->aggregateRevisionsByBug($revisions, $revision_log); |
||
244 | $revisions = array_keys($aggregated_revisions); |
||
245 | } |
||
246 | |||
247 | $last_revision = end($revisions); |
||
248 | |||
249 | $project_path = $revision_log->getProjectPath(); |
||
250 | |||
251 | $bugs_per_row = $with_details ? 1 : 3; |
||
252 | |||
253 | $revisions_data = $revision_log->getRevisionsData('summary', $revisions); |
||
254 | $revisions_paths = $revision_log->getRevisionsData('paths', $revisions); |
||
255 | $revisions_bugs = $revision_log->getRevisionsData('bugs', $revisions); |
||
256 | $revisions_refs = $revision_log->getRevisionsData('refs', $revisions); |
||
257 | |||
258 | if ( $with_merge_status ) { |
||
259 | $revisions_merged_via = $revision_log->getRevisionsData('merges', $revisions); |
||
260 | $revisions_merged_via_refs = $revision_log->getRevisionsData( |
||
261 | 'refs', |
||
262 | call_user_func_array('array_merge', $revisions_merged_via) |
||
263 | ); |
||
264 | } |
||
265 | |||
266 | $first_revision = reset($revisions); |
||
267 | |||
268 | foreach ( $revisions as $revision ) { |
||
269 | $revision_data = $revisions_data[$revision]; |
||
270 | |||
271 | $new_bugs = $revisions_bugs[$revision]; |
||
272 | |||
273 | if ( isset($prev_bugs) && $new_bugs !== $prev_bugs ) { |
||
274 | $last_color = $last_color === 'yellow' ? 'magenta' : 'yellow'; |
||
275 | } |
||
276 | |||
277 | $row = array( |
||
278 | $this->_aggregateByBug ? $aggregated_revisions[$revision] . ' cmts' : $revision, |
||
|
|||
279 | $revision_data['author'], |
||
280 | $this->_dateHelper->getAgoTime($revision_data['date']), |
||
281 | $this->_outputHelper->formatArray($new_bugs, $bugs_per_row, $last_color), |
||
282 | $this->_generateLogMessageColumn($with_full_message || $with_details, $revision_data), |
||
283 | ); |
||
284 | |||
285 | $revision_paths = $revisions_paths[$revision]; |
||
286 | |||
287 | // Add "Summary" column. |
||
288 | if ( $with_summary ) { |
||
289 | $row[] = $this->_generateSummaryColumn($revision_paths); |
||
290 | } |
||
291 | |||
292 | // Add "Refs" column. |
||
293 | if ( $with_refs ) { |
||
294 | $row[] = $this->_outputHelper->formatArray( |
||
295 | $revisions_refs[$revision], |
||
296 | 1 |
||
297 | ); |
||
298 | } |
||
299 | |||
300 | // Add "M.O." column. |
||
301 | if ( $with_merge_oracle ) { |
||
302 | $merge_conflict_prediction = $this->_getMergeConflictPrediction($revision_paths); |
||
303 | $row[] = $merge_conflict_prediction ? '<error>' . count($merge_conflict_prediction) . '</error>' : ''; |
||
304 | } |
||
305 | else { |
||
306 | $merge_conflict_prediction = array(); |
||
307 | } |
||
308 | |||
309 | // Add "Merged Via" column. |
||
310 | if ( $with_merge_status ) { |
||
311 | $row[] = $this->_generateMergedViaColumn($revisions_merged_via[$revision], $revisions_merged_via_refs); |
||
312 | } |
||
313 | |||
314 | // Add "Revision URL" header. |
||
315 | if ( $with_revision_url ) { |
||
316 | $row[] = \str_replace('{revision}', $revision, $revision_url_mask); |
||
1 ignored issue
–
show
|
|||
317 | } |
||
318 | |||
319 | if ( $revision === $this->_currentRevision ) { |
||
320 | foreach ( $row as $index => $cell ) { |
||
321 | $row[$index] = $this->applyStyle($cell, 'fg=white;options=bold'); |
||
322 | } |
||
323 | } |
||
324 | |||
325 | if ( $with_full_message && $revision !== $first_revision ) { |
||
326 | $table->addRow(new TableSeparator()); |
||
327 | } |
||
328 | |||
329 | $table->addRow($row); |
||
330 | |||
331 | if ( $with_details ) { |
||
332 | $details = $this->_generateDetailsRowContent( |
||
333 | $revision, |
||
334 | $revisions_refs, |
||
335 | $revision_paths, |
||
336 | $merge_conflict_prediction, |
||
337 | $project_path |
||
338 | ); |
||
339 | |||
340 | $table->addRow(new TableSeparator()); |
||
341 | $table->addRow(array( |
||
342 | new TableCell($details, array('colspan' => count($headers))), |
||
343 | )); |
||
344 | |||
345 | if ( $revision != $last_revision ) { |
||
346 | $table->addRow(new TableSeparator()); |
||
347 | } |
||
348 | } |
||
349 | |||
350 | $prev_bugs = $new_bugs; |
||
351 | } |
||
352 | |||
353 | $table->render(); |
||
354 | |||
355 | $this->_resetState(); |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Aggregates revisions by bugs. |
||
360 | * |
||
361 | * @param array $revisions Revisions. |
||
362 | * @param RevisionLog $revision_log Revision Log. |
||
363 | * |
||
364 | * @return array |
||
365 | */ |
||
366 | protected function aggregateRevisionsByBug(array $revisions, RevisionLog $revision_log) |
||
367 | { |
||
368 | $bugs_revisions = array( |
||
369 | 'unknown' => array(), |
||
370 | ); |
||
371 | |||
372 | $revisions_bugs = $revision_log->getRevisionsData('bugs', $revisions); |
||
373 | |||
374 | foreach ( \array_reverse($revisions) as $revision ) { |
||
375 | $revision_bugs = $revisions_bugs[$revision]; |
||
376 | |||
377 | if ( !$revision_bugs ) { |
||
378 | $bugs_revisions['unknown'][] = $revision; |
||
379 | continue; |
||
380 | } |
||
381 | |||
382 | foreach ( $revision_bugs as $revision_bug ) { |
||
383 | if ( !isset($bugs_revisions[$revision_bug]) ) { |
||
384 | $bugs_revisions[$revision_bug] = array(); |
||
385 | } |
||
386 | |||
387 | $bugs_revisions[$revision_bug][] = $revision; |
||
388 | } |
||
389 | } |
||
390 | |||
391 | if ( !$bugs_revisions['unknown'] ) { |
||
392 | unset($bugs_revisions['unknown']); |
||
393 | } |
||
394 | |||
395 | $bugs_revisions = \array_reverse($bugs_revisions, true); |
||
396 | |||
397 | $ret = array(); |
||
398 | |||
399 | foreach ( $bugs_revisions as $bug => $bug_revisions ) { |
||
400 | $ret[reset($bug_revisions)] = count($bug_revisions); |
||
401 | } |
||
402 | |||
403 | return $ret; |
||
404 | } |
||
405 | |||
406 | /** |
||
407 | * Applies a style to the text. |
||
408 | * |
||
409 | * @param string $text Text. |
||
410 | * @param string $style Style. |
||
411 | * |
||
412 | * @return string |
||
413 | */ |
||
414 | protected function applyStyle($text, $style) |
||
415 | { |
||
416 | if ( strpos($text, PHP_EOL) === false ) { |
||
417 | return '<' . $style . '>' . $text . '</>'; |
||
418 | } |
||
419 | |||
420 | $lines = explode(PHP_EOL, $text); |
||
421 | |||
422 | return '<' . $style . '>' . implode('</>' . PHP_EOL . '<' . $style . '>', $lines) . '</>'; |
||
423 | } |
||
424 | |||
425 | /** |
||
426 | * Returns log message. |
||
427 | * |
||
428 | * @param boolean $with_full_message Show commit message without truncation. |
||
429 | * @param array $revision_data Revision data. |
||
430 | * |
||
431 | * @return string |
||
432 | */ |
||
433 | private function _generateLogMessageColumn($with_full_message, array $revision_data) |
||
434 | { |
||
435 | $commit_message = trim($revision_data['msg']); |
||
436 | |||
437 | if ( $with_full_message ) { |
||
438 | // When details requested don't transform commit message except for word wrapping. |
||
439 | // FIXME: Not UTF-8 safe solution. |
||
440 | $log_message = wordwrap($commit_message, $this->_logMessageLimit); |
||
441 | |||
442 | return $log_message; |
||
443 | } |
||
444 | else { |
||
445 | // When details not requested only operate on first line of commit message. |
||
446 | list($log_message,) = explode(PHP_EOL, $commit_message); |
||
447 | $log_message = preg_replace('/(^|\s+)\[fixes:.*?\]/s', "$1\xE2\x9C\x94", $log_message); |
||
448 | |||
449 | if ( strpos($commit_message, PHP_EOL) !== false |
||
450 | || mb_strlen($log_message) > $this->_logMessageLimit |
||
451 | ) { |
||
452 | $log_message = mb_substr($log_message, 0, $this->_logMessageLimit - 3) . '...'; |
||
453 | |||
454 | return $log_message; |
||
455 | } |
||
456 | |||
457 | return $log_message; |
||
458 | } |
||
459 | } |
||
460 | |||
461 | /** |
||
462 | * Generates change summary for a revision. |
||
463 | * |
||
464 | * @param array $revision_paths Revision paths. |
||
465 | * |
||
466 | * @return string |
||
467 | */ |
||
468 | private function _generateSummaryColumn(array $revision_paths) |
||
469 | { |
||
470 | $summary = array('added' => 0, 'changed' => 0, 'removed' => 0); |
||
471 | |||
472 | foreach ( $revision_paths as $path_data ) { |
||
473 | $path_action = $path_data['action']; |
||
474 | |||
475 | if ( $path_action === 'A' ) { |
||
476 | $summary['added']++; |
||
477 | } |
||
478 | elseif ( $path_action === 'D' ) { |
||
479 | $summary['removed']++; |
||
480 | } |
||
481 | else { |
||
482 | $summary['changed']++; |
||
483 | } |
||
484 | } |
||
485 | |||
486 | if ( $summary['added'] ) { |
||
487 | $summary['added'] = '<fg=green>+' . $summary['added'] . '</>'; |
||
488 | } |
||
489 | |||
490 | if ( $summary['removed'] ) { |
||
491 | $summary['removed'] = '<fg=red>-' . $summary['removed'] . '</>'; |
||
492 | } |
||
493 | |||
494 | return implode(' ', array_filter($summary)); |
||
495 | } |
||
496 | |||
497 | /** |
||
498 | * Returns merge conflict path predictions. |
||
499 | * |
||
500 | * @param array $revision_paths Revision paths. |
||
501 | * |
||
502 | * @return array |
||
503 | */ |
||
504 | private function _getMergeConflictPrediction(array $revision_paths) |
||
521 | } |
||
522 | |||
523 | /** |
||
524 | * Generates content for "Merged Via" cell content. |
||
525 | * |
||
526 | * @param array $merged_via Merged Via. |
||
527 | * @param array $revisions_merged_via_refs Merged Via Refs. |
||
528 | * |
||
529 | * @return string |
||
530 | */ |
||
531 | private function _generateMergedViaColumn(array $merged_via, array $revisions_merged_via_refs) |
||
551 | } |
||
552 | |||
553 | /** |
||
554 | * Generates details row content. |
||
555 | * |
||
556 | * @param integer $revision Revision. |
||
557 | * @param array $revisions_refs Refs. |
||
558 | * @param array $revision_paths Revision paths. |
||
559 | * @param array $merge_conflict_prediction Merge conflict prediction. |
||
560 | * @param string $project_path Project path. |
||
561 | * |
||
562 | * @return string |
||
563 | */ |
||
564 | private function _generateDetailsRowContent( |
||
610 | } |
||
611 | |||
612 | /** |
||
613 | * Returns path cut off regexp. |
||
614 | * |
||
615 | * @param string $project_path Project path. |
||
616 | * @param array $refs Refs. |
||
617 | * |
||
618 | * @return string |
||
619 | */ |
||
620 | protected function getPathCutOffRegExp($project_path, array $refs) |
||
621 | { |
||
622 | $ret = array(); |
||
623 | |||
624 | // Remove ref from path only for single-ref revision. |
||
625 | /*if ( count($refs) === 1 ) { |
||
626 | $ret[] = $project_path . reset($refs) . '/'; |
||
627 | }*/ |
||
628 | |||
629 | // Always remove project path. |
||
630 | $ret[] = $project_path; |
||
631 | |||
632 | return '#^(' . implode('|', array_map('preg_quote', $ret)) . ')#'; |
||
633 | } |
||
634 | |||
635 | /** |
||
636 | * Returns relative path to "svn log" returned path. |
||
637 | * |
||
638 | * @param array $path_data Path data. |
||
639 | * @param string $path_key Path key. |
||
640 | * @param string $path_cut_off_regexp Path cut off regexp. |
||
641 | * |
||
642 | * @return string |
||
643 | */ |
||
644 | private function _getRelativeLogPath(array $path_data, $path_key, $path_cut_off_regexp) |
||
653 | } |
||
654 | |||
655 | } |
||
656 |