From aca6555ed4696ba1f24296a63afa7450a5afde2a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Mar 2021 11:45:51 +0000 Subject: lei blob: aka "git-show-harder" for blobs This implements blob reconstruction via SolverGit, emulating the functionality of /$INBOX/$OID/s/ endpoint in PublicInbox::WWW. It uses the current working tree as a coderepo, and accepts any number of --git-dir=$PATH args. Remote externals are not yet supported. v2: use absolute path for git repos --- lib/PublicInbox/LeiBlob.pm | 121 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 lib/PublicInbox/LeiBlob.pm (limited to 'lib/PublicInbox/LeiBlob.pm') diff --git a/lib/PublicInbox/LeiBlob.pm b/lib/PublicInbox/LeiBlob.pm new file mode 100644 index 00000000..2facbad3 --- /dev/null +++ b/lib/PublicInbox/LeiBlob.pm @@ -0,0 +1,121 @@ +# Copyright (C) 2021 all contributors +# License: AGPL-3.0+ + +# "lei blob $OID" command +package PublicInbox::LeiBlob; +use strict; +use v5.10.1; +use parent qw(PublicInbox::IPC); +use PublicInbox::Spawn qw(spawn popen_rd); +use PublicInbox::DS; +use PublicInbox::Eml; + +sub sol_done_wait { # dwaitpid callback + my ($arg, $pid) = @_; + my (undef, $lei) = @$arg; + $lei->child_error($?) if $?; + $lei->dclose; +} + +sub sol_done { # EOF callback for main daemon + my ($lei) = @_; + my $sol = delete $lei->{sol} or return; + $sol->wq_wait_old(\&sol_done_wait, $lei); +} + +sub get_git_dir ($) { + my ($d) = @_; + return $d if -d "$d/objects" && -d "$d/refs" && -e "$d/HEAD"; + + my $cmd = [ qw(git rev-parse --git-dir) ]; + my ($r, $pid) = popen_rd($cmd, {GIT_DIR => undef}, { '-C' => $d }); + chomp(my $gd = do { local $/; <$r> }); + waitpid($pid, 0) == $pid or die "BUG: waitpid @$cmd ($!)"; + $? == 0 ? $gd : undef; +} + +sub solver_user_cb { # called by solver when done + my ($res, $self) = @_; + my $lei = $self->{lei}; + my $log_buf = delete $lei->{'log_buf'}; + $$log_buf =~ s/^/# /sgm; + ref($res) eq 'ARRAY' or return $lei->fail($$log_buf); + $lei->qerr($$log_buf); + my ($git, $oid, $type, $size, $di) = @$res; + my $gd = $git->{git_dir}; + + # don't try to support all the git-show(1) options for non-blob, + # this is just a convenience: + $type ne 'blob' and + $lei->err("# $oid is a $type of $size bytes in:\n#\t$gd"); + + my $cmd = [ 'git', "--git-dir=$gd", 'show', $oid ]; + my $rdr = { 1 => $lei->{1}, 2 => $lei->{2} }; + waitpid(spawn($cmd, $lei->{env}, $rdr), 0); + $lei->child_error($?) if $?; +} + +sub do_solve_blob { # via wq_do + my ($self) = @_; + my $lei = $self->{lei}; + my $git_dirs = $lei->{opt}->{'git-dir'}; + my $hints = {}; + for my $x (qw(oid-a path-a path-b)) { + my $v = $lei->{opt}->{$x} // next; + $x =~ tr/-/_/; + $hints->{$x} = $v; + } + open my $log, '+>', \(my $log_buf = '') or die "PerlIO::scalar: $!"; + $lei->{log_buf} = \$log_buf; + my $git = $lei->ale->git; + my $solver = bless { + gits => [ map { + PublicInbox::Git->new($lei->rel2abs($_)) + } @$git_dirs ], + user_cb => \&solver_user_cb, + uarg => $self, + # -cur_di, -qsp, -msg => temporary fields for Qspawn callbacks + inboxes => [ $self->{lxs}->locals ], + }, 'PublicInbox::SolverGit'; + $lei->{env}->{'psgi.errors'} = $lei->{2}; # ugh... + local $PublicInbox::DS::in_loop = 0; # waitpid synchronously + $solver->solve($lei->{env}, $log, $self->{oid_b}, $hints); +} + +sub lei_blob { + my ($lei, $blob) = @_; + $lei->start_pager if -t $lei->{1}; + + # first, see if it's a blob returned by "lei q" JSON output: + my $rdr = { 1 => $lei->{1} }; + open $rdr->{2}, '>', '/dev/null' or die "open: $!"; + my $cmd = [ 'git', '--git-dir='.$lei->ale->git->{git_dir}, + 'cat-file', 'blob', $blob ]; + waitpid(spawn($cmd, $lei->{env}, $rdr), 0); + return if $? == 0; + + # maybe it's a non-email (code) blob from a coderepo + my $git_dirs = $lei->{opt}->{'git-dir'} //= []; + if ($lei->{opt}->{'cwd'} //= 1) { + my $cgd = get_git_dir('.'); + unshift(@$git_dirs, $cgd) if defined $cgd; + } + my $lxs = $lei->lxs_prepare or return; + require PublicInbox::SolverGit; + my $self = bless { lxs => $lxs, oid_b => $blob }, __PACKAGE__; + my $op = $lei->workers_start($self, 'lei_solve', 1, + { '' => [ \&sol_done, $lei ] }); + $lei->{sol} = $self; + $self->wq_io_do('do_solve_blob', []); + $self->wq_close(1); + while ($op && $op->{sock}) { $op->event_step } +} + +sub ipc_atfork_child { + my ($self) = @_; + $self->{lei}->_lei_atfork_child; + $SIG{__WARN__} = PublicInbox::Eml::warn_ignore_cb(); + $self->SUPER::ipc_atfork_child; +} + +1; -- cgit v1.2.3-24-ge0c7