File Coverage

blib/lib/SVK/Log/Filter/Stats.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


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__