File Coverage

blib/lib/App/Git/Workflow.pm
Criterion Covered Total %
statement 163 164 99.3
branch 53 62 85.4
condition 16 20 80.0
subroutine 28 28 100.0
pod 15 15 100.0
total 275 289 95.1


line stmt bran cond sub pod time code
1              
2             # Created on: 2014-03-11 22:09:32
3             # Create by: Ivan Wills
4             # $Id$
5             # $Revision$, $HeadURL$, $Date$
6             # $Revision$, $Source$, $Date$
7              
8             use strict;
9 25     25   217759 use warnings;
  25         58  
  25         671  
10 25     25   99 use version;
  25         40  
  25         527  
11 25     25   1178 use autodie;
  25         4675  
  25         123  
12 25     25   10377 use Carp qw/carp croak cluck confess longmess/;
  25         270511  
  25         100  
13 25     25   130337 use Data::Dumper qw/Dumper/;
  25         59  
  25         2189  
14 25     25   9165 use English qw/ -no_match_vars /;
  25         85943  
  25         1368  
15 25     25   1423 use App::Git::Workflow::Repository qw//;
  25         5742  
  25         165  
16 25     25   15190 use base qw/Exporter/;
  25         64  
  25         560  
17 25     25   133  
  25         38  
  25         2503  
18             our $VERSION = version->new(1.1.20);
19              
20             no warnings qw/once/;
21             my $A = $a;
22 25     25   137 $A =~ s/(\d+)/sprintf "%014i", $1/egxms;
  25         45  
  25         48280  
23 177     177   252 my $B = $b;
24 177         327 $B =~ s/(\d+)/sprintf "%014i", $1/egxms;
  95         269  
25 177         216  
26 177         321 return $A cmp $B;
  125         400  
27             }
28 177         503  
29             my $class = shift;
30             my %param = @_;
31             my $self = \%param;
32 146     146 1 409  
33 146         380 bless $self, $class;
34 146         264  
35             $self->{TEST} = 0;
36 146         253 $self->{VERBOSE} = 0;
37             $self->{GIT_DIR} = '.git';
38 146         471 $self->{branches} = undef;
39 146         273 $self->{tags} = undef;
40 146         331 $self->{settings_dir} = ($ENV{HOME} || "/tmp/") . '/.git-workflow';
41 146         251 mkdir $self->{settings_dir} if !-d $self->{settings_dir};
42 146         248  
43 146   100     634 return $self;
44 146 100       2893 }
45              
46 146         3696  
47             my ($self, $type, $contains) = @_;
48             $type ||= 'local';
49 494 50   494 1 3004 my @options
50             = $type eq 'local' ? ()
51             : $type eq 'remote' ? ('-r')
52 121     121 1 250 : $type eq 'both' ? ('-a')
53 121   100     257 : confess "Unknown type '$type'!\n";
54              
55 121 100       549 if ($contains) {
    100          
    100          
56             push @options, "--contains", $contains;
57             $type .= $contains;
58             }
59              
60 120 100       229 # assign to or cache
61 46         70 $self->{branches}{$type} = [
62 46         70 sort _alphanum_sort
63             map { /^[*]?\s+(?:remotes\/)?(.*?)\s*$/xms }
64             grep {!/HEAD/}
65             $self->git->branch(@options)
66             ] if !$self->{branches}{$type};
67              
68 222         1110 return @{ $self->{branches}{$type} };
69 222         474 }
70              
71 120 100       364 my ($self) = @_;
72             # assign to or cache
73 119         191 $self->{tags} = [
  119         332  
74             sort _alphanum_sort
75             #map { /^(.*?)\s*$/xms }
76             $self->git->tag
77 4     4 1 11 ] if !$self->{tags} || !@{ $self->{tags} };
78              
79             return @{ $self->{tags} };
80             }
81              
82             my ($self) = @_;
83 4 50 66     26 # get the git directory
  2         8  
84             my $git_dir = $self->git->rev_parse("--show-toplevel");
85 4         9 chomp $git_dir;
  4         13  
