debug log seek error'; } $log = do { local $/; <$log> } // do { warn "readline(log): $!"; return '
debug log read error'; }; $ctx->{-linkify} //= PublicInbox::Linkify->new; "
debug log:\n\n". $ctx->{-linkify}->to_html($log).''; } sub stream_blob_parse_hdr { # {parse_hdr} for Qspawn my ($r, $bref, $ctx) = @_; my ($git, $oid, $type, $size, $di) = @{$ctx->{-res}}; my @cl = ('Content-Length', $size); if (!defined $r) { # sysread error html_page($ctx, 500, dbg_log($ctx)); } elsif (index($$bref, "\0") >= 0) { [200, [qw(Content-Type application/octet-stream), @cl] ]; } else { my $n = length($$bref); if ($n >= $BIN_DETECT || $n == $size) { return [200, [ 'Content-Type', 'text/plain; charset=UTF-8', @cl ] ]; } if ($r == 0) { my $log = dbg_log($ctx); warn "premature EOF on $oid $log"; return html_page($ctx, 500, $log); } undef; # bref keeps growing } } sub stream_large_blob ($$) { my ($ctx, $res) = @_; $ctx->{-res} = $res; my ($git, $oid, $type, $size, $di) = @$res; my $cmd = ['git', "--git-dir=$git->{git_dir}", 'cat-file', $type, $oid]; my $qsp = PublicInbox::Qspawn->new($cmd); my $env = $ctx->{env}; $env->{'qspawn.wcb'} = delete $ctx->{-wcb}; $qsp->psgi_return($env, undef, \&stream_blob_parse_hdr, $ctx); } sub show_other_result ($$) { # tag my ($bref, $ctx) = @_; if (my $qsp_err = delete $ctx->{-qsp_err}) { return html_page($ctx, 500, dbg_log($ctx) . "git show error:$qsp_err"); } my $l = PublicInbox::Linkify->new; utf8::decode($$bref); html_page($ctx, 200, '
', $l->to_html($$bref), '
glossary -------- Commit objects reference one tree, and zero or more parents. Single parent commits can typically generate a patch in unified diff format via `git format-patch'. Multiple parents means the commit is a merge. Root commits have no ancestor. Note that it is possible to have multiple root commits when merging independent histories. Every commit references one top-level tree object.EOM delete($ctx->{env}->{'qspawn.wcb'})->($ctx->html_done($x)); } sub stream_patch_parse_hdr { # {parse_hdr} for Qspawn my ($r, $bref, $ctx) = @_; if (!defined $r) { # sysread error html_page($ctx, 500, dbg_log($ctx)); } elsif (index($$bref, "\n\n") >= 0) { my $eml = bless { hdr => $bref }, 'PublicInbox::Eml'; my $fn = to_filename($eml->header('Subject') // ''); $fn = substr($fn // 'PATCH-no-subject', 6); # drop "PATCH-" return [ 200, [ 'Content-Type', 'text/plain; charset=UTF-8', 'Content-Disposition', qq(inline; filename=$fn.patch) ] ]; } elsif ($r == 0) { my $log = dbg_log($ctx); warn "premature EOF on $ctx->{patch_oid} $log"; return html_page($ctx, 500, $log); } else { undef; # bref keeps growing until "\n\n" } } sub show_patch ($$) { my ($ctx, $res) = @_; my ($git, $oid) = @$res; my @cmd = ('git', "--git-dir=$git->{git_dir}", qw(format-patch -1 --stdout -C), "--signature=git format-patch -1 --stdout -C $oid", $oid); my $qsp = PublicInbox::Qspawn->new(\@cmd); $ctx->{env}->{'qspawn.wcb'} = delete $ctx->{-wcb}; $ctx->{patch_oid} = $oid; $qsp->psgi_return($ctx->{env}, undef, \&stream_patch_parse_hdr, $ctx); } sub show_commit ($$) { my ($ctx, $res) = @_; return show_patch($ctx, $res) if ($ctx->{fn} // '') =~ /\.patch\z/; my ($git, $oid) = @$res; # patch-id needs two passes, and we use the initial show to ensure # a patch embedded inside the commit message body doesn't get fed # to patch-id: my $cmd = [ '/bin/sh', '-c', "git show --encoding=UTF-8 '$SHOW_FMT'". " -z --no-notes --no-patch $oid >h && ". 'git show --encoding=UTF-8 --pretty=format:%n -M'. " --stat -p $oid >p && ". "git patch-id --stable
$git->{git_dir} }; my $qsp = PublicInbox::Qspawn->new($cmd, $e, { -C => "$ctx->{-tmp}" }); $qsp->{qsp_err} = \($ctx->{-qsp_err} = ''); $ctx->{env}->{'qspawn.wcb'} = delete $ctx->{-wcb}; $ctx->{git} = $git; $qsp->psgi_qx($ctx->{env}, undef, \&show_commit_start, $ctx); } sub show_other ($$) { my ($ctx, $res) = @_; my ($git, $oid, $type, $size) = @$res; $size > $MAX_SIZE and return html_page($ctx, 200, ascii_html($type)." $oid is too big to show\n". dbg_log($ctx)); my $cmd = ['git', "--git-dir=$git->{git_dir}", qw(show --encoding=UTF-8 --no-color --no-abbrev), $oid ]; my $qsp = PublicInbox::Qspawn->new($cmd); $qsp->{qsp_err} = \($ctx->{-qsp_err} = ''); $qsp->psgi_qx($ctx->{env}, undef, \&show_other_result, $ctx); } sub show_tree_result ($$) { my ($bref, $ctx) = @_; if (my $qsp_err = delete $ctx->{-qsp_err}) { return html_page($ctx, 500, dbg_log($ctx) . "git ls-tree -z error:$qsp_err"); } my @ent = split(/\0/, $$bref); my $qp = delete $ctx->{qp}; my $l = $ctx->{-linkify} //= PublicInbox::Linkify->new; my $pfx = $qp->{b}; $$bref = "
tree $ctx->{tree_oid}"; if (defined $pfx) { my $x = ascii_html($pfx); $pfx .= '/'; $$bref .= qq( path: $x\n); } else { $pfx = ''; $$bref .= qq[ (path unknown)\n]; } my ($x, $m, $t, $oid, $sz, $f, $n); $$bref .= "\n size name"; for (@ent) { ($x, $f) = split(/\t/, $_, 2); undef $_; ($m, $t, $oid, $sz) = split(/ +/, $x, 4); $m = $GIT_MODE{$m} // '?'; utf8::decode($f); $n = ascii_html($f); if ($m eq 'g') { # gitlink submodule commit $$bref .= "\ng\t\t$n @ commit$oid"; next; } my $q = 'b='.ascii_html(uri_escape_path($pfx.$f)); if ($m eq 'd') { $n .= '/' } elsif ($m eq 'x') { $n = "$n" } elsif ($m eq 'l') { $n = "$n" } $$bref .= qq(\n$m\t$sz\t$n); } $$bref .= dbg_log($ctx); $$bref .= <EOM chop $$bref; html_page($ctx, 200, $$bref); } sub show_tree ($$) { my ($ctx, $res) = @_; my ($git, $oid, undef, $size) = @$res; $size > $MAX_SIZE and return html_page($ctx, 200, "tree $oid is too big to show\n". dbg_log($ctx)); my $cmd = [ 'git', "--git-dir=$git->{git_dir}", qw(ls-tree -z -l --no-abbrev), $oid ]; my $qsp = PublicInbox::Qspawn->new($cmd); $ctx->{tree_oid} = $oid; $qsp->{qsp_err} = \($ctx->{-qsp_err} = ''); $qsp->psgi_qx($ctx->{env}, undef, \&show_tree_result, $ctx); } # user_cb for SolverGit, called as: user_cb->($result_or_error, $uarg) sub solve_result { my ($res, $ctx) = @_; my $hints = delete $ctx->{hints}; $res or return html_page($ctx, 404, dbg_log($ctx)); ref($res) eq 'ARRAY' or return html_page($ctx, 500, dbg_log($ctx)); my ($git, $oid, $type, $size, $di) = @$res; return show_commit($ctx, $res) if $type eq 'commit'; return show_tree($ctx, $res) if $type eq 'tree'; return show_other($ctx, $res) if $type ne 'blob'; my $path = to_filename($di->{path_b} // $hints->{path_b} // 'blob'); my $raw_link = "(raw)"; if ($size > $MAX_SIZE) { return stream_large_blob($ctx, $res) if defined $ctx->{fn}; return html_page($ctx, 200, <glossary -------- Tree objects belong to commits or other tree objects. Trees may reference blobs, sub-trees, or commits of submodules. Path names are stored in tree objects, but trees do not know their own path name. A tree's path name comes from their parent tree, or it is the root tree referenced by a commit object. Thus, this web UI relies on the `b=' URI parameter as a hint to display the path name. Commit objects may be stored in trees to reference submodules.
$e".dbg_log($ctx)) } my $bin = index(substr($$blob, 0, $BIN_DETECT), "\0") >= 0; if (defined $ctx->{fn}) { my $h = [ 'Content-Length', $size, 'Content-Type' ]; push(@$h, ($bin ? 'application/octet-stream' : 'text/plain')); return delete($ctx->{-wcb})->([200, $h, [ $$blob ]]); } $bin and return html_page($ctx, 200, "
blob $oid $size bytes (binary)" . " $raw_link".dbg_log($ctx)); # TODO: detect + convert to ensure validity utf8::decode($$blob); my $nl = ($$blob =~ s/\r?\n/\n/sg); my $pad = length($nl); ($ctx->{-linkify} //= PublicInbox::Linkify->new)->linkify_1($$blob); my $ok = $hl->do_hl($blob, $path) if $hl; if ($ok) { $blob = $ok; } else { $$blob = ascii_html($$blob); } my $x = "
blob $oid $size bytes $raw_link" . "
"; $x .= sprintf("% ${pad}u\n", $_) for (1..$nl); $x .= ' |
|