File Coverage

blib/lib/App/Git/Workflow/Command/Recent.pm
Criterion Covered Total %
statement 33 181 18.2
branch 0 78 0.0
condition 0 18 0.0
subroutine 11 20 55.0
pod 8 8 100.0
total 52 305 17.0


line stmt bran cond sub pod time code
1             package App::Git::Workflow::Command::Recent;
2              
3             # Created on: 2014-03-11 20:58:59
4             # Create by: Ivan Wills
5             # $Id$
6             # $Revision$, $HeadURL$, $Date$
7             # $Revision$, $Source$, $Date$
8              
9 2     2   119428 use strict;
  2         17  
  2         60  
10 2     2   12 use warnings;
  2         5  
  2         66  
11 2     2   451 use version;
  2         1937  
  2         12  
12 2     2   872 use Getopt::Long;
  2         10679  
  2         29  
13 2     2   853 use Pod::Usage ();
  2         55256  
  2         67  
14 2     2   670 use List::MoreUtils qw/uniq/;
  2         13538  
  2         21  
15 2     2   2683 use Data::Dumper qw/Dumper/;
  2         6943  
  2         152  
16 2     2   561 use English qw/ -no_match_vars /;
  2         3720  
  2         13  
17 2     2   1778 use CHI::Memoize qw(:all);
  2         321526  
  2         303  
18 2     2   557 use App::Git::Workflow;
  2         9  
  2         98  
19 2     2   466 use App::Git::Workflow::Command qw/get_options/;
  2         6  
  2         4177  
