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