AnyEvent-Git-Wrapper
view release on metacpan or search on metacpan
lib/AnyEvent/Git/Wrapper.pm view on Meta::CPAN
package AnyEvent::Git::Wrapper;
use strict;
use warnings;
use Carp qw( croak );
use base qw( Git::Wrapper );
use File::pushd;
use AnyEvent;
use AnyEvent::Open3::Simple;
use Git::Wrapper::Exception;
use Git::Wrapper::Statuses;
use Git::Wrapper::Log;
use Scalar::Util qw( blessed );
# ABSTRACT: Wrap git command-line interface without blocking
our $VERSION = '0.10'; # VERSION
sub new
{
my $class = shift;
my $args;
if(scalar @_ == 1)
{
my $arg = shift;
if(ref $arg eq 'HASH') { $args = $arg }
elsif(blessed $arg) { $args = { dir => "$arg" } }
elsif(! ref $arg) { $args = { dir => $arg } }
else { die "Singlearg must be hashref, scalar or stringify-able object" }
}
else
{
my($dir, %opts) = @_;
$dir = "$dir" if blessed $dir;
$args = { dir => $dir, %opts };
}
my $cache_version = delete $args->{cache_version};
my $self = $class->SUPER::new($args);
$self->{ae_cache_version} = $cache_version;
$self;
}
sub RUN
{
my($self) = shift;
my $cv;
if(ref($_[-1]) eq 'CODE')
{
$cv = AE::cv;
$cv->cb(pop);
}
elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
{
$cv = pop;
}
else
{
return $self->SUPER::RUN(@_);
}
my $cmd = shift;
my $customize;
$customize = pop if ref($_[-1]) eq 'CODE';
my ($parts, $in) = Git::Wrapper::_parse_args( $cmd, @_ );
my @out;
my @err;
my $ipc = AnyEvent::Open3::Simple->new(
on_stdout => \@out,
on_stderr => \@err,
on_error => sub {
#my($error) = @_;
$cv->croak(
Git::Wrapper::Exception->new(
output => \@out,
error => \@err,
status => -1,
)
);
},
on_exit => sub {
my(undef, $exit, $signal) = @_;
# borrowed from superclass, see comment there
my $stupid_status = $cmd eq 'status' && @out && ! @err;
if(($exit || $signal) && ! $stupid_status)
{
$cv->croak(
Git::Wrapper::Exception->new(
output => \@out,
error => \@err,
status => $exit,
)
);
}
else
{
$self->{err} = \@err;
$self->{out} = \@out;
$cv->send(\@out, \@err);
}
},
$customize ? $customize->() : ()
);
do {
my $d = pushd $self->dir unless $cmd eq 'clone';
my @cmd = ( $self->git, @$parts );
local $ENV{GIT_EDITOR} = $^O eq 'MSWin32' ? 'cmd /c "exit 2"' : '';
$ipc->run(@cmd, \$in);
undef $d;
};
$cv;
}
my %STATUS_CONFLICTS = map { $_ => 1 } qw<DD AU UD UA DU AA UU>;
sub status
{
my($self) = shift;
my $cv;
if(ref($_[-1]) eq 'CODE')
{
$cv = AE::cv;
$cv->cb(pop);
}
elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
{
$cv = pop;
}
else
{
return $self->SUPER::status(@_);
}
my $opt = ref $_[0] eq 'HASH' ? shift : {};
$opt->{porcelain} = 1;
$self->RUN('status' => $opt, @_, sub {
my $out = shift->recv;
my $stat = Git::Wrapper::Statuses->new;
for(@$out)
{
my ($x, $y, $from, $to) = $_ =~ /\A(.)(.) (.*?)(?: -> (.*))?\z/;
if ($STATUS_CONFLICTS{"$x$y"})
{
$stat->add('conflict', "$x$y", $from, $to);
}
elsif ($x eq '?' && $y eq '?')
{
$stat->add('unknown', '?', $from, $to);
}
else
{
$stat->add('changed', $y, $from, $to)
if $y ne ' ';
$stat->add('indexed', $x, $from, $to)
if $x ne ' ';
}
}
$cv->send($stat);
});
$cv;
}
sub log
{
my($self) = shift;
my $cv;
if(ref($_[-1]) eq 'CODE')
{
$cv = AE::cv;
$cv->cb(pop);
}
elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
{
$cv = pop;
}
else
{
return $self->SUPER::log(@_);
}
my $cb;
if(ref($_[-1]) eq 'CODE')
{
$cb = pop;
}
my $opt = ref $_[0] eq 'HASH' ? shift : {};
$opt->{no_color} = 1;
$opt->{pretty} = 'medium';
$opt->{no_abbrev_commit} = 1
if $self->supports_log_no_abbrev_commit;
my $raw = defined $opt->{raw} && $opt->{raw};
my $out = [];
my @logs;
my $process_commit = sub {
if(my $line = shift @$out)
{
unless($line =~ /^commit (\S+)/)
{
$cv->croak("unhandled: $line");
return;
}
my $current = Git::Wrapper::Log->new($1);
$line = shift @$out; # next line
while($line =~ /^(\S+):\s+(.+)$/)
{
$current->attr->{lc $1} = $2;
$line = shift @$out; # next line
}
if($line)
{
$cv->croak("no blank line separating head from message");
return;
}
my($initial_indent) = $out->[0] =~ /^(\s*)/ if @$out;
my $message = '';
while(@$out and $out->[0] !~ /^commit (\S+)/ and length($line = shift @$out))
{
$line =~ s/^$initial_indent//; # strip just the indenting added by git
$message .= "$line\n";
}
$current->message($message);
if($raw)
{
my @modifications;
while(@$out and $out->[0] =~ m/^\:(\d{6}) (\d{6}) (\w{7})\.\.\. (\w{7})\.\.\. (\w{1})\t(.*)$/)
{
push @modifications, Git::Wrapper::File::RawModification->new($6,$5,$1,$2,$3,$4);
shift @$out;
}
$current->modifications(@modifications) if @modifications;
}
if($cb)
{ $cb->($current) }
else
{ push @logs, $current }
}
};
my $on_stdout = sub {
my $line = pop;
push @$out, $line;
$process_commit->() if $line =~ /^commit (\S+)/ && @$out > 1;
};
$self->RUN(log => $opt, @_, sub { on_stdout => $on_stdout }, sub {
eval { shift->recv };
$cv->croak($@) if $@;
while($out->[0]) {
$process_commit->();
}
$cv->send(@logs);
});
$cv;
}
sub version
{
my($self) = @_;
my $cv;
if(ref($_[-1]) eq 'CODE')
{
$cv = AE::cv;
$cv->cb(pop);
}
elsif(eval { $_[-1]->isa('AnyEvent::CondVar') })
{
$cv = pop;
}
else
{
if($self->{ae_cache_version} && $self->{ae_version})
{ return $self->{ae_version} }
$self->{ae_version} = $self->SUPER::version(@_);
return $self->{ae_version};
}
if($self->{ae_cache_version} && $self->{ae_version})
{
$cv->send($self->{ae_version});
}
else
{
$self->RUN('version', sub {
my $out = eval { shift->recv };
if($@)
{
$cv->croak($@);
}
else
{
$self->{ae_version} = $out->[0];
$self->{ae_version} =~ s/^git version //;
$cv->send($self->{ae_version});
}
});
}
$cv;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
AnyEvent::Git::Wrapper - Wrap git command-line interface without blocking
=head1 VERSION
version 0.10
=head1 SYNOPSIS
use AnyEvent::Git::Wrapper;
# add all files and make a commit...
my $git = AnyEvent::Git::Wrapper->new($dir);
$git->add('.', sub {
$git->commit({ message => 'initial commit' }, sub {
say "made initial commit";
});
});
=head1 DESCRIPTION
B<DEPRECATED>: May go away at some point.
( run in 1.153 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )