File Coverage

blib/lib/App/Git/Workflow/Command/Committers.pm
Criterion Covered Total %
statement 80 119 67.2
branch 29 58 50.0
condition 5 18 27.7
subroutine 10 14 71.4
pod 6 6 100.0
total 130 215 60.4


line stmt bran cond sub pod time code
1             package App::Git::Workflow::Command::Committers;
2              
3             # Created on: 2014-06-11 10:00:36
4             # Create by: Ivan Wills
5             # $Id$
6             # $Revision$, $HeadURL$, $Date$
7             # $Revision$, $Source$, $Date$
8              
9 2     2   117024 use strict;
  2         18  
  2         69  
10 2     2   12 use warnings;
  2         5  
  2         86  
11 2     2   492 use version;
  2         2168  
  2         13  
12 2     2   693 use English qw/ -no_match_vars /;
  2         4314  
  2         35  
13 2     2   1430 use Time::Piece;
  2         14212  
  2         13  
14 2     2   781 use App::Git::Workflow;
  2         7  
  2         131  
15 2     2   637 use App::Git::Workflow::Command qw/get_options/;
  2         6  
  2         180  
16 2     2   844 use utf8;
  2         29  
  2         14  
17              
18             our $VERSION = version->new(1.1.16);
19             our $workflow = App::Git::Workflow->new;
20             our ($name) = $PROGRAM_NAME =~ m{^.*/(.*?)$}mxs;
21             our %option;
22              
23             sub run {
24 9     9 1 35 my ($self) = @_;
25 9         42 %option = (
26             period => 'day',
27             );
28 9         85 get_options(
29             \%option,
30             'remote|r',
31             'all|a',
32             'fmt|format|f=s',
33             'changes|c',
34             'commits|C',
35             'min|min-commits|M=i',
36             'since|s=s',
37             'until|u=s',
38             'period|p=s',
39             'periods|P=i',
40             'merges|m!',
41             );
42              
43 9         18 my @stats;
44 9         23 my $total_commits = 0;
45 9         23 my $since = $option{since};
46              
47 9 100       32 if (!$since) {
48 8         56 my $now = localtime;
49             my $period
50             = $option{period} eq 'day' ? 1
51             : $option{period} eq 'week' ? 7
52             : $option{period} eq 'month' ? 30
53 8 100       927 : $option{period} eq 'year' ? 365
    100          
    100          
    100          
54             : die "Unknown period '$option{period}' please choose one of day, week, month or year\n";
55 7 0       41 $since
    50          
56             = $now->wday == 1 ? localtime(time - 3 * $period * 24 * 60 * 60)->ymd
57             : $now->wday == 7 ? localtime(time - 2 * $period * 24 * 60 * 60)->ymd
58             : localtime(time - 1 * $period * 24 * 60 * 60)->ymd;
59             }
60              
61 8         763 my @options;
62 8 100       32 push @options, '-r' if $option{remote};
63 8 100       32 push @options, '-a' if $option{all};
64             my @log = (
65             '--format=format:%h %an',
66 8 100       43 ($option{merges} ? () : '--no-merges'),
67             );
68              
69 8   50     47 my $periods = $option{periods} || 1;
70 8         26 while ($periods--) {
71 8         18 my $commits = 0;
72 8         20 my %users;
73             my @dates;
74 8 50       22 if ($option{periods}) {
75 0         0 @dates = $self->dates($option{period}, $option{periods}--);
76             }
77             else {
78             @dates = (
79             "--since=$since",
80 8 50       40 ($option{until} ? "--until=$option{until}" : ()),
81             );
82             }
83              
84 8         60 for my $branch ($workflow->git->branch(@options)) {
85 9 100       40 next if $branch =~ / -> /;
86 8         37 $branch =~ s/^[*]?\s*//;
87 8         31 for my $log ( $workflow->git->log( @log, @dates, $branch, '--' ) ) {
88 24         90 my ($hash, $name) = split /\s/, $log, 2;
89 24         75 $users{$name}{$hash} = 1;
90 24         53 $commits++;
91             }
92             }
93              
94 8         35 for my $user (keys %users) {
95 16         34 my $commits = $users{$user};
96             $users{$user} = {
97 16         96 commit_count => scalar keys %{ $users{$user} },
98 0         0 $option{commits} ? (commits => [keys %{ $users{$user} }]) : (),
99 16 50       25 $option{changes} ? (changes => $self->changes($commits)) : (),
    50          
100             };
101             }
102             my $dates = join ' - ',
103 8         31 map {/=(.*)$/; $1}
  8         39  
  8         49  
104             @dates;
105 8 50       63 push @stats, {
    50          
106             period => $dates,
107             ( %users ? (commits => $commits) : () ),
108             ( %users ? (users => \%users ) : () ),
109             };
110 8         38 $total_commits += $commits;
111             }
112              
113 8   50     46 my $fmt = 'fmt_' . ($option{fmt} || 'table');
114 8 50       100 if ($self->can($fmt)) {
115 8         40 $self->$fmt(\@stats, $total_commits);
116             }
117              
118 8         61 return;
119             }
120              
121             sub dates {
122 0     0 1 0 my ($self, $period, $count) = @_;
123              
124 0         0 my $now = localtime;
125 0 0       0 $period
    0          
    0          
    0          
126             = $period eq 'day' ? 1
127             : $period eq 'week' ? 7 - $now->wdaygg
128             : $period eq 'month' ? 30
129             : $period eq 'year' ? 365
130             : die "Unknown period '$option{period}' please choose one of day, week, month or year\n";
131              
132 0         0 my $until = localtime(time - ($count - 1) * $period * 24 * 60 * 60);
133 0 0       0 my $since
    0          
134             = $until->wday == 1 ? localtime(time - 3 * $count * $period * 24 * 60 * 60)
135             : $until->wday == 7 ? localtime(time - 2 * $count * $period * 24 * 60 * 60)
136             : localtime(time - 1 * $count * $period * 24 * 60 * 60);
137              
138             return (
139 0         0 "--since=" . $since->ymd,
140             "--until=" . $until->ymd,
141             );
142             }
143              
144             sub changes {
145 0     0 1 0 my ($self, $commits) = @_;
146 0         0 my %changes = (
147             lines_added => 0,
148             lines_removed => 0,
149             files => {},
150             files_added => 0,
151             files_removed => 0,
152             );
153              
154 0         0 for my $commit (keys %$commits) {
155             # get the stats from each commit
156 0         0 my @show = $workflow->git->show($commit);
157 0         0 $changes{lines_added} += grep {/^[+](?:[^+]|[+][^+]|[+][+]\s|$)/} @show;
  0         0  
158 0         0 $changes{lines_removed} += grep {/^[-](?:[^-]|[-][^-]|[-][-]\s|$)/} @show;
  0         0  
159             $changes{files} = {
160 0 0       0 %{ $changes{files} || {} },
161 0   0     0 map {/^[+]{3}\s+b\/(.*)$/; ($1 || "" => 1) }
  0         0  
162 0         0 grep {/^[+]{3}\s/}
  0         0  
163             @show
164             };
165 0         0 $changes{total}++;
166             }
167 0 0       0 $changes{files} = keys %{ $changes{files} || {} };
  0         0  
168              
169 0         0 return \%changes;
170             }
171              
172             sub fmt_table {
173 8     8 1 25 my ($self, $stats) = @_;
174 8         17 my $fmt = "%-25s % 7d";
175 8         16 my $max = 1;
176 8   50     26 my $users = $stats->[0]{users} || {};
177 8         48 my %totals = (
178             commit_count => 0,
179             lines_added => 0,
180             lines_removed => 0,
181             files => 0,
182             );
183              
184 8 50       28 if ($option{changes}) {
185 0         0 $fmt .= " % 9d % 9d % 5d";
186 0         0 my $fmt2 = $fmt;
187 0         0 $fmt2 =~ s/d/s/g;
188 0         0 printf "$fmt2\n", qw/Name Commits Added Removed Files/;
189 0         0 $max = 4;
190             }
191              
192             my @users =
193 8         59 reverse sort {$users->{$a}{commit_count} <=> $users->{$b}{commit_count}}
194 8   50     29 grep { $users->{$_}{commit_count} >= ($option{min} || 0) }
  16         109  
195             keys %$users;
196              
197 8         27 for my $user (@users) {
198             %totals = (
199             commit_count => ($totals{commit_count} + $users->{$user}{commit_count} || 0),
200             $users->{$user}{changes} ? (
201             lines_added => ($totals{lines_added} + ($users->{$user}{changes}{lines_added} || 0)),
202             lines_removed => ($totals{lines_removed} + ($users->{$user}{changes}{lines_removed} || 0)),
203 16 50 50     117 files => ($totals{files} + ($users->{$user}{changes}{files} || 0)),
      0        
      0        
      0        
204             ) : ()
205             );
206             my @out = (
207             $user,
208             $users->{$user}{commit_count},
209             $users->{$user}{changes}{lines_added},
210             $users->{$user}{changes}{lines_removed},
211             $users->{$user}{changes}{files},
212 16         81 );
213 16         866 printf "$fmt\n", @out[0..$max];
214             }
215 8 50       51 if ($option{changes}) {
216             my @out = (
217             'Total',
218             $totals{commit_count},
219             $totals{lines_added},
220             $totals{lines_removed},
221             $totals{files},
222 0         0 );
223 0         0 printf "\n$fmt\n", @out[0..$max];
224             }
225             else {
226 8         108 print "Total commits = $totals{commit_count}\n";
227             }
228              
229 8         46 return;
230             }
231              
232             sub fmt_json {
233 0     0 1   my ($self, $users, $total) = @_;
234 0           require JSON;
235              
236 0           print JSON::encode_json({ total => $total, users => $users });
237             }
238              
239             sub fmt_perl {
240 0     0 1   my ($self, $users, $total) = @_;
241 0           require Data::Dumper;
242              
243 0           local $Data::Dumper::Indent = 1;
244 0           print Data::Dumper::Dumper({ total => $total, users => $users });
245             }
246              
247             1;
248              
249             __DATA__
250              
251             =head1 NAME
252              
253             git-committers - Stats on the number of commits by committer
254              
255             =head1 VERSION
256              
257             This documentation refers to git-committers version 1.1.16
258              
259             =head1 SYNOPSIS
260              
261             git-committers [option]
262              
263             OPTIONS:
264             -r --remote Committers to remote branches
265             -a --all Committers to any branch (remote or local)
266             -c --changes Add stats for lines added/removed
267             -C --commits Output the individual commits (with --format json)
268             -s --since[=]YYYY-MM-DD
269             Only commits since this date
270             -u --until[=]YYYY-MM-DD
271             Only commits up until this date
272             -f --format[=](table|json|csv)
273             Change how the data is presented
274             - table : shows the data in a simple table
275             - json : returns the raw data as a json object
276             - perl : Dump the data structure
277             -p --period=[day|week|month|year]
278             If --since is not specified this works out the date for the
279             last day/week/month/year
280             -P --periods[=]int
281             Generate stats for more than one period.
282             -M --min-commit[=]int
283             Only show stats for users with at least this number of commits
284             -m --merges Count merge commits
285             --no-merges
286             Don't count merge commits
287              
288             -v --verbose Show more detailed option
289             --version Prints the version information
290             --help Prints this help information
291             --man Prints the full documentation for git-committers
292              
293             =head1 DESCRIPTION
294              
295             The C<git-committers> command allows to get statistics on who is committing
296             to the git repository.
297              
298             =head1 SUBROUTINES/METHODS
299              
300             =head2 C<run ()>
301              
302             Executes the git workflow command
303              
304             =head2 C<dates ($period, $count)>
305              
306             Returns the C<--since> and C<--until> dates for the C<$period> specified
307              
308             =head2 C<changes ($commits)>
309              
310             Calculates the changes for C<$commits>.
311              
312             =head2 C<fmt_table ()>
313              
314             Output a table
315              
316             =head2 C<fmt_json ()>
317              
318             Output JSON
319              
320             =head2 C<fmt_perl ()>
321              
322             Output a Perl object
323              
324             =head1 DIAGNOSTICS
325              
326             =head1 CONFIGURATION AND ENVIRONMENT
327              
328             =head1 DEPENDENCIES
329              
330             =head1 INCOMPATIBILITIES
331              
332             =head1 BUGS AND LIMITATIONS
333              
334             There are no known bugs in this module.
335              
336             Please report problems to Ivan Wills (ivan.wills@gmail.com).
337              
338             Patches are welcome.
339              
340             =head1 AUTHOR
341              
342             Ivan Wills - (ivan.wills@gmail.com)
343              
344             =head1 LICENSE AND COPYRIGHT
345              
346             Copyright (c) 2014 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077).
347             All rights reserved.
348              
349             This module is free software; you can redistribute it and/or modify it under
350             the same terms as Perl itself. See L<perlartistic>. This program is
351             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
352             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
353             PARTICULAR PURPOSE.
354              
355             =cut