]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/GitCatFile.pm
3fced28d8d8d6058c04852090840d891c458a495
[public-inbox.git] / lib / PublicInbox / GitCatFile.pm
1 # Copyright (C) 2014-2015 all contributors <meta@public-inbox.org>
2 # License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
3 # This is based on code in Git.pm which is GPLv2, but modified to avoid
4 # dependence on environment variables for compatibility with mod_perl.
5 # There are also API changes to simplify our usage and data set.
6 package PublicInbox::GitCatFile;
7 use strict;
8 use warnings;
9 use POSIX qw(dup2);
10 require IO::Handle;
11
12 sub new {
13         my ($class, $git_dir) = @_;
14         bless { git_dir => $git_dir }, $class;
15 }
16
17 sub _cat_file_begin {
18         my ($self) = @_;
19         return if $self->{pid};
20         my ($in_r, $in_w, $out_r, $out_w);
21
22         pipe($in_r, $in_w) or die "pipe failed: $!\n";
23         pipe($out_r, $out_w) or die "pipe failed: $!\n";
24
25         my @cmd = ('git', "--git-dir=$self->{git_dir}", qw(cat-file --batch));
26         my $pid = fork;
27         defined $pid or die "fork failed: $!\n";
28         if ($pid == 0) {
29                 dup2(fileno($out_r), 0) or die "redirect stdin failed: $!\n";
30                 dup2(fileno($in_w), 1) or die "redirect stdout failed: $!\n";
31                 exec(@cmd) or die 'exec `' . join(' '). "' failed: $!\n";
32         }
33         close $out_r or die "close failed: $!\n";
34         close $in_w or die "close failed: $!\n";
35         $out_w->autoflush(1);
36
37         $self->{in} = $in_r;
38         $self->{out} = $out_w;
39         $self->{pid} = $pid;
40 }
41
42 sub cat_file {
43         my ($self, $object, $sizeref) = @_;
44
45         $self->_cat_file_begin;
46         print { $self->{out} } $object, "\n" or die "pipe write error: $!\n";
47
48         my $in = $self->{in};
49         my $head = <$in>;
50         $head =~ / missing$/ and return undef;
51         $head =~ /^[0-9a-f]{40} \S+ (\d+)$/ or
52                 die "Unexpected result from git cat-file: $head\n";
53
54         my $size = $1;
55         $$sizeref = $size if $sizeref;
56         my $bytes_left = $size;
57         my $offset = 0;
58         my $rv = '';
59
60         while ($bytes_left) {
61                 my $read = read($in, $rv, $bytes_left, $offset);
62                 defined($read) or die "sysread pipe failed: $!\n";
63                 $bytes_left -= $read;
64                 $offset += $read;
65         }
66
67         my $read = read($in, my $buf, 1);
68         defined($read) or die "read pipe failed: $!\n";
69         if ($read != 1 || $buf ne "\n") {
70                 die "newline missing after blob\n";
71         }
72         \$rv;
73 }
74
75 sub DESTROY {
76         my ($self) = @_;
77         my $pid = $self->{pid} or return;
78         $self->{pid} = undef;
79         foreach my $f (qw(in out)) {
80                 my $fh = $self->{$f};
81                 defined $fh or next;
82                 close $fh;
83                 $self->{$f} = undef;
84         }
85         waitpid $pid, 0;
86 }
87
88 1;