File Coverage

blib/lib/App/Git/Workflow/Command/BranchAge.pm
Criterion Covered Total %
statement 30 115 26.0
branch 0 48 0.0
condition 0 12 0.0
subroutine 10 13 76.9
pod 3 3 100.0
total 43 191 22.5


line stmt bran cond sub pod time code
1             package App::Git::Workflow::Command::BranchAge;
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 1     1   1161 use strict;
  1         3  
  1         31  
10 1     1   6 use warnings;
  1         2  
  1         26  
11 1     1   5 use version;
  1         2  
  1         5  
12 1     1   89 use English qw/ -no_match_vars /;
  1         2  
  1         7  
13 1     1   1029 use List::MoreUtils qw/zip/;
  1         13861  
  1         6  
14 1     1   1099 use Term::ANSIColor qw/colored/;
  1         2  
  1         59  
15 1     1   6 use App::Git::Workflow;
  1         2  
  1         38  
16 1     1   44 use App::Git::Workflow::Command qw/get_options/;
  1         22  
  1         51  
17 1     1   536 use DateTime::Format::HTTP;
  1         525885  
  1         45  
18 1     1   9 use Data::Dumper qw/Dumper/;
  1         3  
  1         1367  
19              
20             our $VERSION = version->new(1.1.16);
21             our $workflow = App::Git::Workflow->new;
22             our ($name) = $PROGRAM_NAME =~ m{^.*/(.*?)$}mxs;
23             our %option = (
24             master => 'origin/master',
25             );
26              
27             sub run {
28 0     0 1   get_options(
29             \%option,
30             'all|a',
31             'remote|r',
32             'reverse|R',
33             'unmerged|u!',
34             'master|m=s',
35             'limit|n=i',
36             'format|f=s',
37             'files|F!',
38             'quiet|q',
39             );
40 0           my $fmt = join "-%09-%09-", qw/
41             %(authordate)
42             %(authoremail)
43             %(authorname)
44             %(body)
45             %(HEAD)
46             %(objectname)
47             %(objecttype)
48             %(refname)
49             %(refname:short)
50             %(subject)
51             /;
52 0           my @headings = qw/
53             authordate
54             authoremail
55             authorname
56             body
57             HEAD
58             objectname
59             objecttype
60             refname
61             short
62             subject
63             /;
64              
65 0           my $arg = '';
66 0 0         if ( $option{remote} ) {
67 0           $arg .= ' -r';
68             }
69 0           my $match = '';
70 0           my $files;
71 0 0         if (@ARGV) {
72 0 0         if ($option{files}) {
73 0           $files = join ' ', @ARGV;
74             }
75             else {
76 0 0         $match = @ARGV ? shift @ARGV : '';
77             }
78             }
79              
80 0           my @branches = `git branch $arg --format='$fmt'`;
81 0           my $i = 0;
82 0           my $last = '';
83 0           my @data;
84              
85 0           for my $branch (@branches) {
86 0           chomp $branch;
87 0 0         if ($last) {
88 0           $last .= "\n";
89             }
90 0           $last .= $branch;
91 0           my @cols = split /-\t-\t-/, $last;
92 0 0         if (@cols < @headings) {
93 0           next;
94             }
95              
96 0           $last = '';
97 0           $branch = { zip @headings, @cols };
98 0 0 0       next if $match && $branch->{short} !~ /$match/;
99 0 0         warn 'bad head' if !$branch->{HEAD};
100 0 0         next if !$branch->{HEAD};
101 0 0         if ( defined $option{unmerged} ) {
102 0 0         next if unmerged($branch->{short}, $option{master});
103             }
104              
105 0 0         if ($files) {
106 0           my $date = `git log -n 1 --format=format:%ai $branch->{short} -- $files`;
107 0           chomp $date;
108              
109 0 0         if ($date) {
110 0           $date = $branch->{authordate};
111             }
112             }
113              
114 0           my ($date, $tz) = $branch->{authordate} =~ /^(.*)\s+([+-]\d{4})$/;
115 0 0 0       if ($date && $tz) {
116 0           $branch->{age} = DateTime::Format::HTTP->parse_datetime($date, $tz)->iso8601;
117             }
118             else {
119 0           $Data::Dumper::Sortkeys = 1;
120 0           $Data::Dumper::Indent = 1;
121 0           die Dumper $branch;
122             }
123 0           push @data, $branch;
124             }
125              
126 0           my %max = map {$_ => length $_} @headings;
  0            
127 0           for my $branch (@data) {
128 0           for my $key (keys %{$branch}) {
  0            
129 0 0 0       $max{$key} = length $branch->{$key} if !$max{$key} || $max{$key} < length $branch->{$key};
130             }
131             }
132              
133 0           @data = sort {$a->{age} cmp $b->{age}} @data;
  0            
134 0 0         if ($option{reverse}) {
135 0           @data = reverse @data;
136             }
137              
138 0           my $count = 1;
139             my $fmt_out = $option{verbose} ? "%-age\t%-authorname\t%-short"
140             : $option{quiet} ? '%short'
141             : $option{format} ? $option{format}
142 0 0         : "%age\t%short";
    0          
    0          
143 0           my ($format, @fields) = formatted($fmt_out, \%max);
144              
145 0 0 0       if ($option{limit} && @data > $option{limit}) {
146 0           @data = splice @data, @data - $option{limit}, $option{limit};
147             }
148 0           for my $branch (@data) {
149 0           printf $format, map {$branch->{$_}} @fields;
  0            
150             }
151             }
152              
153             sub formatted {
154 0     0 1   my ($format, $max) = @_;
155 0           my @fields;
156 0           my $fmt = '';
157 0           my @fmt_parts = split /%([+-]?)(\((?:[a-z]+)\)|[a-z]+)/, $format;
158              
159 0           while (defined (my $fixed = shift @fmt_parts)) {
160 0           my $align = shift @fmt_parts;
161 0           my $name = shift @fmt_parts;
162 0           $name =~ s/^[(]|[)]$//g;
163 0           push @fields, $name;
164 0 0         $fmt .= $fixed . ( $align ? "%$align$max->{$name}s" : "%s" );
165             }
166              
167 0           return ("$fmt\n", @fields);
168             }
169              
170             my %dest;
171             sub unmerged {
172 0     0 1   my ($source, $dest) = @_;
173              
174 0 0         if ( ! $dest{$dest} ) {
175 0           @{$dest{$dest}} = map {/^(.*)\n/; $1} `git log --format=format:%H $dest`;
  0            
  0            
  0            
176 0 0         die "No destination branch commits for '$dest'" if !@{$dest{$dest}};
  0            
177             }
178              
179 0           my $source_sha = `git log --format=format:%H -n 1 $source`;
180 0           chomp $source_sha;
181              
182 0 0         return scalar grep {$_ && $_ eq $source_sha} @{$dest{$dest}};
  0            
  0            
183             }
184              
185             1;
186              
187             __DATA__
188              
189             =head1 NAME
190              
191             git-branch-age - grep tags
192              
193             =head1 VERSION
194              
195             This documentation refers to git-branch-age version 1.1.16
196              
197             =head1 SYNOPSIS
198              
199             git-branch-age [option] regex
200              
201             OPTIONS:
202             regex grep's perl (-P) regular expression
203             -a --all All branches (remote and local
204             -r --remote Remote branches only
205             -R --reverse Reverse the branch sort order
206             -u --unmerged
207             Only show branches not merged to --master
208             --no-unmerged
209             Only show branches merged to master
210             -m --master[=]str
211             Branch to check against for --unmerged and --no-unmerged
212             (Default origin/master)
213             -n --limit[=]int
214             Limit the out put to this number
215             -f --format[=]str
216             Specify a format for the output
217             eg normal format would be --format="%age %short"
218             verbose format would be --format="%age %authorname $short"
219             format keys:
220             - authordate
221             - authoremail
222             - authorname
223             - body
224             - HEAD
225             - objectname
226             - objecttype
227             - refname
228             - short
229             - subject
230              
231             -v --verbose Show more detailed option
232             --version Prints the version information
233             --help Prints this help information
234             --man Prints the full documentation for git-branch-age
235              
236             =head1 DESCRIPTION
237              
238             Short hand for running
239              
240             C<git branch | grep -P 'regex'>
241              
242             =head1 SUBROUTINES/METHODS
243              
244             =head2 C<run ()>
245              
246             Executes the git workflow command
247              
248             =head2 C<unmerged ($source, $dest)>
249              
250             Check if there are any commits in C<$source> that are not in C<$dest>
251              
252             =head2 C<formatted ($format, $max)>
253              
254             Creates a format for printf to output a line
255              
256             =head1 DIAGNOSTICS
257              
258             =head1 CONFIGURATION AND ENVIRONMENT
259              
260             =head1 DEPENDENCIES
261              
262             =head1 INCOMPATIBILITIES
263              
264             =head1 BUGS AND LIMITATIONS
265              
266             There are no known bugs in this module.
267              
268             Please report problems to Ivan Wills (ivan.wills@gmail.com).
269              
270             Patches are welcome.
271              
272             =head1 AUTHOR
273              
274             Ivan Wills - (ivan.wills@gmail.com)
275              
276             =head1 LICENSE AND COPYRIGHT
277              
278             Copyright (c) 2014 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077).
279             All rights reserved.
280              
281             This module is free software; you can redistribute it and/or modify it under
282             the same terms as Perl itself. See L<perlartistic>. This program is
283             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
284             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
285             PARTICULAR PURPOSE.
286              
287             =cut