File Coverage

blib/lib/App/Git/Workflow/Command/Changes.pm
Criterion Covered Total %
statement 24 141 17.0
branch 0 64 0.0
condition 0 15 0.0
subroutine 8 14 57.1
pod 6 6 100.0
total 38 240 15.8


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