File Coverage

blib/lib/App/Git/Workflow/Command/UpToDate.pm
Criterion Covered Total %
statement 148 155 95.4
branch 43 64 67.1
condition 17 30 56.6
subroutine 19 19 100.0
pod 11 11 100.0
total 238 279 85.3


line stmt bran cond sub pod time code
1              
2             # Created on: 2014-01-16 04:14:31
3             # Create by: Ivan Wills
4             # $Id$
5             # $Revision$, $HeadURL$, $Date$
6             # $Revision$, $Source$, $Date$
7              
8             use strict;
9 2     2   81376 use warnings;
  2         12  
  2         61  
10 2     2   8 use version;
  2         3  
  2         53  
11 2     2   332 use English qw/ -no_match_vars /;
  2         1449  
  2         11  
12 2     2   512 use App::Git::Workflow;
  2         2619  
  2         10  
13 2     2   916 use App::Git::Workflow::Command qw/get_options/;
  2         4  
  2         68  
14 2     2   329 use Carp qw/cluck/;
  2         4  
  2         88  
15 2     2   10  
  2         4  
  2         2688  
16             our $VERSION = version->new(1.1.20);
17             our $workflow = App::Git::Workflow->new;
18             our ($name) = $PROGRAM_NAME =~ m{^.*/(.*?)$}mxs;
19             our %option;
20             our %p2u_extra;
21              
22             my $self = shift;
23             %option = (
24 16     16 1 23 format => 'test',
25 16   100     45 max_history => $workflow->config('workflow.max-history') || 1,
26             branches => 0,
27             );
28             get_options(
29             \%option,
30 16 100       51 'tag|t=s',
31             'branch|b=s',
32             'local|l!',
33             'remote|r!',
34             'format|f=s',
35             'quick|q!',
36             'include|i=s',
37             'exclude|e=s',
38             'all',
39             'max_history|max-history|m=i',
40             'fetch|F',
41             'fix|x',
42             ) or return;
43              
44             my $action = shift @ARGV || 'am_i';
45             my $format = 'format_' . $option{format};
46 15   100     35  
47 15         26 $action =~ s/-/_/g;
48             $action = "do_$action";
49 15         30  
50 15         23 if ( !$self->can($action) ) {
51             $action =~ s/^do_//;
52 15 100 100     135 $action =~ s/_/-/;
    100          
53 1         3 warn "Unknown action '$action'!\n";
54 1         2 Pod::Usage::pod2usage( %p2u_extra, -verbose => 1 );
55 1         30 return 1;
56 1         10 }
57 1         7974 elsif ( $action eq 'do_show' && !$self->can($format) ) {
58             warn "Unknown format '$option{format}'!\n";
59             Pod::Usage::pod2usage( %p2u_extra, -verbose => 1 );
60 1         32 return 1;
61 1         8 }
62 1         7941  
63             $workflow->{VERBOSE} = $option{verbose};
64             $workflow->{TEST } = $option{test};
65 13         27  
66 13         19 $workflow->git->fetch if $option{fetch};
67              
68 13 100       29 # do stuff here
69             if ($option{branch_age}) {
70             return branch_age();
71 13 50       21 }
72 0         0  
73             if ($action eq 'do_show') {
74             $option{branches} = 1;
75 13 100       26 }
76 7         9 my @releases = $workflow->releases(%option);
77              
78 13         44 if ($option{verbose}) {
79             my $local = localtime($releases[-1]{time});
80 13 50       26 my $now = localtime;
81 0         0 my $time = time;
82 0         0 warn <<"DETAILS";
83 0         0 Branch : $releases[-1]{name}
84 0         0 SHA : $releases[-1]{sha}
85             Time : $local ($releases[-1]{time})
86             Now : $now ($time)
87              
88             DETAILS
89             }
90              
91             $option{all} = 1 if $action eq 'do_show' && $option{format} eq 'test';
92              
93 13 100 100     52 $self->$action(@releases);
94              
95 13         43 return;
96             }
97 13         48  
98             my ($self, @releases) = @_;
99             my $csv = branches_contain(@releases);
100             if ($option{verbose}) {
101 7     7 1 11 warn @$csv . " branches found\n";
102 7         14 }
103 7 50       16  
104 0         0 my $format = 'format_' . $option{format};
105             $self->$format($csv, @releases);
106              
107 7         9 return;
108 7         22 }
109              
110 7         18 my (undef, @releases) = @_;
111              
112             # work out current branch, check that it contains a release branch
113             my $format = q/--format=format:%H %at <%an>%ae/;
114 4     4 1 13  
115             my $bad = 0;
116             for my $release (reverse @releases) {
117 4         5 my ($ans) = grep {/$release->{sha}/} $workflow->git->log($format);
118             chomp $ans if $ans;
119 4         6 next if $ans;
120 4         7 $bad++;
121 4         9 warn "Missing release $release->{name}!\n";
  4         19  
122 4 100       8 }
123 4 100       9  
124 3         3 if ($bad) {
125 3         124 if ( $option{fix} ) {
126             $workflow->git->merge($releases[-1]{name});
127             return;
128 4 100       12 }
129 3 100       8  
130 1         4 return $bad;
131 1         2 }
132             else {
133             print "Up to date\n";
134 2         5 }
135              
136             return;
137 1         32 }
138              
139             my (undef, @releases) = @_;
140 1         5 print "Current prod \"$releases[0]{name}\"\n";
141              
142             return;
143             }
144 1     1 1 3  
145 1         30 my (undef, @releases) = @_;
146             print "Merging \"$releases[0]{name}\"\n";
147 1         4 $workflow->git->merge($releases[0]{name});
148              
149             return;
150             }
151 1     1 1 2  
152 1         30 my @releases = @_;
153 1         7 my @branches = $workflow->branches($option{remote} ? 'remote' : 'both');
154             my $format = q/--format=format:%at <%an>%ae/;
155 1         2 my @csv;
156              
157             BRANCH:
158             for my $branch (@branches) {
159 7     7 1 50 next BRANCH if $option{include} && $branch !~ /$option{include}/;
160 7 50       22 next BRANCH if $option{exclude} && $branch =~ /$option{exclude}/;
161 7         11  
162 7         7 my ($first, $author, $found, $release);
163              
164             my ($log) = $workflow->git->log($format, qw/-n 1/, $branch);
165 7         12 next if !$log;
166 14 50 33     30 my ($time, $user) = split /\s+/, $log, 2;
167 14 50 33     49  
168             $first = $time;
169 14         20 $author = $user;
170             if ( $time < $releases[-1]{time} ) {
171 14         31 warn "skipping $branch\n" if $option{verbose} && $option{verbose} > 1;
172 14 50       23 next BRANCH;
173 14         43 }
174              
175 14         19 my $age = time - $releases[0]{time} + 10 * 60 * 60 * 24;
176 14         18 my $ago = -1;
177 14 100       35 RELEASE:
178 4 50 33     7 for my $released (reverse @releases) {
179 4         8 $ago++;
180             next RELEASE if !$released->{branches}{$branch};
181              
182 10         17 $release = $released->{name};
183 10         11 $age = $released == $releases[-1] ? 0 : time - $released->{time};
184             last RELEASE;
185 10         16 }
186 10         11  
187 10 50       19 next BRANCH if !$option{all} && !$option{verbose} && $found;
188              
189 10         11 push @csv, [ $release || "Out of date", $branch, $author, int $age / 60 / 60 / 24, $ago];
190 10 50       19 warn +( $found ? 'up to date' : "missing $releases[-1]{name}" ) . "\t$branch\t$author\n" if $option{quick};
191 10         15 }
192              
193             return \@csv;
194 10 50 33     37 }
      33        
