File Coverage

blib/lib/AnyEvent/Git/Wrapper.pm
Criterion Covered Total %
statement 169 181 93.3
branch 58 76 76.3
condition 24 33 72.7
subroutine 23 24 95.8
pod 5 5 100.0
total 279 319 87.4


line stmt bran cond sub pod time code
1             package AnyEvent::Git::Wrapper;
2              
3 11     11   434097 use strict;
  11         23  
  11         348  
4 11     11   54 use warnings;
  11         15  
  11         412  
5 11     11   47 use Carp qw( croak );
  11         25  
  11         665  
6 11     11   47 use base qw( Git::Wrapper );
  11         15  
  11         5486  
7 11     11   227292 use File::pushd;
  11         9498  
  11         512  
8 11     11   7010 use AnyEvent;
  11         35453  
  11         368  
9 11     11   5652 use AnyEvent::Open3::Simple;
  11         28416  
  11         385  
10 11     11   72 use Git::Wrapper::Exception;
  11         17  
  11         263  
11 11     11   47 use Git::Wrapper::Statuses;
  11         15  
  11         203  
12 11     11   114 use Git::Wrapper::Log;
  11         15  
  11         243  
13 11     11   38 use Scalar::Util qw( blessed );
  11         12  
  11         18001  
14              
15             # ABSTRACT: Wrap git command-line interface without blocking
16             our $VERSION = '0.10'; # VERSION
17              
18              
19             sub new
20             {
21 15     15 1 43561 my $class = shift;
22            
23 15         23 my $args;
24 15 100       52 if(scalar @_ == 1)
25             {
26 12         20 my $arg = shift;
27 12 100       132 if(ref $arg eq 'HASH') { $args = $arg }
  2 100       3  
    100          
28 2         9 elsif(blessed $arg) { $args = { dir => "$arg" } }
29 6         20 elsif(! ref $arg) { $args = { dir => $arg } }
30 2         16 else { die "Singlearg must be hashref, scalar or stringify-able object" }
31             }
32             else
33             {
34 3         11 my($dir, %opts) = @_;
35 3 50       23 $dir = "$dir" if blessed $dir;
36 3         15 $args = { dir => $dir, %opts };
37             }
38 13         76 my $cache_version = delete $args->{cache_version};
39 13         129 my $self = $class->SUPER::new($args);
40 11         269 $self->{ae_cache_version} = $cache_version;
41 11         28 $self;
42             }
43              
44              
45             sub RUN
46             {
47 172     172 1 277393 my($self) = shift;
48 172         309 my $cv;
49 172 100       859 if(ref($_[-1]) eq 'CODE')
    100          
50             {
51 30         1214 $cv = AE::cv;
52 30         494 $cv->cb(pop);
53             }
54 142         2189 elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
55             {
56 27         43 $cv = pop;
57             }
58             else
59             {
60 115         642 return $self->SUPER::RUN(@_);
61             }
62              
63 57         644 my $cmd = shift;
64              
65 57         105 my $customize;
66 57 100       262 $customize = pop if ref($_[-1]) eq 'CODE';
67            
68 57         471 my ($parts, $in) = Git::Wrapper::_parse_args( $cmd, @_ );
69 57         2790 my @out;
70             my @err;
71              
72             my $ipc = AnyEvent::Open3::Simple->new(
73             on_stdout => \@out,
74             on_stderr => \@err,
75             on_error => sub {
76             #my($error) = @_;
77 0     0   0 $cv->croak(
78             Git::Wrapper::Exception->new(
79             output => \@out,
80             error => \@err,
81             status => -1,
82             )
83             );
84             },
85             on_exit => sub {
86 57     57   97960 my(undef, $exit, $signal) = @_;
87            
88             # borrowed from superclass, see comment there
89 57   66     395 my $stupid_status = $cmd eq 'status' && @out && ! @err;
90            
91 57 100 66     577 if(($exit || $signal) && ! $stupid_status)
      66        
92             {
93 1         119 $cv->croak(
94             Git::Wrapper::Exception->new(
95             output => \@out,
96             error => \@err,
97             status => $exit,
98             )
99             );
100             }
101             else
102             {
103 56         202 $self->{err} = \@err;
104 56         158 $self->{out} = \@out;
105 56         355 $cv->send(\@out, \@err);
106             }
107             },
108 57 100       948 $customize ? $customize->() : ()
109             );
110            
111 57         2826 do {
112 57 50       437 my $d = pushd $self->dir unless $cmd eq 'clone';
113            
114 57         6690 my @cmd = ( $self->git, @$parts );
115            
116 57 50       1229 local $ENV{GIT_EDITOR} = $^O eq 'MSWin32' ? 'cmd /c "exit 2"' : '';
117 57         293 $ipc->run(@cmd, \$in);
118            
119 57         248367 undef $d;
120             };
121            
122 57         3593 $cv;
123             }
124              
125              
126             my %STATUS_CONFLICTS = map { $_ => 1 } qw
;
127              
128             sub status
129             {
130 4     4 1 303 my($self) = shift;
131 4         13 my $cv;
132 4 50       28 if(ref($_[-1]) eq 'CODE')
    100          
133             {
134 0         0 $cv = AE::cv;
135 0         0 $cv->cb(pop);
136             }
137 4         61 elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
138             {
139 2         4 $cv = pop;
140             }
141             else
142             {
143 2         32 return $self->SUPER::status(@_);
144             }
145              
146 2 50       10 my $opt = ref $_[0] eq 'HASH' ? shift : {};
147 2         7 $opt->{porcelain} = 1;
148              
149             $self->RUN('status' => $opt, @_, sub {
150 2     2   33 my $out = shift->recv;
151 2         42 my $stat = Git::Wrapper::Statuses->new;
152              
153 2         13 for(@$out)
154             {
155 1         10 my ($x, $y, $from, $to) = $_ =~ /\A(.)(.) (.*?)(?: -> (.*))?\z/;
156 1 50 33     10 if ($STATUS_CONFLICTS{"$x$y"})
    50          
157             {
158 0         0 $stat->add('conflict', "$x$y", $from, $to);
159             }
160             elsif ($x eq '?' && $y eq '?')
161             {
162 0         0 $stat->add('unknown', '?', $from, $to);
163             }
164             else
165             {
166 1 50       4 $stat->add('changed', $y, $from, $to)
167             if $y ne ' ';
168 1 50       9 $stat->add('indexed', $x, $from, $to)
169             if $x ne ' ';
170             }
171             }
172            
173 2         38 $cv->send($stat);
174 2         24 });
175            
176 2         31 $cv;
177             }
178              
179              
180             sub log
181             {
182 22     22 1 67424 my($self) = shift;
183 22         113 my $cv;
184 22 50       137 if(ref($_[-1]) eq 'CODE')
    100          
185             {
186 0         0 $cv = AE::cv;
187 0         0 $cv->cb(pop);
188             }
189 22         326 elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
190             {
191 11         28 $cv = pop;
192             }
193             else
194             {
195 11         93 return $self->SUPER::log(@_);
196             }
197            
198 11         24 my $cb;
199 11 100       50 if(ref($_[-1]) eq 'CODE')
200             {
201 1         4 $cb = pop;
202             }
203              
204 11 100       48 my $opt = ref $_[0] eq 'HASH' ? shift : {};
205 11         51 $opt->{no_color} = 1;
206 11         55 $opt->{pretty} = 'medium';
207 11 50       96 $opt->{no_abbrev_commit} = 1
208             if $self->supports_log_no_abbrev_commit;
209            
210 11   66     935 my $raw = defined $opt->{raw} && $opt->{raw};
211              
212 11         39 my $out = [];
213 11         27 my @logs;
214            
215             my $process_commit = sub {
216 14 50   14   58 if(my $line = shift @$out)
217             {
218 14 100       92 unless($line =~ /^commit (\S+)/)
219             {
220 1         37 $cv->croak("unhandled: $line");
221 1         16 return;
222             }
223            
224 13         186 my $current = Git::Wrapper::Log->new($1);
225            
226 13         272 $line = shift @$out; # next line
227            
228 13         85 while($line =~ /^(\S+):\s+(.+)$/)
229             {
230 26         69 $current->attr->{lc $1} = $2;
231 26         265 $line = shift @$out; # next line
232             }
233            
234 13 50       29 if($line)
235             {
236 0         0 $cv->croak("no blank line separating head from message");
237 0         0 return;
238             }
239            
240 13 50       94 my($initial_indent) = $out->[0] =~ /^(\s*)/ if @$out;
241            
242 13         30 my $message = '';
243 13   100     220 while(@$out and $out->[0] !~ /^commit (\S+)/ and length($line = shift @$out))
      100        
244             {
245 20         165 $line =~ s/^$initial_indent//; # strip just the indenting added by git
246 20         118 $message .= "$line\n";
247             }
248            
249 13         52 $current->message($message);
250            
251 13 100       105 if($raw)
252             {
253 1         4 my @modifications;
254 1   66     18 while(@$out and $out->[0] =~ m/^\:(\d{6}) (\d{6}) (\w{7})\.\.\. (\w{7})\.\.\. (\w{1})\t(.*)$/)
255             {
256 1         19 push @modifications, Git::Wrapper::File::RawModification->new($6,$5,$1,$2,$3,$4);
257 1         33 shift @$out;
258             }
259 1 50       12 $current->modifications(@modifications) if @modifications;
260             }
261            
262 13 100       43 if($cb)
263 2         9 { $cb->($current) }
264             else
265 11         47 { push @logs, $current }
266             }
267 11         153 };
268            
269             my $on_stdout = sub {
270 77     77   26979 my $line = pop;
271 77         153 push @$out, $line;
272 77 100 100     795 $process_commit->() if $line =~ /^commit (\S+)/ && @$out > 1;
273 11         84 };
274            
275 11     11   153 $self->RUN(log => $opt, @_, sub { on_stdout => $on_stdout }, sub {
276 11     11   171 eval { shift->recv };
  11         60  
277 11 50       120 $cv->croak($@) if $@;
278            
279 11         36 while($out->[0]) {
280 11         91 $process_commit->();
281             }
282            
283 11         45 $cv->send(@logs);
284 11         150 });
285            
286 11         232 $cv;
287             }
288              
289              
290             sub version
291             {
292 68     68 1 63414 my($self) = @_;
293 68         116 my $cv;
294 68 50       365 if(ref($_[-1]) eq 'CODE')
    100          
295             {
296 0         0 $cv = AE::cv;
297 0         0 $cv->cb(pop);
298             }
299 68         934 elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
300             {
301 10         19 $cv = pop;
302             }
303             else
304             {
305 58 100 66     327 if($self->{ae_cache_version} && $self->{ae_version})
306 2         19 { return $self->{ae_version} }
307 56         336 $self->{ae_version} = $self->SUPER::version(@_);
308 56         234140 return $self->{ae_version};
309             }
310            
311 10 100 66     61 if($self->{ae_cache_version} && $self->{ae_version})
312             {
313 2         10 $cv->send($self->{ae_version});
314             }
315             else
316             {
317             $self->RUN('version', sub {
318 8     8   88 my $out = eval { shift->recv };
  8         39  
319 8 50       69 if($@)
320             {
321 0         0 $cv->croak($@);
322             }
323             else
324             {
325 8         22 $self->{ae_version} = $out->[0];
326 8         51 $self->{ae_version} =~ s/^git version //;
327 8         40 $cv->send($self->{ae_version});
328             }
329 8         53 });
330             }
331            
332 10         154 $cv;
333             }
334              
335              
336             1;
337              
338             __END__