From a2bfef2dd80cdbd434f01fe5f7182a9a36c1b905 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 21 Jan 2017 02:29:52 +0000 Subject: repobrowse: git Atom feed uses Qspawn->psgi_return 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 | 215 +++++++++++++++++++++-------------- 1 file changed, 127 insertions(+), 88 deletions(-) (limited to 'lib/PublicInbox') diff --git a/lib/PublicInbox/RepobrowseGitAtom.pm b/lib/PublicInbox/RepobrowseGitAtom.pm index 3031417b..99d79869 100644 --- a/lib/PublicInbox/RepobrowseGitAtom.pm +++ b/lib/PublicInbox/RepobrowseGitAtom.pm @@ -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 .= ''; + $$dst .= utf8_html($hdr->{'s'}); # commit subject + $$dst .= ''; + $$dst .= strftime(DATEFMT, gmtime($hdr->{ct})); + $$dst .= ''; + $$dst .= utf8_html($hdr->{an}); + $$dst .= ''; + $$dst .= utf8_html($hdr->{ae}); + $$dst .= ''; + $$dst .= strftime(DATEFMT, gmtime($hdr->{at})); + $$dst .= ''; + $$dst .= qq(); + $$dst .= $H; + $$dst .= qq(); + + $$dst .= qq(); + $$dst .= qq(); + 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(\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(\n) . qq() . qq($title) . qq($repo_info->{desc_html}) . - qq()); - my $rel = $req->{relcmd}; - my %acache; - local $/ = "\0"; - while (defined(my $s = <$log>)) { - chomp $s; - my $entry = ''; - $entry .= utf8_html($s); # commit subject - $entry .= ''; - - chomp($s = <$log>); # commit time - $entry .= strftime(DATEFMT, gmtime($s)); - $entry .= ''; + qq(); + 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 = ''; + } + + 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(); + $state = 0; + } else { + $dst .= utf8_html($l); + } + } + $dst .= $end; + } +} - chomp($s = <$log>); # author name - $entry .= $acache{$s} ||= utf8_html($s); - $entry .= ''; +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 .= ''; +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 .= ''; + 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(); - chomp($s = <$log>); # unabbreviated commit hash - $entry .= $s; - $entry .= qq(); + sub { + $env->{'qspawn.response'} = $_[0]; + return $read_log->() if $h ne ''; - $entry .= qq(); - $entry .= qq(\n); - chomp($s = <$log>); - $entry .= utf8_html($s); # body - $fh->write($entry .= qq()); - 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(''); } 1; -- cgit v1.2.3-24-ge0c7