about summary refs log tree commit homepage
path: root/lib/PublicInbox/RepobrowseGitCommit.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/PublicInbox/RepobrowseGitCommit.pm')
-rw-r--r--lib/PublicInbox/RepobrowseGitCommit.pm238
1 files changed, 55 insertions, 183 deletions
diff --git a/lib/PublicInbox/RepobrowseGitCommit.pm b/lib/PublicInbox/RepobrowseGitCommit.pm
index 2577ea34..328d2d40 100644
--- a/lib/PublicInbox/RepobrowseGitCommit.pm
+++ b/lib/PublicInbox/RepobrowseGitCommit.pm
@@ -17,8 +17,8 @@ use warnings;
 use base qw(PublicInbox::RepobrowseBase);
 use PublicInbox::Hval qw(utf8_html to_attr);
 use PublicInbox::RepobrowseGit qw(git_unquote git_commit_title);
-use PublicInbox::RepobrowseGitDiffCommon qw/git_diffstat_emit
-        git_diff_ab_index git_diff_ab_hdr git_diff_ab_hunk/;
+use PublicInbox::RepobrowseGitDiffCommon;
+use PublicInbox::Qspawn;
 
 use constant GIT_FMT => '--pretty=format:'.join('%n',
         '%H', '%h', '%s', '%an <%ae>', '%ai', '%cn <%ce>', '%ci',
@@ -29,14 +29,12 @@ use constant CC_MERGE => " This is a merge, showing combined diff:\n\n";
 
 sub commit_header {
         my ($self, $req) = @_;
-        my $res = delete $req->{res} or die "BUG: missing res\n";
         my ($H, $h, $s, $au, $ad, $cu, $cd, $t, $p, $D, $rest) =
                 split("\n", $req->{dbuf}, 11);
         $s = utf8_html($s);
         $au = utf8_html($au);
         $cu = utf8_html($cu);
         my @p = split(' ', $p);
-        my $fh = $req->{fh} = $res->([200, ['Content-Type'=>'text/html']]);
 
         my $rel = $req->{relcmd};
         my $q = $req->{'q'};
@@ -70,7 +68,7 @@ sub commit_header {
                 my $p = $p[0];
                 $x .= git_parent_line('   parent', $p, $q, $git, $rel, $path);
         } elsif ($np > 1) {
-                $req->{help} = CC_MERGE;
+                $req->{mhelp} = CC_MERGE;
                 my @common = ($q, $git, $rel, $path);
                 my @t = @p;
                 my $p = shift @t;
@@ -83,68 +81,45 @@ sub commit_header {
         $x .= $s;
         $x .= "</b>\n\n";
         my $bx00;
+
+        # FIXME: deal with excessively long commit message bodies
         ($bx00, $req->{dbuf}) = split("\0", $rest, 2);
-        $fh->write($x .= utf8_html($bx00) . "<a\nid=D>---</a>\n");
         $req->{anchors} = {};
         $req->{h} = $h;
         $req->{p} = \@p;
+        $x .= utf8_html($bx00) . "<a\nid=D>---</a>\n";
 }
 
-sub git_diff_cc_line_i ($$) {
-        my ($req, $l) = @_;
-        my $cmt = '[a-f0-9]+';
-
-        if ($l =~ m{^diff --git ("?a/.+) ("?b/.+)$}) { # regular
-                git_diff_ab_hdr($req, $1, $2) . "\n";
-        } elsif ($l =~ m{^diff --(cc|combined) (.+)$}) {
-                git_diff_cc_hdr($req, $1, $2) . "\n";
-        } elsif ($l =~ /^index ($cmt)\.\.($cmt)(.*)$/o) { # regular
-                git_diff_ab_index($1, $2, $3) . "\n";
-        } elsif ($l =~ /^@@ (\S+) (\S+) @@(.*)$/) { # regular
-                git_diff_ab_hunk($req, $1, $2, $3) . "\n";
-        } elsif ($l =~ /^index ($cmt,[^\.]+)\.\.($cmt)(.*)$/o) { # --cc
-                git_diff_cc_index($req, $1, $2, $3) . "\n";
-        } elsif ($l =~ /^(@@@+) (\S+.*\S+) @@@+(.*)$/) { # --cc
-                git_diff_cc_hunk($req, $1, $2, $3) . "\n";
-        } else {
-                utf8_html($l) . "\n";
-        }
-}
-
-sub git_commit_stream ($$$$) {
-        my ($self, $req, $fail, $end) = @_;
+sub git_commit_sed ($$) {
+        my ($self, $req) = @_;
+        git_diff_sed_init($req);
         my $dbuf = \($req->{dbuf});
-        my $off = length($$dbuf);
-        my $n = $req->{rpipe}->sysread($$dbuf, 8192, $off);
-        return $fail->() unless defined $n;
-        return $end->() if $n == 0;
-        my $res = $req->{res};
-        if ($res) {
-                return if index($$dbuf, "\0") < 0;
-                commit_header($self, $req);
-                return if $$dbuf eq '';
-        }
-        my $fh = $req->{fh};
-        if (!$req->{diff_state}) {
-                my ($stat, $buf) = split("\0\0", $$dbuf, 2);
-                return unless defined $buf;
-                $$dbuf = $buf;
-                git_diffstat_emit($req, $fh, $stat);
-                $req->{diff_state} = 1;
-        }
-        my @buf = split("\n", $$dbuf, -1);
-        $$dbuf = pop @buf; # last line, careful...
-        if (@buf) {
-                my $s = delete($req->{help}) || '';
-                $s .= git_diff_cc_line_i($req, $_) foreach @buf;
-                $fh->write($s) if $s ne '';
+
+        # this filters for $fh->write or $body->getline (see Qspawn)
+        sub {
+                my $dst = '';
+                if (defined $_[0]) { # $_[0] == scalar buffer
+                        $$dbuf .= $_[0];
+                        if ($req->{dstate} == DSTATE_INIT) {
+                                return $dst if index($$dbuf, "\0") < 0;
+                                $req->{dstate} = DSTATE_STAT;
+                                $dst .= commit_header($self, $req);
+                        }
+                        git_diff_sed_run(\$dst, $req);
+                } else { # undef means EOF from "git show", flush the last bit
+                        git_diff_sed_close(\$dst, $req);
+                        $dst .= CC_EMPTY if delete $req->{mhelp};
+                        show_unchanged(\$dst, $req);
+                        $dst .= '</pre></body></html>';
+                }
+                $dst;
         }
 }
 
-sub call_git_commit {
+sub call_git_commit { # RepobrowseBase calls this
         my ($self, $req) = @_;
-
-        my $q = PublicInbox::RepobrowseGitQuery->new($req->{env});
+        my $env = $req->{env};
+        my $q = PublicInbox::RepobrowseGitQuery->new($env);
         my $id = $q->{id};
         $id eq '' and $id = 'HEAD';
 
@@ -156,72 +131,30 @@ sub call_git_commit {
         }
 
         my $git = $req->{repo_info}->{git};
-        my $cmd = [ qw(show -z --numstat -p --encoding=UTF-8
+        my $cmd = [ 'git', "--git-dir=$git->{git_dir}", qw(show
+                        -z --numstat -p --encoding=UTF-8
                         --no-notes --no-color -c),
                         $git->abbrev, GIT_FMT, $id, '--' ];
-        $req->{rpipe} = $git->popen($cmd, undef, { 2 => $git->err_begin });
-        my $env = $req->{env};
-        my $err = $env->{'psgi.errors'};
-        my $vin;
-        $req->{dbuf} = '';
-        my $end = sub {
-                if (my $fh = delete $req->{fh}) {
-                        my $dbuf = delete $req->{dbuf};
-                        if (!$req->{diff_state}) {
-                                my ($stat, $buf) = split("\0\0", $dbuf, 2);
-                                $dbuf = defined $buf ? $buf : '';
-                                git_diffstat_emit($req, $fh, $stat);
-                                $req->{diff_state} = 1;
-                        }
-                        my @buf = split("\n", $dbuf, -1);
-                        if (@buf) {
-                                my $s = delete($req->{help}) || '';
-                                $s .= git_diff_cc_line_i($req, $_) foreach @buf;
-                                $fh->write($s) if $s ne '';
-                        }
-                        $fh->write(CC_EMPTY) if delete($req->{help});
-                        show_unchanged($req, $fh);
-                        $fh->write('</pre></body></html>');
-                        $fh->close;
-                } elsif (my $res = delete $req->{res}) {
-                        git_commit_404($req, $res);
-                }
-                if (my $rpipe = delete $req->{rpipe}) {
-                        $rpipe->close; # _may_ be Danga::Socket::close
-                }
-                # zero the error file for now, be careful about printing
-                # $id to psgi.errors w/o sanitizing...
-                $git->err;
-        };
-        my $fail = sub {
-                if ($!{EAGAIN} || $!{EINTR}) {
-                        select($vin, undef, undef, undef) if defined $vin;
-                        # $vin is undef on async, so this is a noop on EAGAIN
-                        return;
-                }
-                my $e = $!;
-                $end->();
-                $err->print("git show ($git->{git_dir}): $e\n");
-        };
+        my $rdr = { 2 => $git->err_begin };
+        my $qsp = PublicInbox::Qspawn->new($cmd, undef, $rdr);
         $req->{'q'} = $q;
-        my $cb = sub { # read git-show output and stream to client
-                git_commit_stream($self, $req, $fail, $end);
-        };
-        if (my $async = $env->{'pi-httpd.async'}) {
-                $req->{rpipe} = $async->($req->{rpipe}, $cb);
-                sub { $req->{res} = $_[0] } # let Danga::Socket handle the rest
-        } else { # synchronous loop for other PSGI servers
-                $vin = '';
-                vec($vin, fileno($req->{rpipe}), 1) = 1;
-                sub {
-                        $req->{res} = $_[0];
-                        while ($req->{rpipe}) { $cb->() }
+        $env->{'qspawn.quiet'} = 1;
+        $qsp->psgi_return($env, undef, sub { # parse header
+                my ($r, $bref) = @_;
+                if (!defined $r) {
+                        my $errmsg = $git->err;
+                        [ 500, [ 'Content-Type', 'text/html' ], [ $errmsg ] ];
+                } elsif ($r == 0) {
+                        git_commit_404($req);
+                } else {
+                        $env->{'qspawn.filter'} = git_commit_sed($self, $req);
+                        [ 200, [ 'Content-Type', 'text/html' ] ];
                 }
-        }
+        });
 }
 
 sub git_commit_404 {
-        my ($req, $res) = @_;
+        my ($req) = @_;
         my $x = 'Missing commit or path';
         my $pfx = "$req->{relcmd}commit";
 
@@ -231,70 +164,10 @@ sub git_commit_404 {
         $x .= "<a\nhref=\"$pfx$qs\">$try the latest commit in HEAD</a>\n";
         $x .= '</pre></body>';
 
-        $res->([404, ['Content-Type'=>'text/html'], [ $x ]]);
-}
-
-sub git_diff_cc_hdr {
-        my ($req, $combined, $path) = @_;
-        my $html_path = utf8_html($path);
-        $path = git_unquote($path);
-        my $anchor = to_attr($path);
-        delete $req->{anchors}->{$anchor};
-        my $cc = $req->{cc} = PublicInbox::Hval->utf8($path);
-        $req->{path_cc} = $cc->as_path;
-        qq(<a\nid="$anchor">diff</a> --$combined $html_path);
-}
-
-# index abcdef09,01234567..76543210
-sub git_diff_cc_index {
-        my ($req, $before, $last, $end) = @_;
-        $end = utf8_html($end);
-        my @before = split(',', $before);
-        $req->{pobj_cc} = \@before;
-
-        # not wasting bandwidth on links here, yet
-        # links in hunk headers are far more useful with line offsets
-        "index $before..$last$end";
-}
-
-# @@@ -1,2 -3,4 +5,6 @@@ (combined diff)
-sub git_diff_cc_hunk {
-        my ($req, $at, $offs, $ctx) = @_;
-        my @offs = split(' ', $offs);
-        my $last = pop @offs;
-        my @p = @{$req->{p}};
-        my @pobj = @{$req->{pobj_cc}};
-        my $path = $req->{path_cc};
-        my $rel = $req->{relcmd};
-        my $rv = $at;
-
-        # special 'cc' action as we don't have reliable paths from parents
-        my $ppath = "${rel}cc/$path";
-        foreach my $off (@offs) {
-                my $p = shift @p;
-                my $obj = shift @pobj; # blob SHA-1
-                my ($n) = ($off =~ /\A-(\d+)/); # line number
-
-                if ($n == 0) { # new file (does this happen with --cc?)
-                        $rv .= " $off";
-                } else {
-                        $rv .= " <a\nhref=\"$ppath?id=$p&obj=$obj#n$n\">";
-                        $rv .= "$off</a>";
-                }
-        }
-
-        # we can use the normal 'tree' endpoint for the result
-        my ($n) = ($last =~ /\A\+(\d+)/); # line number
-        if ($n == 0) { # deleted file (does this happen with --cc?)
-                $rv .= " $last";
-        } else {
-                my $h = $req->{h};
-                $rv .= qq( <a\nrel=nofollow);
-                $rv .= qq(\nhref="${rel}tree/$path?id=$h#n$n">$last</a>);
-        }
-        $rv .= " $at" . utf8_html($ctx);
+        [404, ['Content-Type'=>'text/html'], [ $x ]];
 }
 
+# FIXME: horrifically expensive...
 sub git_parent_line {
         my ($pfx, $p, $q, $git, $rel, $path) = @_;
         my $qs = $q->qs(id => $p);
@@ -305,12 +178,12 @@ sub git_parent_line {
 
 # do not break anchor links if the combined diff doesn't show changes:
 sub show_unchanged {
-        my ($req, $fh) = @_;
+        my ($dst, $req) = @_;
 
         my @unchanged = sort keys %{$req->{anchors}};
         return unless @unchanged;
         my $anchors = $req->{anchors};
-        my $s = "\n There are uninteresting changes from this merge.\n" .
+        $$dst .= "\n There are uninteresting changes from this merge.\n" .
                 qq( See the <a\nhref="#P">parents</a>, ) .
                 "or view final state(s) below:\n\n";
         my $rel = $req->{relcmd};
@@ -320,11 +193,10 @@ sub show_unchanged {
                 my $p = PublicInbox::Hval->utf8(git_unquote($fn));
                 $p = $p->as_path;
                 $fn = utf8_html($fn);
-                $s .= qq(\t<a\nrel=nofollow);
-                $s .= qq(\nid="$anchor"\nhref="${rel}tree/$p$qs">);
-                $s .= "$fn</a>\n";
+                $$dst .= qq(\t<a\nrel=nofollow);
+                $$dst .= qq(\nid="$anchor"\nhref="${rel}tree/$p$qs">);
+                $$dst .= "$fn</a>\n";
         }
-        $fh->write($s);
 }
 
 1;