86              
87             # read the HEAD file to find what branch or id we are on
88             open my $fh, '<', "$git_dir/$self->{GIT_DIR}/HEAD";
89 9     9 1 15 my $head = <$fh>;
90             close $fh;
91 9         20 chomp $head;
92 9         17  
93             if ($head =~ m{ref: refs/heads/(.*)$}) {
94             return ('branch', $1);
95 9         45 }
96 9         7962  
97 9         50 # try to identify the commit as it's not a local branch
98 9         2780 open $fh, '<', "$git_dir/$self->{GIT_DIR}/FETCH_HEAD";
99             while (my $line = <$fh>) {
100 9 100       34 next if $line !~ /^$head/;
101 1         7  
102             my ($type, $name, $remote) = $line =~ /(tag|branch) \s+ '([^']+)' \s+ of \s+ (.*?) $/xms;
103             # TODO calculate the remote's alias rather than assume that it is "origin"
104             return ($type, $type eq 'branch' ? "origin/$name" : $name);
105 8         46 }
106 8         667  
107 10 100       116 # not on a branch or commit
108             return ('sha', $head);
109 7         71 }
110              
111 7 50       129 my ($self, $name, $default) = @_;
112             my $value = $self->git->config($name);
113              
114             return $value || $default;
115 1         14 }
116              
117             my ($self, $type, $regex, %option) = @_;
118             $option{max_history} ||= 1;
119 88     88 1 163 $option{branches} = defined $option{branches} ? $option{branches} : 1;
120 88         163 my @commits = grep {/$regex/} $type eq 'tag' ? $self->tags() : $self->branches('both');
121              
122 88   66     468 my $oldest = @commits > $option{max_history} ? -$option{max_history} : -scalar @commits;
123             return map { $self->commit_details($_, branches => $option{branches}) } @commits[ $oldest .. -1 ];
124             }
125              
126 24     24 1 59 my ($self, $tag_or_branch, $local, $search) = @_;
127 24   100     69 my @things
128 24 100       48 = $tag_or_branch eq 'branch'
129 24 100       57 ? $self->branches($local ? 'local' : 'remote')
  60         237  
130             : $self->tags();
131 24 100       64 my ($release) = reverse grep {/$search/} @things;
132 24         49 chomp $release;
  24         54  
133              
134             return $release;
135             }
136 10     10 1 23  
137             my ($self, %option) = @_;
138 10 100       66 my ($type, $regex);
    100          
139             if ($option{tag}) {
140             $type = 'tag';
141 10         18 $regex = $option{tag};
  24         97  
142 10         19 }
143             elsif ($option{branch}) {
144 10         26 $type = 'branch';
145             $regex = $option{branch};
146             }
147             else {
148 24     24 1 64 my $prod = $self->config('workflow.prod') || ( $option{local} ? 'branch=^master$' : 'branch=^origin/master$' );
149 24         32 ($type, $regex) = split /\s*=\s*/, $prod;
150 24 100       64 if ( !$regex ) {
    100          
151 1         3 $type = 'branch';
152 1         3 $regex = '^origin/master$';
153             }
154             }
155 1         3  
156 1         2 my @releases = $self->match_commits($type, $regex, %option);
157             die "Could not find any historic releases for $type /$regex/!\n" if !@releases;
158             return @releases;
159 22   66     41 }
160 22         93  
161 22 100       49 my ($self, $name, %options) = @_;
162 1         3 my $split = "\1";
163 1         2 my $fmt = $options{user} ? "%ct$split%H$split%an$split%ae$split" : "%ct$split%H$split$split$split";
164             my ($time, $sha, $user, $email, $files)
165             = split /$split/, $self->git->log(
166             "--format=format:$fmt",
167 24         60 -1,
168 24 50       66 ($options{files} ? '--name-only' : ()),
169 24         60 $name
170             );
171              
172             return {
173 77     77 1 215 name => $name,
174 77         124 sha => $sha,
175 77 100       251 time => $time,
176             user => $user,
177             email => $email,
178             files => { map {$_ => 1} grep {$_} split "\n", $files || '' },
179             branches => $options{branches} ? { map { $_ => 1 } $self->branches('both', $sha) } : {},
180 77 100       294 };
181             }
182              
183             my ($self, $sha) = @_;
184             my $show = $self->git->show('--name-status', $sha);
185             $show =~ s/\A.*\n\n//xms;
186             my %files;
187             for my $file (split /\n/, $show) {
188             my ($state, $file) = split /\s+/, $file, 2;
189             $files{$file} = $state;
190 22         110 }
  22         62  
191 74 100 100     604  
  56         288  
192             return \%files;
193             }
194              
195             my ($self, $file) = @_;
196 1     1 1 3 open my $fh, '<', $file;
197 1         3  
198 1         5 return wantarray ? <$fh> : do { local $/; <$fh> };
199 1         2 }
200 1         4  
201 2         7 my ($self, $file, @out) = @_;
202 2         5 die "No file passed!" if !$file;
203             open my $fh, '>', $file;
204              
205 1         4 print $fh @out;
206             }
207              
208             my ($self) = @_;
209 2     2 1 5 return $self->{settings} if $self->{settings};
210 2         10  
211             my $key = $self->git->config('remote.origin.url');
212 2 100       2273 chomp $key if $key;
  1         4  
  1         59  
