]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/GitCatFile.pm
48ae6734bbb23eb378dc84e1dd7c29be9bbe820f
[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
11 sub new {
12         my ($class, $git_dir) = @_;
13         bless { git_dir => $git_dir }, $class;
14 }
15
16 sub _cat_file_begin {
17         my ($self) = @_;
18         return if $self->{pid};
19         my ($in_r, $in_w, $out_r, $out_w);
20
21         pipe($in_r, $in_w) or die "pipe failed: $!\n";
22         pipe($out_r, $out_w) or die "pipe failed: $!\n";
23
24         my @cmd = ('git', "--git-dir=$self->{git_dir}", qw(cat-file --batch));
25         my $pid = fork;
26         defined $pid or die "fork failed: $!\n";
27         if ($pid == 0) {
28                 dup2(fileno($out_r), 0) or die "redirect stdin failed: $!\n";
29                 dup2(fileno($in_w), 1) or die "redirect stdout failed: $!\n";
30                 exec(@cmd) or die 'exec `' . join(' '). "' failed: $!\n";
31         }
32         close $out_r or die "close failed: $!\n";
33         close $in_w or die "close failed: $!\n";
34
35         $self->{in} = $in_r;
36         $self->{out} = $out_w;
37         $self->{pid} = $pid;
38 }
39
40 sub cat_file {
41         my ($self, $object, $sizeref) = @_;
42
43         $object .= "\n";
44         my $len = length($object);
45
46         $self->_cat_file_begin;
47         my $written = syswrite($self->{out}, $object);
48         if (!defined $written) {
49                 die "pipe write error: $!\n";
50         } elsif ($written != $len) {
51                 die "wrote too little to pipe ($written < $len)\n";
52         }
53
54         my $in = $self->{in};
55         my $head = <$in>;
56         $head =~ / missing$/ and return undef;
57         $head =~ /^[0-9a-f]{40} \S+ (\d+)$/ or
58                 die "Unexpected result from git cat-file: $head\n";
59
60         my $size = $1;
61         $$sizeref = $size if $sizeref;
62         my $bytes_left = $size;
63         my $offset = 0;
64         my $rv = '';
65
66         while ($bytes_left) {
67                 my $read = read($in, $rv, $bytes_left, $offset);
68                 defined($read) or die "sysread pipe failed: $!\n";
69                 $bytes_left -= $read;
70                 $offset += $read;
71         }
72
73         my $read = read($in, my $buf, 1);
74         defined($read) or die "read pipe failed: $!\n";
75         if ($read != 1 || $buf ne "\n") {
76                 die "newline missing after blob\n";
77         }
78         \$rv;
79 }
80
81 sub DESTROY {
82         my ($self) = @_;
83         my $pid = $self->{pid} or return;
84         $self->{pid} = undef;
85         foreach my $f (qw(in out)) {
86                 my $fh = $self->{$f};
87                 defined $fh or next;
88                 close $fh;
89                 $self->{$f} = undef;
90         }
91         waitpid $pid, 0;
92 }
93
94 1;