]> git.ipfire.org Git - thirdparty/public-inbox.git/commitdiff
repobrowse: git Atom feed uses Qspawn->psgi_return
authorEric Wong <e@80x24.org>
Sat, 21 Jan 2017 02:29:52 +0000 (02:29 +0000)
committerEric Wong <e@80x24.org>
Sat, 21 Jan 2017 02:32:41 +0000 (02:32 +0000)
This allows us to wait on "git log" output in a non-blocking manner
while being able to throttle on backpressure from slow clients
when used with pi-httpd.

lib/PublicInbox/RepobrowseGitAtom.pm

index 3031417b157b8a850fad36aec30b8fdd6facc4a6..99d79869e8fbba1c88de1e9827a2ac799ad8093c 100644 (file)
@@ -8,52 +8,14 @@ use warnings;
 use PublicInbox::Hval qw(utf8_html);
 use base qw(PublicInbox::RepobrowseBase);
 use PublicInbox::Qspawn;
-my $ATOM_FMT = '--pretty=tformat:'.
-               join('%x00', qw(%s %ct %an %ae %at %h %H %b), '', '');
 
 use constant DATEFMT => '%Y-%m-%dT%H:%M:%SZ';
+use constant STATES => qw(H ct an ae at s b);
+use constant STATE_BODY => (scalar(STATES) - 1);
+my $ATOM_FMT = '--pretty=tformat:'.
+               join('%n', map { "%$_" } STATES).'%x00';
 use POSIX qw(strftime);
 
