|
1
|
|
|
<?php |
|
2
|
|
|
namespace Hal\Metric\System\Changes; |
|
3
|
|
|
|
|
4
|
|
|
use Hal\Application\Config\Config; |
|
5
|
|
|
use Hal\Application\Config\ConfigException; |
|
6
|
|
|
use Hal\Metric\FileMetric; |
|
7
|
|
|
use Hal\Metric\Metrics; |
|
8
|
|
|
use Hal\Metric\ProjectMetric; |
|
9
|
|
|
|
|
10
|
|
|
class GitChanges |
|
11
|
|
|
{ |
|
12
|
|
|
|
|
13
|
|
|
/** |
|
14
|
|
|
* @var array |
|
15
|
|
|
*/ |
|
16
|
|
|
private $files = []; |
|
17
|
|
|
|
|
18
|
|
|
/** |
|
19
|
|
|
* @var Config |
|
20
|
|
|
*/ |
|
21
|
|
|
private $config; |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* GitChanges constructor. |
|
25
|
|
|
* @param array $files |
|
26
|
|
|
*/ |
|
27
|
|
|
public function __construct(Config $config, array $files) |
|
28
|
|
|
{ |
|
29
|
|
|
$this->files = $files; |
|
30
|
|
|
$this->config = $config; |
|
31
|
|
|
} |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* @param Metrics $metrics |
|
35
|
|
|
* @throws ConfigException |
|
36
|
|
|
*/ |
|
37
|
|
|
public function calculate(Metrics $metrics) |
|
38
|
|
|
{ |
|
39
|
|
|
|
|
40
|
|
|
if (!$this->config->has('git')) { |
|
41
|
|
|
return; |
|
42
|
|
|
} |
|
43
|
|
|
|
|
44
|
|
|
$bin = $this->config->get('git'); |
|
|
|
|
|
|
45
|
|
|
if (is_bool($bin)) { |
|
46
|
|
|
$bin = 'git'; |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
|
|
if (sizeof($this->files) == 0) { |
|
50
|
|
|
return; |
|
51
|
|
|
} |
|
52
|
|
|
|
|
53
|
|
|
$r = shell_exec(sprintf('%s --version', $bin)); |
|
54
|
|
|
if(!preg_match('!git version!', $r)) { |
|
55
|
|
|
throw new ConfigException(sprintf('Git binary (%s) incorrect', $bin)); |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
// get all history (for only on directory for the moment |
|
59
|
|
|
// @todo: git history for multiple repositories |
|
60
|
|
|
// 500 last commits max |
|
61
|
|
|
$file = current($this->files); |
|
62
|
|
|
$command = sprintf("cd %s && %s log --format='* %%at\t%%cn' --numstat -n 500", realpath(dirname($file)), $bin); |
|
63
|
|
|
$r = shell_exec($command); |
|
64
|
|
|
$r = array_filter(explode(PHP_EOL, $r)); |
|
65
|
|
|
|
|
66
|
|
|
|
|
67
|
|
|
// build a range of commits info, stepped by week number |
|
68
|
|
|
$history = []; |
|
69
|
|
|
$dateFormat = 'Y-W'; |
|
70
|
|
|
|
|
71
|
|
|
// calculate statistics |
|
72
|
|
|
$firstCommitDate = null; |
|
73
|
|
|
$commitsByFile = []; |
|
74
|
|
|
$localFiles = []; |
|
75
|
|
|
$localFiles['additions'] = 0; |
|
76
|
|
|
$localFiles['removes'] = 0; |
|
77
|
|
|
$localFiles['nbFiles'] = 0; |
|
78
|
|
|
$authors = []; |
|
79
|
|
|
|
|
80
|
|
|
foreach ($r as $line) { |
|
81
|
|
|
if (preg_match('!^\* (\d+)\s+(.*)!', $line, $matches)) { |
|
82
|
|
|
// head line |
|
83
|
|
|
|
|
84
|
|
|
if (isset($date)) { |
|
85
|
|
|
// new head line ($author is set). Consolidate now for last commit |
|
86
|
|
View Code Duplication |
if (!isset($history[$date])) { |
|
|
|
|
|
|
87
|
|
|
$history[$date] = ['nbFiles' => 0, 'additions' => 0, 'removes' => 0]; |
|
88
|
|
|
} |
|
89
|
|
|
$history[$date]['nbFiles'] += $localFiles['nbFiles']; |
|
90
|
|
|
$history[$date]['additions'] += $localFiles['additions']; |
|
91
|
|
|
$history[$date]['removes'] += $localFiles['removes']; |
|
92
|
|
|
|
|
93
|
|
|
// reset |
|
94
|
|
|
$localFiles['additions'] = 0; |
|
95
|
|
|
$localFiles['removes'] = 0; |
|
96
|
|
|
$localFiles['nbFiles'] = 0; |
|
97
|
|
|
} |
|
98
|
|
|
|
|
99
|
|
|
// new infos |
|
100
|
|
|
list(, $timestamp, $author) = $matches; |
|
101
|
|
|
$date = (new \DateTime())->setTimestamp($timestamp)->format($dateFormat); |
|
102
|
|
|
|
|
103
|
|
|
if (is_null($firstCommitDate)) { |
|
104
|
|
|
$firstCommitDate = $timestamp; |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
// author |
|
108
|
|
View Code Duplication |
if (!isset($authors[$author])) { |
|
|
|
|
|
|
109
|
|
|
$authors[$author] = ['nbFiles' => 0, 'commits' => 0, 'additions' => 0, 'removes' => 0]; |
|
110
|
|
|
} |
|
111
|
|
|
$authors[$author]['commits']++; |
|
112
|
|
|
|
|
113
|
|
|
} else { |
|
114
|
|
|
if (preg_match('!(\d+)\s+(\d+)\s+(.*)!', $line, $matches)) { |
|
115
|
|
|
// additions and changes for each file |
|
116
|
|
|
list(, $additions, $removes, $filename) = $matches; |
|
117
|
|
|
|
|
118
|
|
|
if (!$this->doesThisFileShouldBeCounted($filename)) { |
|
119
|
|
|
// we don't care about all files |
|
120
|
|
|
continue; |
|
121
|
|
|
} |
|
122
|
|
|
|
|
123
|
|
|
// global history |
|
124
|
|
|
$localFiles['additions'] += $additions; |
|
125
|
|
|
$localFiles['removes'] += $removes; |
|
126
|
|
|
$localFiles['nbFiles']++; |
|
127
|
|
|
|
|
128
|
|
|
// commits by file |
|
129
|
|
|
if (!isset($commitsByFile[$filename])) { |
|
130
|
|
|
$commitsByFile[$filename] = 0; |
|
131
|
|
|
} |
|
132
|
|
|
$commitsByFile[$filename]++; |
|
133
|
|
|
|
|
134
|
|
|
// author |
|
135
|
|
|
if (isset($author)) { |
|
136
|
|
|
$authors[$author]['nbFiles']++; |
|
137
|
|
|
$authors[$author]['additions'] += $additions; |
|
138
|
|
|
$authors[$author]['removes'] += $removes; |
|
139
|
|
|
} |
|
140
|
|
|
} |
|
141
|
|
|
} |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
// build a range of dates since first commit |
|
145
|
|
|
// (pad weeks without any commit) |
|
146
|
|
|
$current = $firstCommitDate; |
|
147
|
|
|
$last = time(); |
|
148
|
|
|
while ($current <= $last) { |
|
149
|
|
|
$key = date($dateFormat, $current); |
|
150
|
|
View Code Duplication |
if (!isset($history[$key])) { |
|
|
|
|
|
|
151
|
|
|
$history[$key] = [ |
|
152
|
|
|
'nbFiles' => 0, |
|
153
|
|
|
'additions' => 0, |
|
154
|
|
|
'removes' => 0, |
|
155
|
|
|
]; |
|
156
|
|
|
} |
|
157
|
|
|
$current = strtotime('+7 day', $current); |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
|
|
161
|
|
|
// store results |
|
162
|
|
|
$result = new ProjectMetric('git'); |
|
163
|
|
|
$result->set('history', $history); |
|
164
|
|
|
$result->set('authors', $authors); |
|
165
|
|
|
$metrics->attach($result); |
|
166
|
|
|
|
|
167
|
|
|
foreach ($commitsByFile as $filename => $nbCommits) { |
|
168
|
|
|
$info = new FileMetric($filename); |
|
169
|
|
|
$info->set('gitChanges', $nbCommits); |
|
170
|
|
|
$metrics->attach($info); |
|
171
|
|
|
} |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
/** |
|
175
|
|
|
* @param $file |
|
176
|
|
|
* @return int |
|
177
|
|
|
*/ |
|
178
|
|
|
private function doesThisFileShouldBeCounted($file) |
|
179
|
|
|
{ |
|
180
|
|
|
return preg_match('!\.(php|inc)$!i', $file); |
|
181
|
|
|
} |
|
182
|
|
|
} |
|
183
|
|
|
|
This check looks for function or method calls that always return null and whose return value is assigned to a variable.
The method
getObject()can return nothing but null, so it makes no sense to assign that value to a variable.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.