File Coverage

blib/lib/Git/Flux/Utils.pm
Criterion Covered Total %
statement 12 171 7.0
branch 0 42 0.0
condition 0 14 0.0
subroutine 4 33 12.1
pod 28 28 100.0
total 44 288 15.2


line stmt bran cond sub pod time code
1             package Git::Flux::Utils;
2              
3 5     5   4915 use strict;
  5         13  
  5         173  
4 5     5   26 use warnings;
  5         8  
  5         198  
5 5     5   4292 use mixin::with 'Git::Flux';
  5         1507  
  5         30  
6 5     5   8047 use List::MoreUtils 'all';
  5         8029  
  5         12872  
7              
8             sub git_local_branches {
9 0     0 1   my $self = shift;
10 0           my $repo = $self->{'repo'};
11 0           my @branches = map { $_ =~ s/^\*?\s+//; $_; }
  0            
  0            
12             $repo->run( branch => '--no-color' );
13              
14 0           return @branches;
15             }
16              
17             sub git_remote_branches {
18 0     0 1   my $self = shift;
19 0           my $repo = $self->{'repo'};
20 0           my @branches = map { $_ =~ s/^\*?\s+//; $_; }
  0            
  0            
21             $repo->run( branch => '-r', '--no-color' );
22              
23 0           return @branches;
24             }
25              
26             sub git_all_branches {
27 0     0 1   my $self = shift;
28 0           my $repo = $self->{'repo'};
29 0           my @branches = ( $self->git_local_branches, $self->git_remote_branches );
30              
31 0           return @branches;
32             }
33              
34             sub git_all_tags {
35 0     0 1   my $self = shift;
36 0           my $repo = $self->{'repo'};
37 0           my @tags = $repo->run('tag');
38              
39 0           return @tags;
40             }
41              
42             sub git_current_branch {
43 0     0 1   my $self = shift;
44 0           my $repo = $self->{'repo'};
45              
46 0           my ($branch) = map { $_ =~ s/^\*\s+//g; $_; }
  0            
  0            
47 0           grep { $_ !~ /no branch/ }
48 0           grep { $_ =~ /^\*\s/ }
49             $repo->run( branch => '--no-color' );
50              
51 0           return $branch;
52             }
53              
54             sub git_is_clean_working_tree {
55 0     0 1   my $self = shift;
56 0           my $repo = $self->{'repo'};
57              
58 0           my $cmd = $repo->command(
59             diff => qw/ --no-ext-diff --ignore-submodules --quiet --exit-code /
60             );
61 0           $cmd->close;
62 0           my $diff = $cmd->exit;
63              
64 0           $cmd = $repo->command(
65             'diff-index' => qw/ --cached --quiet --ignore-submodules HEAD -- /
66             );
67 0           $cmd->close;
68 0           my $diff_index = $cmd->exit;
69              
70 0 0         $diff and return 1;
71 0 0         $diff_index and return 2;
72              
73 0           return 0;
74             }
75              
76             sub git_repo_is_headless {
77 0     0 1   my $self = shift;
78 0           my $repo = $self->{'repo'};
79 0           my $result = $repo->run( 'rev-parse' => qw/ --quiet --verify HEAD / );
80              
81 0           return not $result;
82             }
83              
84             sub git_local_branch_exists {
85 0     0 1   my $self = shift;
86 0           my $branch = shift;
87 0           my @branches = $self->git_local_branches;
88              
89 0           grep { $_ eq $branch } @branches;
  0            
90             }
91              
92             sub git_branch_exists {
93 0     0 1   my $self = shift;
94 0           my $branch = shift;
95 0           my @branches = $self->git_all_branches;
96              
97 0           grep { $_ eq $branch } @branches;
  0            
98             }
99              
100             sub git_tag_exists {
101 0     0 1   my $self = shift;
102 0           my $tag = shift;
103 0           my @tags = $self->git_all_tags;
104              
105 0           grep { $_ eq $tag } @tags;
  0            
106             }
107              
108             # Tests whether branches and their "origin" counterparts have diverged and need
109             # merging first. It returns error codes to provide more detail, like so:
110             #
111             # 0 Branch heads point to the same commit
112             # 1 First given branch needs fast-forwarding
113             # 2 Second given branch needs fast-forwarding
114             # 3 Branch needs a real merge
115             # 4 There is no merge base, i.e. the branches have no common ancestors
116             sub git_compare_branches {
117 0     0 1   my $self = shift;
118 0           my ( $c1, $c2 ) = @_;
119 0           my $repo = $self->{'repo'};
120              
121 0           my $commit1 = $repo->run( 'rev-parse' => $c1 );
122 0           my $commit2 = $repo->run( 'rev-parse' => $c2 );
123              
124 0 0         if ( $commit1 != $commit2 ) {
125 0           my $cmd = $repo->command( 'merge-base' => $c1, $c2 );
126 0           $cmd->close;
127              
128 0 0         $cmd->exit == 0 and return 4;
129 0 0         $commit1 eq $cmd->stdout and return 1;
130 0 0         $commit2 eq $cmd->stdout and return 2;
131              
132 0           return 3;
133             } else {
134 0           return 0;
135             }
136             }
137              
138             # Checks whether branch $1 is succesfully merged into $2
139             sub git_is_branch_merged_into {
140 0     0 1   my $self = shift;
141 0           my ( $subject, $base ) = @_;
142 0           my $repo = $self->{'repo'};
143              
144 0           my @all_merges = map { $_ =~ s/^\*?\s+//; $_; }
  0            
  0            
145             $repo->run(
146             branch => qw/ --no-color --contains /, $subject
147             );
148              
149 0           return grep { $_ eq $base } @all_merges;
  0            
150             }
151              
152             sub gitflux_has_master_configured {
153 0     0 1   my $self = shift;
154 0           my $repo = $self->{'repo'};
155 0           my $master = $repo->run( config => qw/ --get gitflux.branch.master / );
156              
157 0   0       return ( defined $master and $master ne '' ) &&
158             $self->git_local_branch_exists($master);
159             }
160              
161             sub gitflux_has_devel_configured {
162 0     0 1   my $self = shift;
163 0           my $repo = $self->{'repo'};
164 0           my $devel = $repo->run( config => qw/ --get gitflux.branch.devel / );
165              
166 0   0       return ( defined $devel and $devel ne '' ) &&
167             $self->git_local_branch_exists($devel);
168             }
169              
170             sub gitflux_has_prefixes_configured {
171 0     0 1   my $self = shift;
172 0           my $repo = $self->{'repo'};
173 0           my @results = map {
174 0           $repo->run( config => '--get', "gitflux.prefix.$_" );
175             } qw/ feature release hotfix support versiontag /;
176              
177 0 0   0     return all { defined $_ and $_ } @results;
  0            
178             }
179              
180             sub gitflux_is_initialized {
181 0     0 1   my $self = shift;
182 0           my $repo = $self->{'repo'};
183              
184 0   0       return $self->gitflux_has_master_configured &&
185             $self->gitflux_has_devel_configured &&
186             (
187             $repo->run( config => qw/ --get gitflux.branch.master / ) ne
188             $repo->run( config => qw/ --get gitflux.branch.devel / )
189             ) &&
190             $self->gitflux_has_prefixes_configured;
191             }
192              
193             sub gitflux_load_settings {
194 0     0 1   my $self = shift;
195 0           my $repo = $self->{'repo'};
196              
197 0           $self->{'dit_git_dir'} = $repo->run( 'rev-parse' => '--git-dir' );
198              
199 0           $self->{'master_branch'} = $repo->run(
200             config => qw/ --get gitflux.branch.master /
201             );
202              
203 0           $self->{'devel_branch'} = $repo->run(
204             config => qw/ --get gitflux.branch.devel /
205             );
206              
207 0   0       $self->{'origin_branch'} = $repo->run(
208             config => qw/ --get gitflux.origin /
209             ) || 'origin';
210             }
211              
212             # Inputs:
213             # $1 = name prefix to resolve
214             # $2 = branch prefix to use
215             #
216             # Searches branch names from git_local_branches() to look for a unique
217             # branch name whose name starts with the given name prefix.
218             #
219             # There are multiple exit codes possible:
220             # 0: The unambiguous full name of the branch is written to stdout
221             # (success)
222             # 1: No match is found.
223             # 2: Multiple matches found. These matches are written to stderr
224             sub gitflux_resolve_nameprefix {
225 0     0 1   my $self = shift;
226 0           my ( $name, $prefix ) = @_;
227 0           my $repo = $self->{'repo'};
228              
229             # check for perfect match
230 0 0         if ( $self->git_local_branch_exists( $prefix . $name ) ) {
231 0           print "$name\n";
232 0           return 0;
233             }
234              
235 0           my @matches = grep { $_ =~ /^$prefix$name/ }
  0            
236             $self->git_local_branches;
237              
238              
239 0 0         if ( @matches == 0 ){
240 0           warn "No branch matches prefix '$name'\n";
241 0           return 1;
242             } else {
243 0 0         if ( @matches == 1 ) {
244 0           my $match = shift @matches;
245 0           $match =~ s/^\Q$prefix\E//;
246 0           warn "$match\n";
247              
248 0           return 0;
249             } else {
250 0           warn "Multiple branches match prefix '$name':\n";
251 0           warn "- $_\n" for @matches;
252              
253 0           return 2;
254             }
255             }
256             }
257              
258             sub require_git_repo {
259 0     0 1   Git::Repository->new;
260             }
261              
262             sub require_gitflux_initialized {
263 0     0 1   my $self = shift;
264              
265 0 0         $self->gitflux_is_initialized
266             or die 'fatal: Not a gitflux-enabled repo yet. ' .
267             qq{Please run "git flux init" first.\n};
268             }
269              
270             sub require_clean_working_tree {
271 0     0 1   my $self = shift;
272 0           my $result = $self->git_is_clean_working_tree;
273              
274 0 0         $result eq 1
275             and die "fatal: Working tree contains unstaged changes. Aborting.\n";
276              
277 0 0         $result eq 2
278             and die "fatal: Index contains uncommitted changes. Aborting.\n";
279             }
280              
281             sub require_local_branch {
282 0     0 1   my $self = shift;
283 0           my $br = shift;
284              
285 0 0         $self->git_local_branch_exists($br)
286             or die "fatal: Local branch '$br' does not exist and is required.\n";
287             }
288              
289             sub require_remote_branch {
290 0     0 1   my $self = shift;
291 0           my $br = shift;
292              
293 0 0         grep { $_ eq $br } $self->git_remote_branches
  0            
294             or die "Remote branch '$br' does not exist and is required.\n";
295             }
296              
297             sub require_branch {
298 0     0 1   my $self = shift;
299 0           my $br = shift;
300              
301 0 0         grep { $_ eq $br } $self->git_all_branches
  0            
302             or die "Branch '$br' does not exist and is required.\n";
303             }
304              
305             sub require_branch_absent {
306 0     0 1   my $self = shift;
307 0           my $branch = shift;
308 0           my $repo = $self->{'repo'};
309              
310 0 0         grep { $_ eq $branch } $self->git_all_branches
  0            
311             or die "Branch '$branch' already exists. Pick another name.\n";
312             }
313              
314             sub require_tag_absent {
315 0     0 1   my $self = shift;
316 0           my $tag = shift;
317              
318 0 0         grep { $_ eq $tag } $self->git_all_tags
  0            
319             or die "Tag '$tag' already exists. Pick another name.\n";
320             }
321              
322             sub require_branches_equal {
323 0     0 1   my $self = shift;
324 0           my ( $br1, $br2 ) = @_;
325 0           my $repo = $self->{'repo'};
326              
327 0           my $status = $self->git_compare_branches( $br1, $br2 );
328              
329 0 0         if ( $status > 0 ) {
330 0           warn "Branches '$br1' and '$br2' have diverged.\n";
331              
332 0 0         if ( $status == 1 ) {
    0          
333 0           die "And branch '$br1' may be fast-forwarded.\n";
334             } elsif ( $status == 2 ) {
335 0           warn "And local branch '$br1' is ahead of '$br2'.\n";
336             } else {
337 0           die "Branches need merging first.\n";
338             }
339             }
340             }
341              
342             sub is_interactive {
343 0   0 0 1   return -t STDIN && -t STDOUT;
344             }
345              
346             1;
347              
348             __END__