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 24     24   225399 use warnings;
  24         106  
  24         627  
10 24     24   102 use version;
  24         40  
  24         548  
11 24     24   1219 use autodie;
  24         5076  
  24         131  
12 24     24   11126 use Carp qw/carp croak cluck confess longmess/;
  24         285797  
  24         112  
13 24     24   138177 use Data::Dumper qw/Dumper/;
  24         63  
  24         1749  
14 24     24   9628 use English qw/ -no_match_vars /;
  24         88574  
  24         1444  
15 24     24   1776 use App::Git::Workflow::Repository qw//;
  24         6152  
  24         167  
16 24     24   15592 use base qw/Exporter/;
  24         59  
  24         570  
17 24     24   133  
  24         58  
  24         2465  
18             our $VERSION = version->new(1.1.19);
19              
20             no warnings qw/once/;
21             my $A = $a;
22 24     24   144 $A =~ s/(\d+)/sprintf "%014i", $1/egxms;
  24         46  
  24         50919  
23 177     177   286 my $B = $b;
24 177         358 $B =~ s/(\d+)/sprintf "%014i", $1/egxms;
  95         277  
25 177         240  
26 177         357 return $A cmp $B;
  125         471  
27             }
28 177         580  
29             my $class = shift;
30             my %param = @_;
31             my $self = \%param;
32 144     144 1 438  
33 144         381 bless $self, $class;
34 144         274  
35             $self->{TEST} = 0;
36 144         260 $self->{VERBOSE} = 0;
37             $self->{GIT_DIR} = '.git';
38 144         518 $self->{branches} = undef;
39 144         287 $self->{tags} = undef;
40 144         311 $self->{settings_dir} = ($ENV{HOME} || "/tmp/") . '/.git-workflow';
41 144         283 mkdir $self->{settings_dir} if !-d $self->{settings_dir};
42 144         285  
43 144   100     628 return $self;
44 144 100       3273 }
45              
46 144         7078  
47             my ($self, $type, $contains) = @_;
48             $type ||= 'local';
49 494 50   494 1 3341 my @options
50             = $type eq 'local' ? ()
51             : $type eq 'remote' ? ('-r')
52 121     121 1 279 : $type eq 'both' ? ('-a')
53 121   100     269 : confess "Unknown type '$type'!\n";
54              
55 121 100       574 if ($contains) {
    100          
    100          
56             push @options, "--contains", $contains;
57             $type .= $contains;
58             }
59              
60 120 100       236 # assign to or cache
61 46         77 $self->{branches}{$type} = [
62 46         81 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         1313 return @{ $self->{branches}{$type} };
69 222         547 }
70              
71 120 100       548 my ($self) = @_;
72             # assign to or cache
73 119         217 $self->{tags} = [
  119         363  
74             sort _alphanum_sort
75             #map { /^(.*?)\s*$/xms }
76             $self->git->tag
77 4     4 1 13 ] if !$self->{tags} || !@{ $self->{tags} };
78              
79             return @{ $self->{tags} };
80             }
81              
82             my ($self) = @_;
83 4 50 66     24 # get the git directory
  2         7  
84             my $git_dir = $self->git->rev_parse("--show-toplevel");
85 4         7 chomp $git_dir;
  4         16  
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 20 my $head = <$fh>;
90             close $fh;
91 9         20 chomp $head;
92 9         20  
93             if ($head =~ m{ref: refs/heads/(.*)$}) {
94             return ('branch', $1);
95 9         61 }
96 9         7952  
97 9         63 # try to identify the commit as it's not a local branch
98 9         2726 open $fh, '<', "$git_dir/$self->{GIT_DIR}/FETCH_HEAD";
99             while (my $line = <$fh>) {
100 9 100       40 next if $line !~ /^$head/;
101 1         6  
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         56 }
106 8         749  
107 10 100       118 # not on a branch or commit
108             return ('sha', $head);
109 7         89 }
110              
111 7 50       160 my ($self, $name, $default) = @_;
112             my $value = $self->git->config($name);
113              
114             return $value || $default;
115 1         13 }
116              
117             my ($self, $type, $regex, %option) = @_;
118             $option{max_history} ||= 1;
119 88     88 1 192 $option{branches} = defined $option{branches} ? $option{branches} : 1;
120 88         186 my @commits = grep {/$regex/} $type eq 'tag' ? $self->tags() : $self->branches('both');
121              
122 88   66     532 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 79 my ($self, $tag_or_branch, $local, $search) = @_;
127 24   100     104 my @things
128 24 100       67 = $tag_or_branch eq 'branch'
129 24 100       95 ? $self->branches($local ? 'local' : 'remote')
  60         257  
