use strict; use warnings; package Git::Wrapper; our $VERSION = '0.007'; use IPC::Open3 () ; use Symbol; use File::pushd; sub new { my ($class, $arg, %opt) = @_; my $self = bless { dir => $arg, %opt } => $class; die "usage: $class->new(\$dir)" unless $self->dir; return $self; } sub dir { shift->{dir} } my $GIT = 'git'; sub _opt { my $name = shift; $name =~ tr/_/-/; return length($name) == 1 ? "-$name" : "--$name" ; } sub _cmd { my $self = shift; my $cmd = shift; my $opt = ref $_[0] eq 'HASH' ? shift : {}; my @cmd = $GIT; for (grep { /^-/ } keys %$opt) { (my $name = $_) =~ s/^-//; my $val = delete $opt->{$_}; next if $val eq '0'; push @cmd, _opt($name) . ($val eq '1' ? "" : "=$val"); } push @cmd, $cmd; for my $name (keys %$opt) { my $val = delete $opt->{$name}; next if $val eq '0'; push @cmd, _opt($name) . ($val eq '1' ? "" : "=$val"); } push @cmd, @_; #print "running [@cmd]\n"; my @out; my @err; { my $d = pushd $self->dir; my ($wtr, $rdr, $err); $err = Symbol::gensym; my $pid = IPC::Open3::open3($wtr, $rdr, $err, @cmd); close $wtr; chomp(@out = <$rdr>); chomp(@err = <$err>); waitpid $pid, 0; }; #print "status: $?\n"; if ($?) { die Git::Wrapper::Exception->new( output => \@out, error => \@err, status => $? >> 8, ); } chomp(@out); return @out; } sub AUTOLOAD { my $self = shift; (my $meth = our $AUTOLOAD) =~ s/.+:://; return if $meth eq 'DESTROY'; $meth =~ tr/_/-/; return $self->_cmd($meth, @_); } sub version { my $self = shift; my ($version) = $self->_cmd('version'); $version =~ s/^git version //; return $version; } sub log { my $self = shift; my $opt = ref $_[0] eq 'HASH' ? shift : {}; $opt->{no_color} = 1; my @out = $self->_cmd(log => $opt, @_); my @logs; while (@out) { local $_ = shift @out; die "unhandled: $_" unless /^commit (\S+)/; my $current = Git::Wrapper::Log->new($1); $_ = shift @out; while (/^(\S+):\s+(.+)$/) { $current->attr->{lc $1} = $2; $_ = shift @out; } die "no blank line separating head from message" if $_; my $message = ''; while (@out and length($_ = shift @out)) { s/^\s+//; $message .= "$_\n"; } $current->message($message); push @logs, $current; } return @logs; } package Git::Wrapper::Exception; sub new { my $class = shift; bless { @_ } => $class } use overload ( q("") => 'error', fallback => 1, ); sub output { join "", map { "$_\n" } @{ shift->{output} } } sub error { join "", map { "$_\n" } @{ shift->{error} } } sub status { shift->{status} } package Git::Wrapper::Log; sub new { my ($class, $id, %arg) = @_; return bless { id => $id, attr => {}, %arg, } => $class; } sub id { shift->{id} } sub attr { shift->{attr} } sub message { @_ > 1 ? ($_[0]->{message} = $_[1]) : $_[0]->{message} } sub date { shift->attr->{date} } sub author { shift->attr->{author} } 1; __END__ =head1 NAME Git::Wrapper - wrap git(7) command-line interface =head1 VERSION Version 0.006 =head1 SYNOPSIS my $git = Git::Wrapper->new('/var/foo'); $git->commit(...) print $_->message for $git->log; =head1 DESCRIPTION Git::Wrapper provides an API for git(7) that uses Perl data structures for argument passing, instead of CLI-style C<--options> as L does. =head1 METHODS Except as documented, every git subcommand is available as a method on a Git::Wrapper object. The first argument should be a hashref containing options and their values. Boolean options are either true (included) or false (excluded). The remaining arguments are passed as ordinary command arguments. $git->commit({ all => 1, message => "stuff" }); $git->checkout("mybranch"); Output is available as an array of lines, each chomped. @sha1s_and_titles = $git->rev_list({ all => 1, pretty => 'oneline' }); If a git command exits nonzero, a C object will be thrown. It has three useful methods: =over =item * error error message =item * output normal output, as a single string =item * status the exit status =back The exception stringifies to the error message. =head2 new my $git = Git::Wrapper->new($dir); =head2 dir print $git->dir; # /var/foo =head2 version my $version = $git->version; # 1.6.1.4.8.15.16.23.42 =head2 log my @logs = $git->log; Instead of giving back an arrayref of lines, the C method returns a list of C objects. They have four methods: =over =item * id =item * author =item * date =item * message =back =head1 SEE ALSO L is the git implementation for L, a generic interface to version-controle systems. Git itself is at L. =head1 AUTHOR Hans Dieter Pearcey, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 COPYRIGHT & LICENSE Copyright 2008 Hans Dieter Pearcey, All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut