#
# Used to read files from a git repository without excessive forking.
# Used in our web interfaces as well as our -nntpd server.
-# This is based on code in Git.pm which is GPLv2, but modified to avoid
+# This is based on code in Git.pm which is GPLv2+, but modified to avoid
# dependence on environment variables for compatibility with mod_perl.
# There are also API changes to simplify our usage and data set.
package PublicInbox::Git;
use warnings;
use POSIX qw(dup2);
require IO::Handle;
+use PublicInbox::Spawn qw(spawn popen_rd);
sub new {
my ($class, $git_dir) = @_;
pipe($out_r, $out_w) or fail($self, "pipe failed: $!");
my @cmd = ('git', "--git-dir=$self->{git_dir}", qw(cat-file), $batch);
- $self->{$pid} = fork;
- defined $self->{$pid} or fail($self, "fork failed: $!");
- if ($self->{$pid} == 0) {
- dup2(fileno($out_r), 0) or die "redirect stdin failed: $!\n";
- dup2(fileno($in_w), 1) or die "redirect stdout failed: $!\n";
- exec(@cmd) or die 'exec `' . join(' '). "' failed: $!\n";
- }
- close $out_r or fail($self, "close failed: $!");
- close $in_w or fail($self, "close failed: $!");
+ my $redir = { 0 => fileno($out_r), 1 => fileno($in_w) };
+ my $p = spawn(\@cmd, undef, $redir);
+ defined $p or fail($self, "spawn failed: $!");
+ $self->{$pid} = $p;
$out_w->autoflush(1);
$self->{$out} = $out_w;
$self->{$in} = $in_r;
sub cat_file {
my ($self, $obj, $ref) = @_;
- $self->_bidi_pipe(qw(--batch in out pid));
+ batch_prepare($self);
$self->{out}->print($obj, "\n") or fail($self, "write error: $!");
my $in = $self->{in};
+ local $/ = "\n";
my $head = $in->getline;
$head =~ / missing$/ and return undef;
$head =~ /^[0-9a-f]{40} \S+ (\d+)$/ or
$rv;
}
+sub batch_prepare ($) { _bidi_pipe($_[0], qw(--batch in out pid)) }
+
sub check {
my ($self, $obj) = @_;
$self->_bidi_pipe(qw(--batch-check in_c out_c pid_c));
$self->{out_c}->print($obj, "\n") or fail($self, "write error: $!");
+ local $/ = "\n";
chomp(my $line = $self->{in_c}->getline);
my ($hex, $type, $size) = split(' ', $line);
return if $type eq 'missing';
sub _destroy {
my ($self, $in, $out, $pid) = @_;
- my $p = $self->{$pid} or return;
- $self->{$pid} = undef;
+ my $p = delete $self->{$pid} or return;
foreach my $f ($in, $out) {
- my $fh = $self->{$f};
- defined $fh or next;
- close $fh;
- $self->{$f} = undef;
+ delete $self->{$f};
}
waitpid $p, 0;
}
sub popen {
my ($self, @cmd) = @_;
- my $mode = '-|';
- $mode = shift @cmd if ($cmd[0] eq '|-');
@cmd = ('git', "--git-dir=$self->{git_dir}", @cmd);
- my $pid = open my $fh, $mode, @cmd or
- die('open `'.join(' ', @cmd) . " pipe failed: $!\n");
- $fh;
+ popen_rd(\@cmd);
+}
+
+sub qx {
+ my ($self, @cmd) = @_;
+ my $fh = $self->popen(@cmd);
+ defined $fh or return;
+ local $/ = "\n";
+ return <$fh> if wantarray;
+ local $/;
+ <$fh>
}
sub cleanup {
sub DESTROY { cleanup(@_) }
1;
+__END__
+=pod
+
+=head1 NAME
+
+PublicInbox::Git - git wrapper
+
+=head1 VERSION
+
+version 1.0
+
+=head1 SYNOPSIS
+
+ use PublicInbox::Git;
+ chomp(my $git_dir = `git rev-parse --git-dir`);
+ $git_dir or die "GIT_DIR= must be specified\n";
+ my $git = PublicInbox::Git->new($git_dir);
+
+=head1 DESCRIPTION
+
+Unstable API outside of the L</new> method.
+It requires L<git(1)> to be installed.
+
+=head1 METHODS
+
+=cut
+
+=head2 new
+
+ my $git = PublicInbox::Git->new($git_dir);
+
+Initialize a new PublicInbox::Git object for use with L<PublicInbox::Import>
+This is the only public API method we support. Everything else
+in this module is subject to change.
+
+=head1 SEE ALSO
+
+L<Git>, L<PublicInbox::Import>
+
+=head1 CONTACT
+
+All feedback welcome via plain-text mail to L<mailto:meta@public-inbox.org>
+
+The mail archives are hosted at L<https://public-inbox.org/meta/>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2016 all contributors L<mailto:meta@public-inbox.org>
+
+License: AGPL-3.0+ L<http://www.gnu.org/licenses/agpl-3.0.txt>
+
+=cut