130             : $self->tags();
131 24 100       89 my ($release) = reverse grep {/$search/} @things;
132 24         72 chomp $release;
  24         72  
133              
134             return $release;
135             }
136 10     10 1 20  
137             my ($self, %option) = @_;
138 10 100       45 my ($type, $regex);
    100          
139             if ($option{tag}) {
140             $type = 'tag';
141 10         21 $regex = $option{tag};
  24         112  
142 10         17 }
143             elsif ($option{branch}) {
144 10         26 $type = 'branch';
145             $regex = $option{branch};
146             }
147             else {
148 24     24 1 80 my $prod = $self->config('workflow.prod') || ( $option{local} ? 'branch=^master$' : 'branch=^origin/master$' );
149 24         45 ($type, $regex) = split /\s*=\s*/, $prod;
150 24 100       77 if ( !$regex ) {
    100          
151 1         2 $type = 'branch';
152 1         2 $regex = '^origin/master$';
153             }
154             }
155 1         2  
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     60 }
160 22         112  
161 22 100       61 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         93 -1,
168 24 50       66 ($options{files} ? '--name-only' : ()),
169 24         78 $name
170             );
171              
172             return {
173 77     77 1 240 name => $name,
174 77         128 sha => $sha,
175 77 100       280 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       318 };
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         81 }
  22         38  
191 74 100 100     763  
  56         306  
192             return \%files;
193             }
194              
195             my ($self, $file) = @_;
196 1     1 1 3 open my $fh, '<', $file;
197 1         3  
198 1         4 return wantarray ? <$fh> : do { local $/; <$fh> };
199 1         3 }
200 1         4  
201 2         7 my ($self, $file, @out) = @_;
202 2         4 die "No file passed!" if !$file;
203             open my $fh, '>', $file;
204              
205 1         3 print $fh @out;
206             }
207              
208             my ($self) = @_;
209 2     2 1 7 return $self->{settings} if $self->{settings};
210 2         12  
211             my $key = $self->git->config('remote.origin.url');
212 2 100       2481 chomp $key if $key;
  1         4  
  1         68  
213             if ( !$key ) {
214             $key = $self->git->rev_parse("--show-toplevel");
215             chomp $key;
216 11     11 1 1684 }
217 11 50       27 $key = $self->_url_encode($key);
218 11         38  
219             $self->{settings_file} = "$self->{settings_dir}/$key";
220 11         12970  
221             $self->{settings}
222             = -f $self->{settings_file}
223             ? do $self->{settings_file}
224 4     4 1 9 : {};
225 4 50       12  
226             if ( $self->{settings}->{version} && version->new($self->{settings}->{version}) > $App::Git::Workflow::VERSION ) {
227 4         9 die "Current settings created with newer version than this program!\n";
228 4 50       19 }
229 4 50       11  
230 4         9 return $self->{settings};
231 4         8 }
232              
233 4         15 my ($self) = @_;
234             return if !$self->{settings_file};
235 4         16 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       508 }
241              
242 4 50 66     51 my ($self, $url) = @_;
243 0         0 $url =~ s/([^-\w.:])/sprintf "%%%x", ord $1/egxms;
244             return $url;
245             }
246 4         22  
247             my ($self) = @_;
248             $self->save_settings();
249             }
250 108     108 1 198  
251 108 100       600 1;
252 10         23  
253 10         53  
254 10         19 =head1 NAME
255 10         22  
256 10         32 App::Git::Workflow - Git workflow tools
257              
258             =head1 VERSION
259              
260 6     6   14 This documentation refers to App::Git::Workflow version 1.1.19
261 6         25  
  5         37  
262 6         22 =head1 SYNOPSIS
263              
264             use App::Git::Workflow qw/branches tags/;
265              
266 101     101   242 # Get all local branches
267 101         437 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