195              
196 10   50     30 my (undef, $csv, @releases) = @_;
197 10 0       23  
    50          
198             my @max = (0,0,0);
199             for my $row (@$csv) {
200 7         16 $max[0] = length $row->[0] if $max[0] < length $row->[0];
201             $max[1] = length $row->[1] if $max[1] < length $row->[1];
202             $max[2] = length $row->[2] if $max[2] < length $row->[2];
203             }
204 1     1 1 3 for my $row (@$csv) {
205             printf "%$max[0]s %-$max[1]s %-$max[2]s (%2.0f days old)\n", @$row[0..3];
206 1         2 }
207 1         2  
208 2 100       4 return;
209 2 50       5 }
210 2 100       4  
211             my (undef, $csv, @releases) = @_;
212 1         3  
213 2         64 my $sepperator = $option{format} eq 'tab' ? "\t" : ',';
214             for my $row (@$csv) {
215             print +(join $sepperator, @$row), "\n";
216 1         4 }
217              
218             return;
219             }
220 2     2 1 4 {
221             no warnings qw/once/;
222 2 100       6 *format_tab = *format_csv;
223 2         4 }
224 4         90  
225             my (undef, $csv, @releases) = @_;
226              
227 2         7 require JSON;
228             my $repo = $workflow->config('remote.origin.url');
229             my ($name) = $repo =~ m{[/:](.*?)(?:[.]git)?$}xms;
230 2     2   15  
  2         3  
  2         990  