-sub call_git_atom {
-       my ($self, $req) = @_;
-       my $repo_info = $req->{repo_info};
-       my $max = $repo_info->{max_commit_count} || 10;
-       $max = int($max);
-       $max = 50 if $max == 0;
-
-       my $git = $repo_info->{git};
-       my $env = $req->{env};
-       my $q = PublicInbox::RepobrowseGitQuery->new($env);
-       my $h = $q->{h};
-       my $res;
-
-       my $read_log = sub {
-               my @cmd = (qw(log --no-notes --no-color --abbrev-commit),
-                               $git->abbrev, $ATOM_FMT, "-$max", $h, '--');
-               my $expath = $req->{expath};
-               push @cmd, $expath if $expath ne '';
-               my $log = $git->popen(@cmd);
-               my @h = ( 'Content-Type' => 'application/atom+xml' );
-               my $fh = $res->([200, \@h]);
-               $self->git_atom_stream($req, $q, $log, $fh, $h);
-               $fh->close;
-       };
-
-       sub {
-               $res = $_[0];
-               return $read_log->() if $h ne '';
-
-               my $cmd = [ 'git', "--git-dir=$git->{git_dir}",
-                               qw(symbolic-ref --short HEAD) ];
-               my $rdr = { 2 => $git->err_begin };
-               my $qsp = PublicInbox::Qspawn->new($cmd, undef, undef, $rdr);
-               $qsp->psgi_qx($env, undef, sub {
-                       chomp($h = ${$_[0]});
-                       $read_log->();
-               })
-       }
-}
-
 sub repo_root_url {
        my ($self, $req) = @_;
        my $env = $req->{env};
@@ -70,63 +32,140 @@ sub repo_root_url {
        PublicInbox::Repobrowse::base_url($env) . join('/', @uri);
 }
 
-sub git_atom_stream {
-       my ($self, $req, $q, $log, $fh, $h) = @_;
+sub flush_hdr ($$) {
+       my ($dst, $hdr) = @_;
+       $$dst .= '<entry><title>';
+       $$dst .= utf8_html($hdr->{'s'}); # commit subject
+       $$dst .= '</title><updated>';
+       $$dst .= strftime(DATEFMT, gmtime($hdr->{ct}));
+       $$dst .= '</updated><author><name>';
+       $$dst .= utf8_html($hdr->{an});
+       $$dst .= '</name><email>';
+       $$dst .= utf8_html($hdr->{ae});
+       $$dst .= '</email></author><published>';
+       $$dst .= strftime(DATEFMT, gmtime($hdr->{at}));
+       $$dst .= '</published>';
+       $$dst .= qq(<link\nrel="alternate"\ntype="text/html"\nhref=");
+       $$dst .= $hdr->{url};
+       $$dst .= '/commit?id=';
+       my $H = $hdr->{H};
+       $$dst .= $H;
+       $$dst .= qq("\n/><id>);
+       $$dst .= $H;
+       $$dst .= qq(</id>);
+
+       $$dst .= qq(<content\ntype="xhtml"><div\nxmlns=");
+       $$dst .= qq(http://www.w3.org/1999/xhtml">);
+       $$dst .= qq(<pre\nstyle="white-space:pre-wrap">);
+       undef
+}
+
+sub git_atom_sed_end ($$) {
+       my ($req, $buf) = @_;
+}
+
+sub git_atom_sed ($$) {
+       my ($self, $req) = @_;
+       my $buf = '';
+       my $state = 0;
+       my $rel = $req->{relcmd};
        my $repo_info = $req->{repo_info};
        my $title = join('/', $repo_info->{repo}, @{$req->{extra}});
-       $title = utf8_html("$title, branch $h");
-
-       my $url = $self->repo_root_url($req);
-       $fh->write(qq(<?xml version="1.0"?>\n) .
+       $title = utf8_html("$title, branch $req->{q}->{h}");
+       my $url = repo_root_url($self, $req);
+       my $hdr = $req->{hdr} = { url => $url };
+       $req->{axml} = qq(<?xml version="1.0"?>\n) .
                qq(<feed\nxmlns="http://www.w3.org/2005/Atom">) .
                qq(<title>$title</title>) .
                qq(<subtitle>$repo_info->{desc_html}</subtitle>) .
-               qq(<link\nrel="alternate"\ntype="text/html"\nhref="$url"\n/>));
-       my $rel = $req->{relcmd};
-       my %acache;
-       local $/ = "\0";
-       while (defined(my $s = <$log>)) {
-               chomp $s;
-               my $entry = '<entry><title>';
-               $entry .= utf8_html($s); # commit subject
-               $entry .= '</title><updated>';
-
-               chomp($s = <$log>); # commit time
-               $entry .= strftime(DATEFMT, gmtime($s));
-               $entry .= '</updated><author><name>';
+               qq(<link\nrel="alternate"\ntype="text/html"\nhref="$url"\n/>);
+       my ($plinks, $id, $ai);
+       my $end = '';
+       sub {
+               my $dst;
+               # $_[0] == scalar buffer, undef means EOF from "git log"
+               $dst = delete $req->{axml} || '';
+               my @tmp;
+               if (defined $_[0]) {
+                       $buf .= $_[0];
+                       @tmp = split(/\n/, $buf, -1);
+                       $buf = @tmp ? pop(@tmp) : '';
+               } else {
+                       @tmp = split(/\n/, $buf, -1);
+                       $buf = '';
+                       $end = '</feed>';
+               }
+
+               foreach my $l (@tmp) {
+                       if ($state != STATE_BODY) {
+                               $hdr->{((STATES)[$state])} = $l;
+                               if (++$state == STATE_BODY) {
+                                       flush_hdr(\$dst, $hdr);
+                                       %$hdr = (url => $url);
+                               }
+                               next;
+                       }
+                       if ($l eq "\0") {
+                               $dst .= qq(</pre></div></content></entry>);
+                               $state = 0;
+                       } else {
+                               $dst .= utf8_html($l);
+                       }
+               }
+               $dst .= $end;
+       }
+}
 
-               chomp($s = <$log>); # author name
-               $entry .= $acache{$s} ||= utf8_html($s);
-               $entry .= '</name><email>';
+sub git_atom_cb {
+       my ($self, $req) = @_;
+       sub {
+               my ($r) = @_;
+               my $env = $req->{env};
+               if (!defined $r) {
+                       my $git = $req->{repo_info}->{git};
+                       return [ 400, [ 'Content-Type', 'text/plain' ],
+                               [ $git->err ] ];
+               }
+               $env->{'qspawn.filter'} = git_atom_sed($self, $req);
+               [ 200, [ 'Content-Type', 'application/atom+xml' ] ];
+       }
+}
 
-               chomp($s = <$log>); # author email
-               $entry .= $acache{$s} ||= utf8_html($s);
-               $entry .= '</email></author><published>';
+sub call_git_atom {
+       my ($self, $req) = @_;
+       my $repo_info = $req->{repo_info};
+       my $max = $repo_info->{max_commit_count} || 10;
+       $max = int($max);
+       $max = 50 if $max == 0;
 
-               chomp($s = <$log>); # author time
-               $entry .= strftime(DATEFMT, gmtime($s));
-               $entry .= '</published>';
+       my $git = $repo_info->{git};
+       my $env = $req->{env};
+       my $q =$req->{'q'} = PublicInbox::RepobrowseGitQuery->new($env);
+       my $h = $q->{h};
+       my $git_dir = "--git-dir=$git->{git_dir}";
+       my $read_log = sub {
+               my $cmd = ['git', $git_dir,
+                               qw(log --no-notes --no-color --abbrev-commit),
+                               $git->abbrev, $ATOM_FMT, "-$max", $h, '--' ];
+               my $expath = $req->{expath};
+               push @$cmd, $expath if $expath ne '';
+               my $rdr = { 2 => $git->err_begin };
+               my $qsp = PublicInbox::Qspawn->new($cmd, undef, undef, $rdr);
+               $qsp->psgi_return($env, undef, git_atom_cb($self, $req));
+       };
 
-               $entry .= qq(<link\nrel="alternate"\ntype="text/html"\nhref=");
-               $entry .= $url;
-               chomp($s = <$log>); # abbreviated commit hash for URL
-               $entry .= qq(/commit?id=$s"\n/><id>);
-               chomp($s = <$log>); # unabbreviated commit hash
-               $entry .= $s;
-               $entry .= qq(</id>);
+       sub {
+               $env->{'qspawn.response'} = $_[0];
+               return $read_log->() if $h ne '';
 
-               $entry .= qq(<content\ntype="xhtml"><div\nxmlns=");
-               $entry .= qq(http://www.w3.org/1999/xhtml">);
-               $entry .= qq(<pre\nstyle="white-space:pre-wrap">\n);
-               chomp($s = <$log>);
-               $entry .= utf8_html($s);  # body
-               $fh->write($entry .= qq(</pre></div></content></entry>));
-               eval {
-                       local $/ = "\0\n";
-                       $s = <$log>;
-               };
+               my $cmd = [ 'git', $git_dir, qw(symbolic-ref --short HEAD) ];
+               my $rdr = { 2 => $git->err_begin };
+               my $qsp = PublicInbox::Qspawn->new($cmd, undef, undef, $rdr);
+               $qsp->psgi_qx($env, undef, sub {
+                       chomp($h = ${$_[0]});
+                       $read_log->();
+               })
        }
-       $fh->write('</feed>');
 }
 
 1;