213             if ( !$key ) {
214             $key = $self->git->rev_parse("--show-toplevel");
215             chomp $key;
216 11     11 1 1847 }
217 11 50       28 $key = $self->_url_encode($key);
218 11         39  
219             $self->{settings_file} = "$self->{settings_dir}/$key";
220 11         5927  
221             $self->{settings}
222             = -f $self->{settings_file}
223             ? do $self->{settings_file}
224 4     4 1 8 : {};
225 4 50       19  
226             if ( $self->{settings}->{version} && version->new($self->{settings}->{version}) > $App::Git::Workflow::VERSION ) {
227 4         11 die "Current settings created with newer version than this program!\n";
228 4 50       11 }
229 4 50       10  
230 4         11 return $self->{settings};
231 4         11 }
232              
233 4         15 my ($self) = @_;
234             return if !$self->{settings_file};
235 4         17 local $Data::Dumper::Indent = 1;
236             local $Data::Dumper::Sortkeys = 1;
237             $self->{settings}->{version} = $App::Git::Workflow::VERSION;
238             $self->{settings}->{date} = time;
239             $self->spew($self->{settings_file}, 'my ' . Dumper $self->{settings});
240 4 100       545 }
241              
242 4 50 66     63 my ($self, $url) = @_;
243 0         0 $url =~ s/([^-\w.:])/sprintf "%%%x", ord $1/egxms;
244             return $url;
245             }
246 4         17  
247             my ($self) = @_;
248             $self->save_settings();
249             }
250 108     108 1 207  
251 108 100       543 1;
252 10         22  
253 10         20  
254 10         20 =head1 NAME
255 10         17  
256 10         39 App::Git::Workflow - Git workflow tools
257              
258             =head1 VERSION
259              
260 6     6   17 This documentation refers to App::Git::Workflow version 1.1.20
261 6         25  
  5         37  
262 6         20 =head1 SYNOPSIS
263              
264             use App::Git::Workflow qw/branches tags/;
265              
266 101     101   221 # Get all local branches
267 101         288 my @branches = $self->branches();
268             # or
269             @branches = $self->branches('local');
270              
271             # remote branches
272             @branches = $self->branches('remote');
273              
274             # both remote and local branches
275             @branches = $self->branches('both');
276              
277             # similarly for tags
278             my @tags = $self->tags();
279              
280             =head1 DESCRIPTION
281              
282             This module contains helper functions for the command line scripts.
283              
284             =head1 SUBROUTINES/METHODS
285              
286             =head2 C<new (%params)>
287              
288             Create a new C<App::Git::Workflow::Pom> object
289              
290             =head2 C<git ()>
291              
292             Get the git repository object
293              
294             =head2 C<branches ([ $type ])>
295              
296             Param: C<$type> - one of local, remote or both
297              
298             Returns a list of all branches of the specified type. (Default type is local)
299              
300             =head2 C<tags ()>
301              
302             Returns a list of all tags.
303              
304             =head2 C<_alphanum_sort ()>
305              
306             Does sorting (for the building C<sort>) in a alpha numerical fashion.
307             Specifically all numbers are converted for the comparison to 14 digit strings
308             with leading zeros.
309              
310             =head2 C<children ($dir)>
311              
312             Get the child files of C<$dir>
313              
314             =head2 C<config ($name, $default)>
315              
316             Get the git config value of C<$name>, or if not set C<$default>
317              
318             =head2 C<current ()>
319              
320             Get the current branch/tag or commit
321              
322             =head2 C<match_commits ($type, $regex, $max)>
323              
324             =head2 C<release ($tag_or_branch, $local, $search)>
325              
326             =head2 C<releases (%option)>
327              
328             =head2 C<commit_details ($name)>
329              
330             Get info from C<git show $name>
331              
332             =head2 C<files_from_sha ($sha)>
333              
334             Get the files changed by the commit
335              
336             =head2 C<slurp ($file)>
337              
338             Return the contents of C<$file>
339              
340             =head2 C<spew ( $file, @data )>
341              
342             Write C<@data> to the file C<$file>
343              
344             =head2 C<settings ()>
345              
346             Get the saved settings for the current repository
347              
348             =head2 C<save_settings ()>
349              
350             Save any changed settings for the current repository
351              
352             =head1 DIAGNOSTICS
353              
354             =head1 CONFIGURATION AND ENVIRONMENT
355              
356             =head1 DEPENDENCIES
357              
358             =head1 INCOMPATIBILITIES
359              
360             =head1 BUGS AND LIMITATIONS
361              
362             There are no known bugs in this module.
363              
364             Please report problems to Ivan Wills (ivan.wills@gmail.com).
365              
366             Patches are welcome.
367              
368             =head1 AUTHOR
369              
370             Ivan Wills - (ivan.wills@gmail.com)
371              
372             =head1 LICENSE AND COPYRIGHT
373              
374             Copyright (c) 2014 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077).
375             All rights reserved.
376              
377             This module is free software; you can redistribute it and/or modify it under
378             the same terms as Perl itself. See L<perlartistic>. This program is
379             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
380             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
381             PARTICULAR PURPOSE.
382              
383             =cut