File Coverage

blib/lib/Git/Archive.pm
Criterion Covered Total %
statement 99 114 86.8
branch 41 52 78.8
condition 10 12 83.3
subroutine 9 10 90.0
pod 1 1 100.0
total 160 189 84.6


line stmt bran cond sub pod time code
1             package Git::Archive;
2              
3 1     1   52004 use strict;
  1         2  
  1         39  
4 1     1   13 use v5.10.0;
  1         4  
  1         81  
5             our $VERSION = '0.10';
6 1     1   1517 use IPC::Cmd qw/can_run/;
  1         92513  
  1         65  
7              
8 1     1   1019 use Git::Repository;
  1         13369  
  1         8  
9              
10             sub commit {
11 16     16 1 1759753 my $self = shift;
12             # Get passed-in arguments correctly into hash
13 16 50       307 my $args = ref $_[0] eq 'HASH' ? shift @_ : {@_};
14              
15             # Check for mandatory args
16             ## First, make sure we have an error sub defined
17             my $error = $args->{error}
18             || sub {
19 0     0   0 my ($args, $error) = @_;
20 0         0 print STDERR "[ERROR] $error\n";
21 0         0 return 1;
22 16   50     276 };
23              
24             ## Now throw errors if necessary
25 16 100       172 unless ( $args->{msg} ) {
26 1         7 return $error->( $args,'No commit message supplied');
27             }
28 15 100 100     201 unless ( $args->{files} || $args->{all_tracked} || $args->{all_dirty} ) {
      100        
29 1         17 return $error->( $args,'No files specified to commit');
30             }
31 14 50       277 unless ( can_run( 'git' ) ) {
32 0         0 return $error->( $args,'Git does not appear to be installed');
33             }
34              
35             # Seems all is well with args. Check if the environment is sane
36             ## Is the current or passed-in directory a git repo?
37 14         7987 my $repo = $self->_get_repo( $args );
38 14 100       275 unless ( $repo ) { return $error->( $args, $args->{error} ) }
  2         8  
39              
40             ## Are there files already staged?
41 12 100       133 if ( $repo->run( qw/diff --cached --name-only/ ) ) {
42 1         22625 return $error->( $args,'Repo already has staged files');
43             }
44              
45             ## Populate name & email if not already done
46 11 50       245538 unless ( $repo->run( 'config', 'user.email' ) ) {
47 0         0 system( $repo->run( 'config', 'user.email', '"git.user@example.com"' ) );
48             }
49 11 50       238915 unless ( $repo->run( 'config', 'user.name' ) ) {
50 0         0 system( $repo->run( 'config', 'user.name', '"Automated Commit"' ) );
51             }
52              
53             # Looks like we're good to go. Let's commit!
54 11   100     244399 my $files = $self->_commit( $args, $repo ) || [];
55 11 100       58 unless ( @{$files} ) {
  11         49  
56 1         11 return $error->( $args, $args->{error} );
57             }
58             # We've got a new commit. Do we need to worry about a remote?
59 10         25 my $do_remote;
60 10 100       257 $do_remote = $self->_handle_remote( $args, $repo ) if $args->{use_remote};
61 10 100       134 return $error->( $args, $args->{error} ) if $do_remote;;
62              
63             # Looks like we made it! Run the success sub if appropriate
64 9 50       34 $args->{success}->( $args ) if $args->{success};
65              
66 9         551 return 0;
67             }
68              
69             sub _filenames {
70 9     9   28 my ( $self, $args ) = @_;
71              
72 9 100       60 unless ( ref $args->{files} eq 'ARRAY' ) {
73 5         30 my $files = $args->{files};
74 5         59 $files =~ s/\s+/ /;
75 5         34 return [ split ' ', $files ];
76             }
77 4         25 return $args->{files};
78             }
79              
80             sub _get_repo {
81 14     14   48 my ($self, $args) = @_;
82 14   50     66 $args->{git_dir} ||= './';
83 14 100       226 unless (-e $args->{git_dir}) {
84 1         7 $args->{error} = "Unable to initialise git directory:\nNo such directory";
85 1         3 return;
86             }
87 13 100       265 unless (-e $args->{git_dir}.'/.git') {
88 1         9 $args->{error} = "Unable to initialise git directory:\nNo .git found";
89 1         4 return;
90             }
91 12         28 my $repo;
92 12         36 my $options = {};
93 12 50       87 if ( my $sudo = $args->{sudo} ) {
94 0         0 $options = { git => [ 'sudo', '-u', $args->{sudo}, 'git' ] };
95             }
96 12         62 eval { $repo = Git::Repository->new( work_tree => $args->{git_dir}, $options ); };
  12         232  
97 12 50       865694 unless ($@) {
98 12         218 return $repo;
99             }
100 0         0 $args->{error} = "Unable to initialise git directory:\n" . $@;
101 0         0 return;
102             }
103              
104             sub _commit {
105 11     11   225 my ($self, $args, $repo) = @_;
106              
107 11         30 my $files; # To record what files we intend to commit
108              
109 11 100       118 if ( $args->{files} ) {
    100          
    50          
110             ## We have a list of specified files to commit
111 9         189 $files = $self->_filenames( $args );
112 9         51 eval { $repo->run( 'add', @{$files} ); };
  9         18  
  9         61  
113             ## Do we need to make sure all the files had changes to stage?
114 9 100       273455 if ( $args->{check_all_staged} ) {
115 2         132 my @staged = $repo->run( qw/diff --cached --name-only/ );
116 2 100       48856 unless ( @staged == @{$files} ) {
  2         20  
117             # Numerical equality is Good Enough for now
118 1         12 $repo->run( reset => 'HEAD' ); # Unstage the files, it's all gone wrong!
119 1         20544 $args->{error} = 'Some files not staged when "check_all_staged" specified';
120 1         33 return;
121             }
122             }
123             # Files staged and ready to go. Commit time
124 8         116 $repo->run( commit => '-m "' . $args->{msg} . '"' );
125             }
126             elsif ( $args->{all_tracked} ) {
127             ## We want to commit any modified tracked files
128 1         53 my @status = $repo->run( status => '-s' );
129 1         46559 my @staged;
130 1 50       79 unless ( @staged = grep { $_ !~ m/^\?\?/ } @status ) {
  1         15  
131 0         0 $args->{error} = 'No modified files to commit';
132 0         0 return;
133             }
134 1         3 $files = [map { $_ =~ s/^\s*\S+\s+(\S+)/$1/ } @staged];
  1         30  
135 1         15 $repo->run( commit => '-a', '-m "' . $args->{msg} . '"' );
136             }
137             elsif ( $args->{all_dirty} ) {
138             ## We want to commit all files in their current state
139 1         14 my @status = $repo->run( status => '-s' );
140 1 50       38912 unless ( @status ) {
141 0         0 $args->{error} = 'No modified files to commit';
142 0         0 return;
143             }
144 1         9 $files = [map { $_ =~ s/^\s*\S+\s+(\S+)/$1/; $_ } @status];
  1         18  
  1         9  
145 1         6 eval { $repo->run( 'add', @{$files} ); };
  1         2  
  1         11  
146 1         23517 $repo->run( commit => '-m "' . $args->{msg} . '"' );
147             }
148              
149 10         335082 return $files;
150             }
151              
152             sub _handle_remote {
153 2     2   6 my ($self, $args, $repo) = @_;
154             # We have a commit. Hopefully, the remote repo has nothing we don't.
155             # But since it may well have, we need to:
156             # Pull, and hope it doesn't fail
157             # Then push, and hope it doesn't fail
158 2         10 my $remote = $args->{use_remote};
159 2         18 my $pull = $repo->run( pull => $remote );
160 2 100       387933 if ( $pull =~ /Automatic merge failed/ ) {
161             # Damn, the pull didn't work.
162             # Quick, pretend it never happened!
163 1         15 $repo->run( merge => '--abort' );
164             # Actually, we should probably 'fess up
165 1         19529 $args->{error} = 'Unable to push to remote: Cannot pull';
166 1         25 return 1;
167             }
168             # Ok, we managed a pull. Hopefully we can now push
169 1         19 my $push = $repo->run( push => $remote );
170 1 50       106823 if ( $push =~ /\[rejected\]/ ) {
171             # We failed. Maybe somebody managed to push?
172             # (in the tiny amount of time they had to work with)
173             # Possibly we should try to push multiple times, but CBA -
174             # "fail once, shout for help" seems far saner.
175 0         0 $args->{error} = 'Unable to push to remote: Rejected';
176 0         0 return 1;
177             }
178 1         21 return;
179             }
180              
181             1;
182              
183             __END__