20              
21             our $VERSION = version->new(1.1.16);
22             our $workflow = App::Git::Workflow->new;
23             our ($name) = $PROGRAM_NAME =~ m{^.*/(.*?)$}mxs;
24             our %option;
25             our $memoized = 0;
26              
27             sub run {
28 0     0 1   my $self = shift;
29              
30 0           get_options(
31             \%option,
32             'all|a',
33             'branch|b',
34             'day|d',
35             'depth|D=i',
36             'path_depth|path-depth|p=i%',
37             'files|f',
38             'ignore_files|ignore-files=s@',
39             'ignore_user|ignore-users=s@',
40             'ignore_branch|ignore-branches=s@',
41             'month|m',
42             'out|o=s',
43             'quiet|q',
44             'remote|r',
45             'since|s=s',
46             'tag|t',
47             'users|u',
48             'week|w',
49             );
50              
51 0 0         if (!$memoized) {
52 0           my $git_dir = $workflow->git->rev_parse("--show-toplevel");
53 0           chomp $git_dir;
54 0           $git_dir =~ s{[/\\]$}{};
55             memoize('App::Git::Workflow::commit_details',
56             driver => 'File',
57             root_dir => "$git_dir/.git/gw-commit-detials",
58             expires_in => '1M',
59 0     0     key => sub { shift @_; @_ },
  0            
60 0           );
61 0           $memoized = 1;
62             }
63              
64             # get a list of recent commits
65 0           my @commits = $self->recent_commits(\%option);
66              
67             # find the files in each commit
68 0           my %changed = $self->changed_from_shas(@commits);
69              
70 0 0         if ( $option{users} ) {
    0          
71 0           my %users;
72 0           for my $file (keys %changed) {
73 0           for my $user (@{ $changed{$file}{users} }) {
  0            
74 0   0       $users{$user} ||= {};
75 0           @{ $users{$user}{files} } = (
76 0 0         uniq sort @{ $users{$user}{files} || [] }, @{ $changed{$file}{files} || [] }
  0 0          
  0            
77             );
78 0           @{ $users{$user}{branches} } = (
79 0 0         uniq sort @{ $users{$user}{branches} || [] }, @{ $changed{$file}{branches} || [] }
  0 0          
  0            
80             );
81             }
82             }
83 0           %changed = %users;
84             }
85             elsif ( $option{branches} ) {
86 0           my %branches;
87 0           for my $file (keys %changed) {
88 0           for my $branch (@{ $changed{$file}{branches} }) {
  0            
89 0   0       $branches{$branch} ||= {};
90 0           @{ $branches{$branch}{files} } = (
91 0 0         uniq sort @{ $branches{$branch}{files} || [] }, @{ $changed{$file}{files} || [] }
  0 0          
  0            
92             );
93 0           @{ $branches{$branch}{users} } = (
94 0 0         uniq sort @{ $branches{$branch}{users} || [] }, @{ $changed{$file}{users} || [] }
  0 0          
  0            
95             );
96             }
97             }
98 0           %changed = %branches;
99             }
100             else {
101 0           my %files;
102 0           for my $file (keys %changed) {
103 0           delete $changed{$file}{files};
104             }
105             }
106              
107             # display results
108 0   0       my $out = 'out_' . ($option{out} || 'text');
109              
110 0 0         if ($self->can($out)) {
111 0           $self->$out(\%changed);
112             }
113              
114 0           return;
115             }
116              
117             sub out_text {
118 0     0 1   my ($self, $changed) = @_;
119              
120 0           for my $file (sort keys %$changed) {
121 0           print "$file\n";
122 0 0         if ( ! $option{users} ) {
123 0 0         print " Changed by : " . ( join ', ', @{ $changed->{$file}{users} || [] } ), "\n";
  0            
124             }
125 0 0         if ( ! $option{branches} ) {
126 0 0         print " In branches: " . ( join ', ', @{ $changed->{$file}{branches} || [] } ), "\n";
  0            
127             }
128 0 0 0       if ( $option{users} || $option{branches} ) {
129 0 0         print " Files: " . ( join ', ', @{ $changed->{$file}{files} || [] } ), "\n";
  0            
130             }
131             }
132              
133 0           return;
134             }
135              
136             sub out_perl {
137 0     0 1   my ($self, $changed) = @_;
138              
139 0           $Data::Dumper::Sortkeys = 1;
140 0           $Data::Dumper::Indent = 1;
141 0           print Dumper $changed;
142              
143 0           return;
144             }
145              
146             sub out_json {
147 0     0 1   my ($self, $changed) = @_;
148              
149 0           require JSON;
150 0           print JSON::encode_json($changed), "\n";
151              
152 0           return;
153             }
154              
155             sub out_yaml {
156 0     0 1   my ($self, $changed) = @_;
157              
158 0           require YAML;
159 0           print YAML::Dump($changed);
160              
161 0           return;
162             }
163              
164             sub recent_commits {
165 0     0 1   my ($self, $option) = @_;
166              
167 0           my @args = ('--since', $option->{since} );
168              
169 0 0         if ( !$option->{since} ) {
170             my $sec_ago = $option->{month} ? 60 * 60 * 24 * 30
171 0 0         : $option->{week} ? 60 * 60 * 24 * 7
    0          
172             : 60 * 60 * 24;
173              
174 0           my (undef,undef,undef,$day,$month,$year) = localtime( time - $sec_ago );
175 0           $year += 1900;
176 0           $month++;
177              
178 0           @args = ('--since', sprintf "%04d-%02d-%02d", $year, $month, $day );
179             }
180              
181             unshift @args, $option->{tag} ? '--tags'
182             : $option->{all} ? '--all'
183 0 0         : $option->{remote} ? '--remotes'
    0          
    0          
184             : '--branches';
185              
186 0           return $workflow->git->rev_list(@args);
187             }
188              
189             sub changed_from_shas {
190 0     0 1   my ($self, @commits) = @_;
191 0           my %changed;
192 0           my $count = 0;
193 0 0         print {*STDERR} '.' if $option{verbose};
  0            
194              
195 0           for my $sha (@commits) {
196 0           my $changed = $workflow->commit_details($sha, branches => 1, files => 1, user => 1);
197 0 0         next if $self->ignore($changed);
198              
199 0           for my $type (keys %{ $changed->{files} }) {
  0            
200 0 0         if ( defined $option{depth} ) {
201 0           $type = join '/', grep {defined $_} (split m{/}, $type)[0 .. $option{depth} - 1];
  0            
202             }
203 0 0         if ( defined $option{path_depth} ) {
204 0           for my $path (keys %{ $option{path_depth} }) {
  0            
205 0 0         if ( $type =~ /^$path/ ) {
206 0           $type = join '/', grep {defined $_} (split m{/}, $type)[0 .. $option{path_depth}{$path} - 1];
  0            
207             }
208             }
209             }
210 0           my %branches;
211 0 0         if ( $option{remote} ) {
    0          
212 0           %branches = map { $_ => 1 } grep {/^origin/} keys %{ $changed->{branches} };
  0            
  0            
  0            
213             }
214             elsif ( $option{all} ) {
215 0           %branches = %{ $changed->{branches} };
  0            
216             }
217             else {
218 0           %branches = map { $_ => 1 } grep {!/^origin/} keys %{ $changed->{branches} };
  0            
  0            
  0            
219             }
220 0 0         next if !%branches;
221              
222 0           $changed{$type}{users}{$changed->{user}}++;
223             $changed{$type}{files} = {
224 0 0         %{ $changed{$type}{files} || {} },
225 0           %{ $changed->{files} },
  0            
226             };
227             $changed{$type}{branches} = {
228 0 0         %{ $changed{$type}{branches} || {} },
  0            
229             %branches,
230             };
231             }
232              
233 0 0 0       print {*STDERR} '.' if $option{verbose} && ++$count % 10 == 0;
  0            
234             }
235              
236 0           for my $type (keys %changed) {
237 0           $changed{$type}{users } = [ sort keys %{ $changed{$type}{users } } ];
  0            
238 0           $changed{$type}{files } = [ sort keys %{ $changed{$type}{files } } ];
  0            
239 0           $changed{$type}{branches} = [ sort keys %{ $changed{$type}{branches} } ];
  0            
240             }
241              
242 0           return %changed;
243             }
244              
245             sub ignore {
246 0     0 1   my ($self, $commit) = @_;
247              
248 0 0         if ($option{ignore_files}) {
249 0           for my $ignore (@{ $option{ignore_files} }) {
  0            
250 0           for my $file (keys %{ $commit->{files} }) {
  0            
251 0 0         return 1 if $file =~ /$ignore/;
252             }
253             }
254             }
255              
256 0 0 0       if ($option{ignore_user}
257 0           && grep {$commit->{user} =~ /$_/} @{ $option{ignore_user} } ) {
  0            
258 0           return 1;
259             }
260              
261 0 0 0       if ($option{ignore_branch}
262 0           && grep {$commit->{branches} =~ /$_/} @{ $option{ignore_branch} } ) {
  0            
263 0           return 1;
264             }
265              
266 0           return 0;
267             }
268              
269             1;
270              
271             __DATA__
272              
273             =head1 NAME
274              
275             git-recent - Find what files have been changed recently in a repository
276              
277             =head1 VERSION
278              
279             This documentation refers to git-recent version 1.1.16
280              
281             =head1 SYNOPSIS
282              
283             git-recent [-since=YYYY-MM-DD|--day|--week|--month] [(-o|--out) [text|json|perl]]
284             git-recent --help
285             git-recent --man
286             git-recent --version
287              
288             OPTIONS:
289             -s --since[=]iso-date
290             Show changed files since this date
291             -d --day Show changed files from the last day (Default action)
292             -w --week Show changed files from the last week
293             -m --month Show changed files from the last month
294             -a --all Show recent based on local and remote branches
295             -r --remote Show recent based on remotes only
296             -t --tag Show recent based on tags only
297              
298             OUTPUT:
299             -b --branch Show the output by what's changed in each branch
300             -D --depth[=]int
301             Truncate files to this number of directories (allows showing
302             areas that have changed)
303             -u --users Show the output by who has made the changes
304             -f --files Show the output the files changed (Default)
305             --ignore-user[=]regexp
306             --ignore-users[=]regexp
307             Ignore any user(s) matching regexp (can be specified more than once)
308             --ignore-branch[=]regexp
309             --ignore-branches[=]regexp
310             Ignore any branch(s) matching regexp (can be specified more than once)
311             -o --out[=](text|json|perl)
312             Specify how to display the results
313             - text : Nice human readable format (Default)
314             - json : as a JSON object
315             - perl : as a Perl object
316             -q --quiet Don't show who has changed the file or where it was changed
317              
318             -v --verbose Show more detailed option
319             --version Prints the version information
320             --help Prints this help information
321             --man Prints the full documentation for git-recent
322              
323             =head1 DESCRIPTION
324              
325             C<git-recent> finds all files that have been changed in all branches in the
326             repository. This allows collaborators to quickly see who is working on what
327             even if it's in a different branch.
328              
329             =head1 SUBROUTINES/METHODS
330              
331             =head2 C<run ()>
332              
333             Executes the git workflow command
334              
335             =head2 C<recent_commits ($options)>
336              
337             Gets a list of recent commits
338              
339             =head2 C<changed_from_shas (@commits)>
340              
341             Takes a list of commits and returns a HASH of files changed, by whom and in
342             what branches.
343              
344             =head2 C<out_text ($changed)>
345              
346             Displays changed files in a textural format
347              
348             =head2 C<out_perl ($changed)>
349              
350             Displays changed files in a Perl format
351              
352             =head2 C<out_json ($changed)>
353              
354             Displays changed files in a JSON format
355              
356             =head2 C<out_yaml ($changed)>
357              
358             Displays changed files in a YAML format
359              
360             =head2 C<ignore ($commit)>
361              
362             Determine if a commit should be ignored or not
363              
364             =head1 DIAGNOSTICS
365              
366             =head1 CONFIGURATION AND ENVIRONMENT
367              
368             =head1 DEPENDENCIES
369              
370             =head1 INCOMPATIBILITIES
371              
372             =head1 BUGS AND LIMITATIONS
373              
374             There are no known bugs in this module.
375              
376             Please report problems to Ivan Wills (ivan.wills@gmail.com).
377              
378             Patches are welcome.
379              
380             =head1 AUTHOR
381              
382             Ivan Wills - (ivan.wills@gmail.com)
383              
384             =head1 LICENSE AND COPYRIGHT
385              
386             Copyright (c) 2014 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077).
387             All rights reserved.
388              
389             This module is free software; you can redistribute it and/or modify it under
390             the same terms as Perl itself. See L<perlartistic>. This program is
391             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
392             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
393             PARTICULAR PURPOSE.
394              
395             =cut