231             print JSON::encode_json({
232             repository => $repo,
233             name => $name,
234             release => $releases[-1]{name},
235 1     1 1 3 release_date => '' . localtime($releases[-1]{time}),
236             branches => [
237 1         5 map {{ status => $_->[0], name => $_->[1], last_author => $_->[2], release_age => $_->[3] }}
238 1         4 @$csv
239 1         6 ]
240             });
241              
242             return;
243             }
244              
245             my (undef, $csv, @releases) = @_;
246              
247 1         28 my $sepperator = "</td><td>";
  2         48  
248             my $date = localtime;
249             my $repo = $workflow->config('remote.origin.url');
250             print <<"HTML";
251             <table>
252 1         8 <caption>Branch statuses for <i>$repo</i> ($date)</caption>
253             <thead>
254             <tr>
255             <th>Production Branch/Tag Status</th>
256 1     1 1 3 <th>Branch Name</th>
257             <th>Last commit owner</th>
258 1         3 <th>Included release age (days)</th>
259 1         28 </tr>
260 1         5 </thead>
261 1         33 HTML
262              
263             for my $row (@$csv) {
264             next if !$row && !$row->[2];
265             my ($name, $email) = $row->[2] =~ /^<([^>]+)>(.*)$/;
266             $row->[0] = $row->[0] eq $releases[-1]{name} ? $row->[0] : qq{<span class="old">$row->[0]</span>};
267             $row->[2] = $row->[0] eq $releases[-1]{name} ? $name : qq{<a href="mailto:$email?subject=$row->[1]%20is%20out%20of%20date">$name</a>};
268             print "<tr class=\"age_$row->[4]\"><td>" . (join $sepperator, @$row[0..3]), "</td></tr>\n";
269             }
270              
271             print "</table>\n";
272              
273             return;
274 1         4 }
275 2 0 33     8  
276 2         13 my (undef, $csv, @releases) = @_;
277 2 50       6  
278 2 50       5 require Test::More;
279 2         28 Test::More->import();
280             for my $row (@$csv) {
281             is( $row->[0], $releases[-1]{name}, $row->[1] . ' is upto date')
282 1         9 or note("Release is $row->[3] days old");
283             }
284 1         4  
285             return;
286             }
287              
288 2     2 1 5 1;
289              
290 2         9  
291 2         12 =head1 NAME
292 2         334  
293 0 0       0 git-up-to-date - Check that git branches include latest production branch/tag
294              
295             =head1 VERSION
296              
297 2         3 This documentation refers to git-up-to-date version 1.1.20
298              
299             =head1 SYNOPSIS
300              
301             git-up-to-date [am-i] [option]
302             git-up-to-date show [option]
303             git-up-to-date current [option]
304             git-up-to-date update-me [option]
305              
306             SUB-COMMANDS
307             am-i (default) determine if the current branch is up-to-date
308             show Show's the status of all active branches (ie branches with
309             commits since last release)
310             current Show the current "production" branch or tag
311             update-me Merges in the latest release
312              
313             OPTIONS:
314             -t --tab[=]str Specify a tag that any branch with newer commits must contain
315             -b --branch[=]str Similarly a branch that other branches with newer commits must
316             contain (Default origin/master)
317             -l --local Shorthand for --branch '^master$'
318             -f --format[=](test|text|html|csv|tab|json)
319             Set the out put format
320             * test - TAP test formatted output (default)
321             * text - Simple formatted text
322             * html - A basic HTML page
323             * csv - Comma seperated values formatted output
324             * tab - Tab seperated values formatted output
325             -q --quick Print to STDERR the statuses as they are found (no formatting)
326             -i --include[=]regexp
327             Include only "neweer" branches that match this regexp
328             -e --exclude[=]regexp
329             Exclude any "neweer" branches that match this regexp
330             --all Show the status of all branches not just current ones.
331             -m --max-history[=]int
332             Set the maximum number of release branches/tags to go back
333             (if more than one) to find where a branch was created from.
334             (Default 1)
335              
336             -s --branch-status
337             Shows the status (name, last committer, last commit date) of
338             all branches.
339             -a --age-limit[=]date
340             With --branch-status limit to only branches created after
341             date (a YYYY-MM-DD formatted date)
342             -F --fetch Do a fetch before anything else.
343             -x --fix With am-i, merges in the current prod/release branch/tag
344              
345             -v --verbose Shows changed branches that are upto date.
346             --version Prints the version information
347             --help Prints this help information
348             --man Prints the full documentation for git-up-to-date
349              
350             =head1 DESCRIPTION
351              
352             The C<git up-to-date> command can tell you the status of "active" branches as
353             compared to a release tag or branch. It does this by finding all tags or
354             branches that match the regular expression passed to C<--tag> or C<--branch>,
355             sorts them alpha-numerically assuming that the largest is the most recent.
356              
357             eg release_1, release_1_1
358              
359             The branch release_1_1 would be considered the most recent. With the found
360             tag/branch the date/time it was created is used to find all branches that
361             have newer commits (unless C<--all> is used). These branches are then searched
362             to see if they contain the found release tag or branch (and if C<--max-history>
363             is specified and the branch doesn't contain the release branch or tag the older
364             releases are searched for).
365              
366             =head1 SUBROUTINES/METHODS
367              
368             =head2 C<run ()>
369              
370             Executes the git workflow command
371              
372             =head2 C<do_show ()>
373              
374             =head2 C<do_am_i ()>
375              
376             =head2 C<do_current ()>
377              
378             =head2 C<do_update_me ()>
379              
380             =head2 C<branches_contain ()>
381              
382             =head2 C<format_text ()>
383              
384             =head2 C<format_csv ()>
385              
386             =head2 C<format_json ()>
387              
388             =head2 C<format_html ()>
389              
390             =head2 C<format_test ()>
391              
392             =head2 C<format_tab ()>
393              
394             =head1 DIAGNOSTICS
395              
396             =head1 CONFIGURATION AND ENVIRONMENT
397              
398             Defaults for this script can be set through C<git config>
399              
400             workflow.prod Sets how a prod release is determined
401             eg the default equivalent is branch=^origin/master$
402             workflow.max-history
403             Sets the default C<--max-history> value
404              
405             You can set these values either by editing the repository local C<.git/config>
406             file or C<~/.gitconfig> or use the C<git config> command
407              
408             # eg Setting the global value
409             git config --global workflow.max-history 10
410              
411             # or set a repository's local value
412             git config workflow.prod 'tag=^release_\d{4}_\d{2}\d{2}$'
413              
414             =head1 DEPENDENCIES
415              
416             =head1 INCOMPATIBILITIES
417              
418             =head1 BUGS AND LIMITATIONS
419              
420             There are no known bugs in this module.
421              
422             Please report problems to Ivan Wills (ivan.wills@gmail.com).
423              
424             Patches are welcome.
425              
426             =head1 AUTHOR
427              
428             Ivan Wills - (ivan.wills@gmail.com)
429              
430             =head1 LICENSE AND COPYRIGHT
431              
432             Copyright (c) 2014 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077).
433             All rights reserved.
434              
435             This module is free software; you can redistribute it and/or modify it under
436             the same terms as Perl itself. See L<perlartistic>. This program is
437             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
438             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
439             PARTICULAR PURPOSE.
440              
441             =cut