line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package SVK::Log::Filter::Stats; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
24746
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
37
|
|
4
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
30
|
|
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
5
|
use base qw( SVK::Log::Filter::Output ); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
831
|
|
7
|
|
|
|
|
|
|
use List::Util qw( max min minstr maxstr ); |
8
|
|
|
|
|
|
|
use Time::Local; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = '0.0.4'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub setup { |
13
|
|
|
|
|
|
|
my ($self, $args) = @_; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
$self->{commits} = 0; |
16
|
|
|
|
|
|
|
$self->{committers} = {}; |
17
|
|
|
|
|
|
|
$self->{files} = {}; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# hacks! |
20
|
|
|
|
|
|
|
$self->{newest_commit} = ''; |
21
|
|
|
|
|
|
|
$self->{oldest_commit} = '9999-99-99'; |
22
|
|
|
|
|
|
|
} |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sub revision { |
25
|
|
|
|
|
|
|
my ($self, $args) = @_; |
26
|
|
|
|
|
|
|
my ($props, $changed_paths) = @{$args}{'props', 'paths'}; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
my $date = $props->{'svn:date'} || q{}; |
29
|
|
|
|
|
|
|
my $author = $props->{'svn:author'} || 'no author'; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# track the commit dates (usually from newest to oldest) |
32
|
|
|
|
|
|
|
$self->{newest_commit} = maxstr( $self->{newest_commit}, $date ); |
33
|
|
|
|
|
|
|
$self->{oldest_commit} = minstr( $self->{oldest_commit}, $date ); |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
$self->{commits}++; |
36
|
|
|
|
|
|
|
$self->{committers}{$author}++; |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
for my $changed_path ( $changed_paths->paths() ) { |
39
|
|
|
|
|
|
|
my $path = $changed_path->path(); |
40
|
|
|
|
|
|
|
$self->{files}{$path}++; |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
return; |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sub footer { |
47
|
|
|
|
|
|
|
my ($self, $args) = @_; |
48
|
|
|
|
|
|
|
my $stash = $args->{stash}; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
my $quiet = $stash->{quiet}; |
51
|
|
|
|
|
|
|
my $verbose = $stash->{verbose}; |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
$self->newest_commit; |
54
|
|
|
|
|
|
|
$self->oldest_commit; |
55
|
|
|
|
|
|
|
print "Commits: ", $self->{commits}, "\n"; |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
$self->commits_per_day; |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
my $author_count = $verbose ? 999_999 : 5; |
60
|
|
|
|
|
|
|
$self->author_details($author_count) if !$quiet; |
61
|
|
|
|
|
|
|
$self->file_details(5) if $verbose; |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
if ($verbose) { |
64
|
|
|
|
|
|
|
print "Concentration:\n"; |
65
|
|
|
|
|
|
|
$self->concentration_ratio; |
66
|
|
|
|
|
|
|
$self->herfindahl; |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
return; |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
sub newest_commit { |
74
|
|
|
|
|
|
|
my ($self) = @_; |
75
|
|
|
|
|
|
|
my $newest = substr($self->{newest_commit}, 0, 10); |
76
|
|
|
|
|
|
|
print "Newest commit : $newest\n"; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub oldest_commit { |
80
|
|
|
|
|
|
|
my ($self) = @_; |
81
|
|
|
|
|
|
|
my $oldest = substr($self->{oldest_commit}, 0, 10); |
82
|
|
|
|
|
|
|
print "Oldest commit : $oldest\n"; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub days { |
86
|
|
|
|
|
|
|
my ($self) = @_; |
87
|
|
|
|
|
|
|
my $young = _date_into_time( $self->{newest_commit} ); |
88
|
|
|
|
|
|
|
my $old = _date_into_time( $self->{oldest_commit} ); |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
my $delta = $young - $old; |
91
|
|
|
|
|
|
|
my $days = int($delta/86400); |
92
|
|
|
|
|
|
|
print "Days: $days\n"; |
93
|
|
|
|
|
|
|
return $days; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub _date_into_time { |
97
|
|
|
|
|
|
|
my ($date) = @_; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
my ($y, $m, $d, $h, $min, $s, $ms) = split /[-T:.]/, $date; |
100
|
|
|
|
|
|
|
$m--; |
101
|
|
|
|
|
|
|
$y -= 1900; |
102
|
|
|
|
|
|
|
return timegm($s,$min,$h,$d,$m,$y); |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub commits_per_day { |
106
|
|
|
|
|
|
|
my ($self) = @_; |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
my $days = $self->days; |
109
|
|
|
|
|
|
|
return if $days < 1; |
110
|
|
|
|
|
|
|
return if $self->{commits} < 1; |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
my $c_per_day = $self->{commits} / $days; |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
if ( $c_per_day > 1 ) { |
115
|
|
|
|
|
|
|
printf "Commits per day : %.1f\n", $c_per_day; |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
else { |
118
|
|
|
|
|
|
|
printf "Days per commit : %.1f\n", (1 / $c_per_day); |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
sub author_details { |
123
|
|
|
|
|
|
|
my ($self, $count) = @_; |
124
|
|
|
|
|
|
|
$count ||= 5; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# sort the committer list by commits |
127
|
|
|
|
|
|
|
my @committers; |
128
|
|
|
|
|
|
|
while ( my ($author, $commits) = each %{ $self->{committers} } ) { |
129
|
|
|
|
|
|
|
push @committers, [ $author, $commits ]; |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
@committers = sort { $b->[1] <=> $a->[1] } @committers; |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
print "Committer count: ", (scalar @committers), "\n"; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# trim the list of committers |
136
|
|
|
|
|
|
|
my $count_index = min( $count-1, $#committers ); |
137
|
|
|
|
|
|
|
@committers = @committers[ 0 .. $count_index ]; |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
# display the top committers |
140
|
|
|
|
|
|
|
my $longest = max map { length $_->[0] } @committers; |
141
|
|
|
|
|
|
|
print "Most active committers:\n"; |
142
|
|
|
|
|
|
|
foreach (@committers) { |
143
|
|
|
|
|
|
|
my ( $author, $count ) = @$_; |
144
|
|
|
|
|
|
|
$author .= ' 'x( $longest - length($author) ); |
145
|
|
|
|
|
|
|
print " - $author ($count)\n"; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
sub file_details { |
150
|
|
|
|
|
|
|
my ($self, $count) = @_; |
151
|
|
|
|
|
|
|
$count ||= 5; |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# sort the file list by modifications |
154
|
|
|
|
|
|
|
my @files; |
155
|
|
|
|
|
|
|
while ( my ($path, $commits) = each %{ $self->{files} } ) { |
156
|
|
|
|
|
|
|
push @files, [ $path, $commits ]; |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
@files = sort { $b->[1] <=> $a->[1] } @files; |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
print "Count of modified paths: ", (scalar @files), "\n"; |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# trim the list of files |
163
|
|
|
|
|
|
|
my $count_index = min( $count-1, $#files ); |
164
|
|
|
|
|
|
|
@files = @files[ 0 .. $count_index ]; |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# display the file details |
167
|
|
|
|
|
|
|
my $longest = max map { length $_->[0] } @files; |
168
|
|
|
|
|
|
|
print "Most modified paths:\n"; |
169
|
|
|
|
|
|
|
foreach (@files) { |
170
|
|
|
|
|
|
|
my ( $file, $count ) = @$_; |
171
|
|
|
|
|
|
|
$file .= ' 'x( $longest - length($file) ); |
172
|
|
|
|
|
|
|
print " - $file ($count)\n"; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub concentration_ratio { |
177
|
|
|
|
|
|
|
my ($self) = @_; |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
my $commit_count = $self->{commits}; |
180
|
|
|
|
|
|
|
return if $commit_count < 1; |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
my @committers; |
183
|
|
|
|
|
|
|
for my $commits ( values %{ $self->{committers} } ) { |
184
|
|
|
|
|
|
|
push @committers, $commits; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
return if @committers < 4; |
187
|
|
|
|
|
|
|
@committers = sort { $b <=> $a } @committers; |
188
|
|
|
|
|
|
|
@committers = @committers[ 0 .. 3 ]; # get the top 4 |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
# find the total commits performed by the top 4 |
191
|
|
|
|
|
|
|
my $commits_by_top = 0; |
192
|
|
|
|
|
|
|
for ( @committers ) { |
193
|
|
|
|
|
|
|
$commits_by_top += $_; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
printf " Concentration ratio : %.2f\n", ($commits_by_top / $commit_count); |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
sub herfindahl { |
200
|
|
|
|
|
|
|
my ($self) = @_; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
my $commit_count = $self->{commits}; |
203
|
|
|
|
|
|
|
return if $commit_count < 1; |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
my @committers; |
206
|
|
|
|
|
|
|
for my $commits ( values %{ $self->{committers} } ) { |
207
|
|
|
|
|
|
|
push @committers, $commits; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
my $cumulative_herfindahl = 0; |
211
|
|
|
|
|
|
|
for ( @committers ) { |
212
|
|
|
|
|
|
|
$cumulative_herfindahl += ( $_ / $commit_count * 100 ) ** 2; |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
printf " Herfindahl index : \%d\n", $cumulative_herfindahl; |
216
|
|
|
|
|
|
|
printf " Normalized Herfindahl index : %.2f\n", |
217
|
|
|
|
|
|
|
( $cumulative_herfindahl / 10_000 ); |
218
|
|
|
|
|
|
|
printf " Equivalent committers : %.1f\n", |
219
|
|
|
|
|
|
|
( 10_000 / $cumulative_herfindahl ); |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
1; |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
__END__ |