File Coverage

blib/lib/Git/Repository/Command.pm
Criterion Covered Total %
statement 92 92 100.0
branch 56 60 93.3
condition 24 32 75.0
subroutine 19 19 100.0
pod 2 2 100.0
total 193 205 94.1


line stmt bran cond sub pod time code
1             package Git::Repository::Command;
2             $Git::Repository::Command::VERSION = '1.326';
3 19     19   243987 use strict;
  19         38  
  19         1719  
4 19     19   126 use warnings;
  19         33  
  19         940  
5 19     19   308 use 5.006;
  19         62  
6              
7 19     19   97 use Carp;
  19         121  
  19         1604  
8 19     19   118 use Cwd qw( cwd );
  19         34  
  19         1246  
9 19     19   12308 use IO::Handle;
  19         142634  
  19         1461  
10 19     19   160 use Scalar::Util qw( blessed );
  19         42  
  19         1276  
11 19     19   111 use File::Spec;
  19         35  
  19         489  
12 19     19   10855 use System::Command;
  19         421261  
  19         102  
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 19     19   1828 no strict 'refs';
  19         41  
  19         1930  
19 2856     2856   28686419 *$attr = sub { return $_[0]{$attr} };
20             }
21             for my $attr (qw( cmdline )) {
22 19     19   128 no strict 'refs';
  19         41  
  19         32438  
23 2     2   31 *$attr = sub { return @{ $_[0]{$attr} } };
  2         54  
24             }
25              
26             sub _which {
27 28     28   194 my @pathext = ('');
28             push @pathext,
29             $^O eq 'MSWin32' ? split ';', $ENV{PATHEXT}
30 28 50       386 : $^O eq 'cygwin' ? qw( .com .exe .bat )
    50          
31             : ();
32              
33 28         432 for my $path ( File::Spec->path ) {
34 155         319 for my $ext (@pathext) {
35 155         1399 my $binary = File::Spec->catfile( $path, $_[0] . $ext );
36 155 100 100     3802 return $binary if -x $binary && !-d _;
37             }
38             }
39              
40             # not found
41 10         57 return undef;
42             }
43              
44             # CAN I HAS GIT?
45             my %binary; # cache calls to _is_git
46             sub _is_git {
47 373     373   282889 my ( $binary, @args ) = @_;
48 373         1634 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 373 100 66     12368 my $path = defined $ENV{PATH} && length( $ENV{PATH} ) ? $ENV{PATH} : '';
60 373 50       62834 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 373 100       4598 if exists $binary{$type}{$key}{$binary}{$args};
77              
78             # compute a list of candidate files (look in PATH if needed)
79 39 100       720 my $git = $type =~ /^path/
80             ? _which($binary)
81             : File::Spec->rel2abs($binary);
82 39 50 66     428 $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 39 100 100     1191 return $binary{$type}{$key}{$binary} = undef
87             if !( defined $git && -x $git );
88              
89             # try to run it
90 25         568 my $cmd = System::Command->new( $git, @args, '--version' );
91 25   50     395301 my $version = do { local $/ = "\n"; $cmd->stdout->getline; } || '';
92 25         3075426 $cmd->close;
93              
94             # does it really look like git?
95 25 100       45276 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 355     355 1 2150 my ( $class, @cmd ) = @_;
105              
106             # split the args
107 355         1496 my (@r, @o);
108 355 100 100     7053 @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 355 100       2492 croak "Too many Git::Repository objects given: @r" if @r > 1;
115 354         1134 my $r = shift @r;
116              
117             # keep changes to the environment local
118 354         44350 local %ENV = %ENV;
119              
120             # a Git::Repository object will give more context
121 354 100       3334 if ($r) {
122              
123             # pick up repository options
124 141         2217 unshift @o, $r->options;
125              
126             # get some useful paths
127 141         954 my ( $git_dir, $work_tree ) = ( $r->git_dir, $r->work_tree );
128 141 100 66     1519 unshift @o, { cwd => $work_tree }
129             if defined $work_tree && length $work_tree;
130              
131             # setup our %ENV
132 141         1976 delete @ENV{qw( GIT_DIR GIT_WORK_TREE )};
133 141         1110 $ENV{GIT_DIR} = $git_dir;
134 141 100       1004 $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 354   66     3258 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 354 100       10478 }
    100          
    100          
151             };
152              
153             # get and check the git command
154 354 100       1671 my $git_cmd = ( map { exists $_->{git} ? $_->{git} : () } @o )[-1];
  775         2521  
155              
156             # git option might be an arrayref containing an executable with arguments
157             # (e.g. [ qw( /usr/bin/sudo -u nobody git ) ] )
158 354 100       1682 ( $git_cmd, my @args )
    100          
159             = defined $git_cmd ? ref $git_cmd ? @$git_cmd : ($git_cmd) : ('git');
160 354         2397 my $git = _is_git($git_cmd, @args);
161              
162 354 100       7502 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 347         3034 delete $ENV{TERM};
168              
169             # spawn the command and re-bless the object in our class
170 347         3510 return bless System::Command->new( $git, @args, @cmd, @o ), $class;
171             }
172              
173             sub final_output {
174 342     342 1 2569 my ($self, @cb) = @_;
175              
176             # get output / errput
177             my $input_record_separator =
178             exists $self->options->{input_record_separator}
179             ? $self->options->{input_record_separator}
180 342 100       4953 : "\n";
181 342         5291 my ( @output, @errput );
182             $self->loop_on(
183             input_record_separator => $input_record_separator,
184 298     298   32871 stdout => sub { chomp( my $o = shift ); push @output, $o; },
  298         1384  
185 153     153   2411 stderr => sub { chomp( my $e = shift ); push @errput, $e; },
  153         467  
186 342         11764 );
187              
188             # done with it
189 342         3531 $self->close;
190              
191             # fatal exit codes set by the 'fatal' option
192             # when working with fatal => '!0' it's helpful to be able to show the exit status
193             # so that specific exit codes can be made non-fatal if desired.
194 342 100       15030 if ( $self->options->{fatal}{ $self->exit } ) {
195 17   66     5485 croak join( "\n", @errput ) || 'fatal: unknown git error, exit status '.$self->exit;
196             }
197              
198             # something else's wrong
199 325 100 100     2327 if ( @errput && !$self->options->{quiet} ) { carp join "\n", @errput; }
  7         4186  
200              
201             # process the output with the optional callbacks
202 325         2379 for my $cb (@cb) {
203 3         43 @output = map $cb->($_), @output;
204             }
205              
206             # return the output
207 325 100       13482 return wantarray ? @output : join "\n", @output;
208             }
209              
210             1;
211              
212             __END__