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   384078 use strict;
  11         23  
  11         250  
4 11     11   49 use warnings;
  11         21  
  11         319  
5 11     11   50 use Carp qw( croak );
  11         29  
  11         576  
6 11     11   48 use base qw( Git::Wrapper );
  11         17  
  11         8178  
7 11     11   368204 use File::pushd;
  11         20006  
  11         547  
8 11     11   12289 use AnyEvent;
  11         44902  
  11         294  
9 11     11   8193 use AnyEvent::Open3::Simple;
  11         27940  
  11         331  
10 11     11   66 use Git::Wrapper::Exception;
  11         18  
  11         252  
11 11     11   48 use Git::Wrapper::Statuses;
  11         17  
  11         247  
12 11     11   130 use Git::Wrapper::Log;
  11         20  
  11         227  
13 11     11   50 use Scalar::Util qw( blessed );
  11         18  
  11         20504  
14              
15             # ABSTRACT: Wrap git command-line interface without blocking
16             our $VERSION = '0.08'; # VERSION
17              
18              
19             sub new
20             {
21 15     15 1 883240 my $class = shift;
22            
23 15         39 my $args;
24 15 100       68 if(scalar @_ == 1)
25             {
26 12         31 my $arg = shift;
27 12 100       136 if(ref $arg eq 'HASH') { $args = $arg }
  2 100       6  
    100          
28 2         11 elsif(blessed $arg) { $args = { dir => "$arg" } }
29 6         29 elsif(! ref $arg) { $args = { dir => $arg } }
30 2         39 else { die "Singlearg must be hashref, scalar or stringify-able object" }
31             }
32             else
33             {
34 3         19 my($dir, %opts) = @_;
35 3 50       54 $dir = "$dir" if blessed $dir;
36 3         18 $args = { dir => $dir, %opts };
37             }
38 13         78 my $cache_version = delete $args->{cache_version};
39 13         173 my $self = $class->SUPER::new($args);
40 11         384 $self->{ae_cache_version} = $cache_version;
41 11         41 $self;
42             }
43              
44              
45             sub RUN
46             {
47 162     162 1 557534 my($self) = shift;
48 162         464 my $cv;
49 162 100       1065 if(ref($_[-1]) eq 'CODE')
    100          
50             {
51 30         2101 $cv = AE::cv;
52 30         632 $cv->cb(pop);
53             }
54 132         2400 elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
55             {
56 27         104 $cv = pop;
57             }
58             else
59             {
60 105         743 return $self->SUPER::RUN(@_);
61             }
62              
63 57         929 my $cmd = shift;
64              
65 57         208 my $customize;
66 57 100       603 $customize = pop if ref($_[-1]) eq 'CODE';
67            
68 57         739 my ($parts, $in) = Git::Wrapper::_parse_args( $cmd, @_ );
69 57         3948 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   159998 my(undef, $exit, $signal) = @_;
87            
88             # borrowed from superclass, see comment there
89 57   66     560 my $stupid_status = $cmd eq 'status' && @out && ! @err;
90            
91 57 100 66     897 if(($exit || $signal) && ! $stupid_status)
      66        
92             {
93 1         32 $cv->croak(
94             Git::Wrapper::Exception->new(
95             output => \@out,
96             error => \@err,
97             status => $exit,
98             )
99             );
100             }
101             else
102             {
103 56         341 $self->{err} = \@err;
104 56         236 $self->{out} = \@out;
105 56         554 $cv->send(\@out, \@err);
106             }
107             },
108 57 100       1348 $customize ? $customize->() : ()
109             );
110            
111 57         4508 do {
112 57 50       685 my $d = pushd $self->dir unless $cmd eq 'clone';
113            
114 57         10752 my @cmd = ( $self->git, @$parts );
115            
116 57 50       1390 local $ENV{GIT_EDITOR} = $^O eq 'MSWin32' ? 'cmd /c "exit 2"' : '';
117 57         455 $ipc->run(@cmd, \$in);
118            
119 57         537882 undef $d;
120             };
121            
122 57         6804 $cv;
123             }
124              
125              
126             my %STATUS_CONFLICTS = map { $_ => 1 } qw
;
127              
128             sub status
129             {
130 4     4 1 336 my($self) = shift;
131 4         15 my $cv;
132 4 50       46 if(ref($_[-1]) eq 'CODE')
    100          
133             {
134 0         0 $cv = AE::cv;
135 0         0 $cv->cb(pop);
136             }
137 4         91 elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
138             {
139 2         14 $cv = pop;
140             }
141             else
142             {
143 2         38 return $self->SUPER::status(@_);
144             }
145              
146 2 50       24 my $opt = ref $_[0] eq 'HASH' ? shift : {};
147 2         35 $opt->{porcelain} = 1;
148              
149             $self->RUN('status' => $opt, @_, sub {
150 2     2   62 my $out = shift->recv;
151 2         77 my $stat = Git::Wrapper::Statuses->new;
152              
153 2         28 for(@$out)
154             {
155 1         17 my ($x, $y, $from, $to) = $_ =~ /\A(.)(.) (.*?)(?: -> (.*))?\z/;
156 1 50 33     31 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       11 $stat->add('changed', $y, $from, $to)
167             if $y ne ' ';
168 1 50       13 $stat->add('indexed', $x, $from, $to)
169             if $x ne ' ';
170             }
171             }
172            
173 2         56 $cv->send($stat);
174 2         63 });
175            
176 2         60 $cv;
177             }
178              
179              
180             sub log
181             {
182 22     22 1 132997 my($self) = shift;
183 22         87 my $cv;
184 22 50       231 if(ref($_[-1]) eq 'CODE')
    100          
185             {
186 0         0 $cv = AE::cv;
187 0         0 $cv->cb(pop);
188             }
189 22         529 elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
190             {
191 11         53 $cv = pop;
192             }
193             else
194             {
195 11         161 return $self->SUPER::log(@_);
196             }
197            
198 11         53 my $cb;
199 11 100       123 if(ref($_[-1]) eq 'CODE')
200             {
201 1         5 $cb = pop;
202             }
203              
204 11 100       100 my $opt = ref $_[0] eq 'HASH' ? shift : {};
205 11         93 $opt->{no_color} = 1;
206 11         95 $opt->{pretty} = 'medium';
207 11 50       245 $opt->{no_abbrev_commit} = 1
208             if $self->supports_log_no_abbrev_commit;
209            
210 11   66     1723 my $raw = defined $opt->{raw} && $opt->{raw};
211              
212 11         101 my $out = [];
213 11         45 my @logs;
214            
215             my $process_commit = sub {
216 14 50   14   73 if(my $line = shift @$out)
217             {
218 14 100       103 unless($line =~ /^commit (\S+)/)
219             {
220 1         33 $cv->croak("unhandled: $line");
221 1         16 return;
222             }
223            
224 13         348 my $current = Git::Wrapper::Log->new($1);
225            
226 13         419 $line = shift @$out; # next line
227            
228 13         147 while($line =~ /^(\S+):\s+(.+)$/)
229             {
230 26         136 $current->attr->{lc $1} = $2;
231 26         414 $line = shift @$out; # next line
232             }
233            
234 13 50       53 if($line)
235             {
236 0         0 $cv->croak("no blank line separating head from message");
237 0         0 return;
238             }
239            
240 13 50       136 my($initial_indent) = $out->[0] =~ /^(\s*)/ if @$out;
241            
242 13         55 my $message = '';
243 13   100     230 while(@$out and $out->[0] !~ /^commit (\S+)/ and length($line = shift @$out))
      100        
244             {
245 20         242 $line =~ s/^$initial_indent//; # strip just the indenting added by git
246 20         191 $message .= "$line\n";
247             }
248            
249 13         83 $current->message($message);
250            
251 13 100       137 if($raw)
252             {
253 1         2 my @modifications;
254 1   66     19 while(@$out and $out->[0] =~ m/^\:(\d{6}) (\d{6}) (\w{7})\.\.\. (\w{7})\.\.\. (\w{1})\t(.*)$/)
255             {
256 1         20 push @modifications, Git::Wrapper::File::RawModification->new($6,$5,$1,$2,$3,$4);
257 1         36 shift @$out;
258             }
259 1 50       12 $current->modifications(@modifications) if @modifications;
260             }
261            
262 13 100       55 if($cb)
263 2         11 { $cb->($current) }
264             else
265 11         98 { push @logs, $current }
266             }
267 11         276 };
268            
269             my $on_stdout = sub {
270 77     77   33367 my $line = pop;
271 77         290 push @$out, $line;
272 77 100 100     889 $process_commit->() if $line =~ /^commit (\S+)/ && @$out > 1;
273 11         152 };
274            
275 11     11   328 $self->RUN(log => $opt, @_, sub { on_stdout => $on_stdout }, sub {
276 11     11   201 eval { shift->recv };
  11         67  
277 11 50       162 $cv->croak($@) if $@;
278            
279 11         59 while($out->[0]) {
280 11         39 $process_commit->();
281             }
282            
283 11         70 $cv->send(@logs);
284 11         352 });
285            
286 11         468 $cv;
287             }
288              
289              
290             sub version
291             {
292 58     58 1 96721 my($self) = @_;
293 58         142 my $cv;
294 58 50       437 if(ref($_[-1]) eq 'CODE')
    100          
295             {
296 0         0 $cv = AE::cv;
297 0         0 $cv->cb(pop);
298             }
299 58         1036 elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
300             {
301 10         30 $cv = pop;
302             }
303             else
304             {
305 48 100 66     391 if($self->{ae_cache_version} && $self->{ae_version})
306 2         20 { return $self->{ae_version} }
307 46         511 $self->{ae_version} = $self->SUPER::version(@_);
308 46         485471 return $self->{ae_version};
309             }
310            
311 10 100 66     78 if($self->{ae_cache_version} && $self->{ae_version})
312             {
313 2         21 $cv->send($self->{ae_version});
314             }
315             else
316             {
317             $self->RUN('version', sub {
318 8     8   150 my $out = eval { shift->recv };
  8         70  
319 8 50       102 if($@)
320             {
321 0         0 $cv->croak($@);
322             }
323             else
324             {
325 8         45 $self->{ae_version} = $out->[0];
326 8         74 $self->{ae_version} =~ s/^git version //;
327 8         50 $cv->send($self->{ae_version});
328             }
329 8         70 });
330             }
331            
332 10         312 $cv;
333             }
334              
335              
336             1;
337              
338             __END__