From 33362430ea03e5877ce1d17bbe1da830dcebea3e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 11 Jan 2017 04:12:26 +0000 Subject: repobrowse: qspawn + streaming for git commit display This prevents "git show" processes from monopolizing the system and allows us to better handle backpressure from gigantic commits. --- lib/PublicInbox/GetlineBody.pm | 16 +- lib/PublicInbox/Qspawn.pm | 21 ++- lib/PublicInbox/RepobrowseGitCommit.pm | 238 +++++++---------------------- lib/PublicInbox/RepobrowseGitDiffCommon.pm | 199 +++++++++++++++++++++++- t/repobrowse_git_commit.t | 2 +- 5 files changed, 286 insertions(+), 190 deletions(-) diff --git a/lib/PublicInbox/GetlineBody.pm b/lib/PublicInbox/GetlineBody.pm index 5f327828..ccc66e48 100644 --- a/lib/PublicInbox/GetlineBody.pm +++ b/lib/PublicInbox/GetlineBody.pm @@ -9,8 +9,13 @@ use strict; use warnings; sub new { - my ($class, $rpipe, $end, $buf) = @_; - bless { rpipe => $rpipe, end => $end, buf => $buf }, $class; + my ($class, $rpipe, $end, $buf, $filter) = @_; + bless { + rpipe => $rpipe, + end => $end, + buf => $buf, + filter => $filter || 0, + }, $class; } # close should always be called after getline returns undef, @@ -20,8 +25,13 @@ sub DESTROY { $_[0]->close } sub getline { my ($self) = @_; + my $filter = $self->{filter}; + return if $filter == -1; # last call was EOF + my $buf = delete $self->{buf}; # initial buffer - defined $buf ? $buf : $self->{rpipe}->getline; + $buf = $self->{rpipe}->getline unless defined $buf; + $self->{filter} = -1 unless defined $buf; # set EOF for next call + $filter ? $filter->($buf) : $buf; } sub close { diff --git a/lib/PublicInbox/Qspawn.pm b/lib/PublicInbox/Qspawn.pm index cd1079a0..eead5a5b 100644 --- a/lib/PublicInbox/Qspawn.pm +++ b/lib/PublicInbox/Qspawn.pm @@ -9,6 +9,7 @@ package PublicInbox::Qspawn; use strict; use warnings; use PublicInbox::Spawn qw(popen_rd); +require Plack::Util; my $def_limiter; sub new ($$$;) { @@ -60,11 +61,25 @@ sub start { } } +# create a filter for "push"-based streaming PSGI writes used by HTTPD::Async +sub filter_fh ($$) { + my ($fh, $filter) = @_; + Plack::Util::inline_object( + close => sub { + $fh->write($filter->(undef)); + $fh->close; + }, + write => sub { + $fh->write($filter->($_[0])); + }); +} + sub psgi_return { my ($self, $env, $limiter, $parse_hdr) = @_; my ($fh, $rpipe); my $end = sub { - if (my $err = $self->finish) { + my $err = $self->finish; + if ($err && !$env->{'qspawn.quiet'}) { $err = join(' ', @{$self->{args}->[0]}).": $err\n"; $env->{'psgi.errors'}->print($err); } @@ -84,6 +99,7 @@ sub psgi_return { my $cb = sub { my $r = $rd_hdr->() or return; $rd_hdr = undef; + my $filter = delete $env->{'qspawn.filter'}; if (scalar(@$r) == 3) { # error if ($async) { $async->close; # calls rpipe->close @@ -94,11 +110,12 @@ sub psgi_return { $res->($r); } elsif ($async) { $fh = $res->($r); # scalar @$r == 2 + $fh = filter_fh($fh, $filter) if $filter; $async->async_pass($env->{'psgix.io'}, $fh, \$buf); } else { # for synchronous PSGI servers require PublicInbox::GetlineBody; $r->[2] = PublicInbox::GetlineBody->new($rpipe, $end, - $buf); + $buf, $filter); $res->($r); } }; 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 .= "\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) . "---\n"); $req->{anchors} = {}; $req->{h} = $h; $req->{p} = \@p; + $x .= utf8_html($bx00) . "---\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 .= ''; + } + $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(''); - $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 .= "$try the latest commit in HEAD\n"; $x .= ''; - $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(diff --$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 .= " "; - $rv .= "$off"; - } - } - - # 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( $last); - } - $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 parents, ) . "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); - $s .= "$fn\n"; + $$dst .= qq(\t); + $$dst .= "$fn\n"; } - $fh->write($s); } 1; diff --git a/lib/PublicInbox/RepobrowseGitDiffCommon.pm b/lib/PublicInbox/RepobrowseGitDiffCommon.pm index 9ed24d03..ac38aa0a 100644 --- a/lib/PublicInbox/RepobrowseGitDiffCommon.pm +++ b/lib/PublicInbox/RepobrowseGitDiffCommon.pm @@ -10,6 +10,8 @@ use PublicInbox::Hval qw/utf8_html to_attr/; use base qw/Exporter/; our @EXPORT_OK = qw/git_diffstat_emit git_diff_ab_index git_diff_ab_hdr git_diff_ab_hunk/; +our @EXPORT = qw/git_diff_sed_init git_diff_sed_close git_diff_sed_run + DSTATE_INIT DSTATE_STAT DSTATE_LINES/; # index abcdef89..01234567 sub git_diff_ab_index ($$$) { @@ -41,6 +43,18 @@ sub git_diff_ab_hdr ($$$) { qq(diff --git $html_a $html_b); } +# diff (--cc|--combined) +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(diff --$combined $html_path); +} + # @@ -1,2 +3,4 @@ (regular diff) sub git_diff_ab_hunk ($$$$) { my ($req, $ca, $cb, $ctx) = @_; @@ -72,6 +86,56 @@ sub git_diff_ab_hunk ($$$$) { $rv . ' @@' . utf8_html($ctx); } +# 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 .= " "; + $rv .= "$off"; + } + } + + # 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( $last); + } + $rv .= " $at" . utf8_html($ctx); +} + sub git_diffstat_rename ($$$) { my ($req, $from, $to) = @_; my $anchor = to_attr(git_unquote($to)); @@ -94,7 +158,7 @@ sub git_diffstat_rename ($$$) { @base ? "$base/{$from => $to}" : "$from => $to"; } -sub git_diffstat_emit ($$$) { +sub git_diffstat_emit ($$$) { # XXX deprecated my ($req, $fh, undef) = @_; my @stat = split("\0", $_[2]); # avoiding copy for $_[2] my $nr = 0; @@ -132,4 +196,137 @@ sub git_diffstat_emit ($$$) { $fh->write($s); } +sub DSTATE_INIT () { 0 } +sub DSTATE_STAT () { 1 } +sub DSTATE_LINES () { 2 } + +sub git_diff_sed_init ($) { + my ($req) = @_; + $req->{dbuf} = ''; + $req->{ndiff} = $req->{nchg} = $req->{nadd} = $req->{ndel} = 0; + $req->{dstate} = DSTATE_INIT; +} + +sub git_diff_sed_stat ($$) { + my ($dst, $req) = @_; + my @stat = split(/\0/, $req->{dbuf}, -1); + my $eos; + my $nchg = \($req->{nchg}); + my $nadd = \($req->{nadd}); + my $ndel = \($req->{ndel}); + if (!$req->{dstat_started}) { + $req->{dstat_started} = 1; + if ($req->{mhelp}) { + if ($stat[0] eq '') { + shift @stat; + } else { + warn +'initial merge diffstat line was not empty'; + } + } else { + $stat[0] =~ s/\A\n//s or warn +'failed to remove initial newline from diffstat'; + } + } + while (defined(my $l = shift @stat)) { + if ($l eq '') { + $eos = 1 if $stat[0] && $stat[0] =~ /\Ad/; # "diff --" + last; + } elsif ($l =~ /\Adiff /) { + unshift @stat, $l; + $eos = 1; + last; + } + $l =~ /\A(\S+)\t+(\S+)\t+(.*)/ or next; + my ($add, $del, $fn) = ($1, $2, $3); + if ($fn ne '') { # normal modification + my $anchor = to_attr(git_unquote($fn)); + $req->{anchors}->{$anchor} = $fn; + $l = utf8_html($fn); + $l = qq($l); + } else { # rename + # incomplete... + if (scalar(@stat) < 2) { + unshift @stat, $l; + last; + } + my $from = shift @stat; + my $to = shift @stat; + $l = git_diffstat_rename($req, $from, $to); + } + + # text changes show numerically, Binary does not + if ($add =~ /\A\d+\z/) { + $$nadd += $add; + $$ndel += $del; + $add = "+$add"; + $del = "-$del"; + } + ++$$nchg; + my $num = sprintf('% 6s/%-6s', $del, $add); + $$dst .= " $num\t$l\n"; + } + + $req->{dbuf} = join("\0", @stat); + return unless $eos; + + $req->{dstate} = DSTATE_LINES; + $$dst .= "\n $$nchg "; + $$dst .= $$nchg == 1 ? 'file changed, ' : 'files changed, '; + $$dst .= $$nadd; + $$dst .= $$nadd == 1 ? ' insertion(+), ' : ' insertions(+), '; + $$dst .= $$ndel; + $$dst .= $$ndel == 1 ? " deletion(-)\n\n" : " deletions(-)\n\n"; +} + +sub git_diff_sed_lines ($$) { + my ($dst, $req) = @_; + + # TODO: discard diffs if they are too big + + my @dlines = split(/\n/, $req->{dbuf}, -1); + $req->{dbuf} = ''; + + if (my $help = delete $req->{mhelp}) { + $$dst .= $help; # CC_MERGE + } + + # don't touch the last line, it may not be terminated + $req->{dbuf} .= pop @dlines; + + my $ndiff = \($req->{ndiff}); + my $cmt = '[a-f0-9]+'; + while (defined(my $l = shift @dlines)) { + if ($l =~ m{\Adiff --git ("?a/.+) ("?b/.+)\z}) { # regular + $$dst .= git_diff_ab_hdr($req, $1, $2) . "\n"; + } elsif ($l =~ m{\Adiff --(cc|combined) (.+)\z}) { + $$dst .= git_diff_cc_hdr($req, $1, $2) . "\n"; + } elsif ($l =~ /\Aindex ($cmt)\.\.($cmt)(.*)\z/o) { # regular + $$dst .= git_diff_ab_index($1, $2, $3) . "\n"; + } elsif ($l =~ /\A@@ (\S+) (\S+) @@(.*)\z/) { # regular + $$dst .= git_diff_ab_hunk($req, $1, $2, $3) . "\n"; + } elsif ($l =~ /\Aindex ($cmt,[^\.]+)\.\.($cmt)(.*)$/o) { #--cc + $$dst .= git_diff_cc_index($req, $1, $2, $3) . "\n"; + } elsif ($l =~ /\A(@@@+) (\S+.*\S+) @@@+(.*)\z/) { # --cc + $$dst .= git_diff_cc_hunk($req, $1, $2, $3) . "\n"; + } else { + $$dst .= utf8_html($l) . "\n"; + } + ++$$ndiff; + } +} + +sub git_diff_sed_run ($$) { + my ($dst, $req) = @_; + $req->{dstate} == DSTATE_STAT and git_diff_sed_stat($dst, $req); + $req->{dstate} == DSTATE_LINES and git_diff_sed_lines($dst, $req); + undef; +} + +sub git_diff_sed_close ($$) { + my ($dst, $req) = @_; + $$dst .= utf8_html(delete $req->{dbuf}); + undef; +} + 1; diff --git a/t/repobrowse_git_commit.t b/t/repobrowse_git_commit.t index 969a0b5e..ed2d6d56 100644 --- a/t/repobrowse_git_commit.t +++ b/t/repobrowse_git_commit.t @@ -25,7 +25,7 @@ test_psgi($test->{app}, sub { like($body, qr!\z!, 'response body finished'); $res = $cb->(GET($req.$q)); - is($res->code, 404, 'got 404 response for default'); + is($res->code, 404, 'got 404 response for bad id'); }); done_testing(); -- cgit v1.2.3-24-ge0c7