File Coverage

blib/lib/Git/Repository/Command.pm
Criterion Covered Total %
statement 91 91 100.0
branch 56 58 96.5
condition 25 32 78.1
subroutine 19 19 100.0
pod 2 2 100.0
total 193 202 95.5


line stmt bran cond sub pod time code
1             package Git::Repository::Command;
2             $Git::Repository::Command::VERSION = '1.325';
3 16     16   122 use strict;
  16         45  
  16         498  
4 16     16   84 use warnings;
  16         37  
  16         442  
5 16     16   303 use 5.006;
  16         78  
6              
7 16     16   146 use Carp;
  16         32  
  16         992  
8 16     16   115 use Cwd qw( cwd );
  16         36  
  16         703  
9 16     16   10156 use IO::Handle;
  16         111443  
  16         1095  
10 16     16   151 use Scalar::Util qw( blessed );
  16         39  
  16         1347  
11 16     16   119 use File::Spec;
  16         42  
  16         361  
12 16     16   10101 use System::Command;
  16         245170  
  16         98  
13              
14             our @ISA = qw( System::Command );
15              
16             # a few simple accessors
17             for my $attr (qw( pid stdin stdout stderr exit signal core )) {
18 16     16   1257 no strict 'refs';
  16         39  
  16         1247  
19 2850     2850   1158003 *$attr = sub { return $_[0]{$attr} };
20             }
21             for my $attr (qw( cmdline )) {
22 16     16   108 no strict 'refs';
  16         36  
  16         21861  
23 2     2   65 *$attr = sub { return @{ $_[0]{$attr} } };
  2         115  
24             }
25              
26             sub _which {
27 38     38   472 my @pathext = ('');
28             push @pathext,
29             $^O eq 'MSWin32' ? split ';', $ENV{PATHEXT}
30 38 50       1100 : $^O eq 'cygwin' ? qw( .com .exe .bat )
    50          
31             : ();
32              
33 38         1448 for my $path ( File::Spec->path ) {
34 190         646 for my $ext (@pathext) {
35 190         2699 my $binary = File::Spec->catfile( $path, $_[0] . $ext );
36 190 100 100     5016 return $binary if -x $binary && !-d _;
37             }
38             }
39              
40             # not found
41 10         82 return undef;
42             }
43              
44             # CAN I HAS GIT?
45             my %binary; # cache calls to _is_git
46             sub _is_git {
47 377     377   41907 my ( $binary, @args ) = @_;
48 377         1767 my $args = join "\0", @args;
49              
50             # git option might be an arrayref containing an executable with arguments
51             # Best that can be done is to check if the first part is executable
52             # and use the arguments as part of the cache key
53              
54             # compute cache key:
55             # - filename (path-rel): $CWD \0 $PATH
56             # - filename (path): $PATH
57             # - absolute path (abs): empty string
58             # - relative path (rel): dirname
59 377 100 66     5992 my $path = defined $ENV{PATH} && length( $ENV{PATH} ) ? $ENV{PATH} : '';
60 377 100       1083719 my ( $type, $key ) =
    100          
    100          
61             ( File::Spec->splitpath($binary) )[2] eq $binary
62             ? grep( !File::Spec->file_name_is_absolute($_), File::Spec->path )
63             ? ( 'path-rel', join "\0", cwd(), $path )
64             : ( 'path', $path )
65             : File::Spec->file_name_is_absolute($binary) ? ( 'abs', '' )
66             : ( 'rel', cwd() );
67              
68             # This relatively complex cache key scheme allows PATH or cwd to change
69             # during the life of a program using Git::Repository, which is likely
70             # to happen. On the other hand, it completely ignores the possibility
71             # that any part of the cached path to a git binary could be a symlink
72             # which target may also change during the life of the program.
73              
74             # check the cache
75             return $binary{$type}{$key}{$binary}{$args}
76 377 100       22188 if exists $binary{$type}{$key}{$binary}{$args};
77              
78             # compute a list of candidate files (look in PATH if needed)
79 49 100       2364 my $git = $type =~ /^path/
80             ? _which($binary)
81             : File::Spec->rel2abs($binary);
82 49 100 100     2047 $git = File::Spec->rel2abs($git)
83             if defined $git && $type eq 'path-rel';
84              
85             # if we can't find any, we're done
86 49 100 100     1806 return $binary{$type}{$key}{$binary} = undef
87             if !( defined $git && -x $git );
88              
89             # try to run it
90 35         1123 my $cmd = System::Command->new( $git, @args, '--version' );
91 35   50     298612 my $version = do { local $/ = "\n"; $cmd->stdout->getline; } || '';
92 35         28923 $cmd->close;
93              
94             # does it really look like git?
95 35 100       11999 return $binary{$type}{$key}{$binary}{$args}
    100          
96             = $version =~ /^git version \d/
97             ? $type eq 'path'
98             ? $binary # leave the shell figure it out itself too
99             : $git
100             : undef;
101             }
102              
103             sub new {
104 359     359 1 1829 my ( $class, @cmd ) = @_;
105              
106             # split the args
107 359         1017 my (@r, @o);
108 359 100 100     8602 @cmd = # take out the first Git::Repository in $r, and options in @o
    100          
109             grep !( blessed $_ && $_->isa('Git::Repository') ? push @r, $_ : 0 ),
110             grep !( ref eq 'HASH' ? push @o, $_ : 0 ),
111             @cmd;
112              
113             # wouldn't know what to do with more than one Git::Repository object
114 359 100       1896 croak "Too many Git::Repository objects given: @r" if @r > 1;
115 358         957 my $r = shift @r;
116              
117             # keep changes to the environment local
118 358         35916 local %ENV = %ENV;
119              
120             # a Git::Repository object will give more context
121 358 100       2523 if ($r) {
122              
123             # pick up repository options
124 141         1840 unshift @o, $r->options;
125              
126             # get some useful paths
127 141         1446 my ( $git_dir, $work_tree ) = ( $r->git_dir, $r->work_tree );
128 141 100 66     1545 unshift @o, { cwd => $work_tree }
129             if defined $work_tree && length $work_tree;
130              
131             # setup our %ENV
132 141         2074 delete @ENV{qw( GIT_DIR GIT_WORK_TREE )};
133 141         859 $ENV{GIT_DIR} = $git_dir;
134 141 100       852 $ENV{GIT_WORK_TREE} = $work_tree
135             if defined $work_tree;
136             }
137              
138             # pick up the modified PATH, if any
139             exists $_->{env} and exists $_->{env}{PATH} and $ENV{PATH} = $_->{env}{PATH}
140 358   66     2552 for @o;
      33        
141              
142             # extract and process the 'fatal' option
143             push @o, {
144             fatal => {
145             128 => 1, # fatal
146             129 => 1, # usage
147             map s/^-// ? ( $_ => '' ) : ( $_ => 1 ),
148             map /^!0$/ ? ( 1 .. 255 ) : $_,
149             map ref() ? @$_ : $_, grep defined, map $_->{fatal}, @o
150 358 100       8446 }
    100          
    100          
151             };
152              
153             # get and check the git command
154 358 100       1772 my $git_cmd = ( map { exists $_->{git} ? $_->{git} : () } @o )[-1];
  778         2440  
155              
156             # git option might be an arrayref containing an executable with arguments
157             # (e.g. [ qw( /usr/bin/sudo -u nobody git ) ] )
158 358 100       1874 ( $git_cmd, my @args )
    100          
159             = defined $git_cmd ? ref $git_cmd ? @$git_cmd : ($git_cmd) : ('git');
160 358         1955 my $git = _is_git($git_cmd, @args);
161              
162 358 100       8993 croak sprintf "git binary '%s' not available or broken",
163             join( ' ', $git_cmd, @args ) # show the full command given
164             if !defined $git;
165              
166             # turn us into a dumb terminal
167 351         7676 delete $ENV{TERM};
168              
169             # spawn the command and re-bless the object in our class
170 351         11116 return bless System::Command->new( $git, @args, @cmd, @o ), $class;
171             }
172              
173             sub final_output {
174 346     346 1 2003 my ($self, @cb) = @_;
175              
176             # get output / errput
177 346         1199 my ( @output, @errput );
178             $self->loop_on(
179             input_record_separator => "\n",
180 300     300   15296 stdout => sub { chomp( my $o = shift ); push @output, $o; },
  300         2255  
181 117     117   2363 stderr => sub { chomp( my $e = shift ); push @errput, $e; },
  117         407  
182 346         14577 );
183              
184             # done with it
185 346         3598 $self->close;
186              
187             # fatal exit codes set by the 'fatal' option
188             # when working with fatal => '!0' it's helpful to be able to show the exit status
189             # so that specific exit codes can be made non-fatal if desired.
190 346 100       16709 if ( $self->options->{fatal}{ $self->exit } ) {
191 18   66     6114 croak join( "\n", @errput ) || 'fatal: unknown git error, exit status '.$self->exit;
192             }
193              
194             # something else's wrong
195 328 100 100     2156 if ( @errput && !$self->options->{quiet} ) { carp join "\n", @errput; }
  7         3409  
196              
197             # process the output with the optional callbacks
198 328         3803 for my $cb (@cb) {
199 3         50 @output = map $cb->($_), @output;
200             }
201              
202             # return the output
203 328 100       13864 return wantarray ? @output : join "\n", @output;
204             }
205              
206             1;
207              
208             __END__