X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=lib%2FPublicInbox%2FViewVCS.pm;h=3b4fa393ed3ff16436db04a0dc3259d62825edbf;hb=35b7adee1f01a7cce720d944e1666aa44717d024;hp=84358d0ebd7fc71e0ede9aeade21e5121de6925a;hpb=e0237a630f249594aeb9649fc2a405a8550cc0ee;p=public-inbox.git diff --git a/lib/PublicInbox/ViewVCS.pm b/lib/PublicInbox/ViewVCS.pm index 84358d0e..3b4fa393 100644 --- a/lib/PublicInbox/ViewVCS.pm +++ b/lib/PublicInbox/ViewVCS.pm @@ -21,7 +21,7 @@ use PublicInbox::GitAsyncCat; use PublicInbox::WwwStream qw(html_oneshot); use PublicInbox::Linkify; use PublicInbox::Tmpfile; -use PublicInbox::ViewDiff qw(flush_diff); +use PublicInbox::ViewDiff qw(flush_diff uri_escape_path); use PublicInbox::View; use PublicInbox::Eml; use Text::Wrap qw(wrap); @@ -37,6 +37,14 @@ my $BIN_DETECT = 8000; # same as git my $SHOW_FMT = '--pretty=format:'.join('%n', '%P', '%p', '%H', '%T', '%s', '%f', '%an <%ae> %ai', '%cn <%ce> %ci', '%b%x00'); +my %GIT_MODE = ( + '100644' => ' ', # blob + '100755' => 'x', # executable blob + '040000' => 'd', # tree + '120000' => 'l', # symlink + '160000' => 'g', # commit (gitlink) +); + sub html_page ($$;@) { my ($ctx, $code) = @_[0, 1]; my $wcb = delete $ctx->{-wcb}; @@ -57,7 +65,7 @@ sub dbg_log ($) { return '
debug log read error'; }; $ctx->{-linkify} //= PublicInbox::Linkify->new; - '
debug log:
'. + "debug log:\n\n". $ctx->{-linkify}->to_html($log).''; } @@ -95,7 +103,7 @@ sub stream_large_blob ($$) { $qsp->psgi_return($env, undef, \&stream_blob_parse_hdr, $ctx); } -sub show_other_result ($$) { # tag, tree, ... +sub show_other_result ($$) { # tag my ($bref, $ctx) = @_; if (my $qsp_err = delete $ctx->{-qsp_err}) { return html_page($ctx, 500, dbg_log($ctx) . @@ -128,8 +136,7 @@ sub show_commit_start { # ->psgi_qx callback chop(my $buf = do { local $/ = "\0"; <$fh> }); chomp $buf; my ($P, $p); - ($P, $p, @$ctx{qw(cmt_H cmt_T cmt_s cmt_f cmt_au cmt_co cmt_b)}) - = split(/\n/, $buf, 9); + ($P, $p, @{$ctx->{cmt_info}}) = split(/\n/, $buf, 9); return cmt_finalize($ctx) if !$P; @{$ctx->{-cmt_P}} = split(/ /, $P); @{$ctx->{-cmt_p}} = split(/ /, $p); # abbreviated @@ -148,8 +155,9 @@ sub show_commit_start { # ->psgi_qx callback sub cmt_finalize { my ($ctx) = @_; $ctx->{-linkify} //= PublicInbox::Linkify->new; + my $upfx = $ctx->{-upfx} = '../../'; # from "/$INBOX/$OID/s/" + my ($H, $T, $s, $f, $au, $co, $bdy) = @{delete $ctx->{cmt_info}}; # try to keep author and committer dates lined up - my ($au, $co) = delete @$ctx{qw(cmt_au cmt_co)}; my $x = length($au) - length($co); if ($x > 0) { $x = ' ' x $x; @@ -159,47 +167,51 @@ sub cmt_finalize { $au =~ s/>/>$x/; } $_ = ascii_html($_) for ($au, $co); - my $s = $ctx->{-linkify}->to_html(delete $ctx->{cmt_s}); - $ctx->{-title_html} = $s; - my $upfx = $ctx->{-upfx} = '../../'; # from "/$INBOX/$OID/s/" + $au =~ s!(> +)([0-9]{4,}-\S+ \S+)! + my ($gt, $t) = ($1, $2); + $t =~ tr/ :-//d; + qq($gt$2) + !e; + $ctx->{-title_html} = $s = $ctx->{-linkify}->to_html($s); my ($P, $p, $pt) = delete @$ctx{qw(-cmt_P -cmt_p -cmt_pt)}; $_ = qq().shift(@$p).' '.shift(@$pt) for @$P; if (@$P == 1) { $x = qq{ ({cmt_f}.patch">patch)\n parent $P->[0]}; +href="$f.patch">patch)\n parent $P->[0]}; } elsif (@$P > 1) { - $x = qq(\n parents $P->[0]\n); + $x = qq(\n parents $P->[0]\n); shift @$P; $x .= qq( $_\n) for @$P; chop $x; } else { - $x = ' (root commit)'; + $x = ' (root commit)'; } PublicInbox::WwwStream::html_init($ctx); $ctx->zmore(<commit $ctx->{cmt_H}$x - tree $ctx->{cmt_T} + commit $H$x + tree $T author $au committer $co $s EOM - $x = delete $ctx->{cmt_b}; - $ctx->zmore("\n", $ctx->{-linkify}->to_html($x)) if length($x); - undef $x; + $ctx->zmore("\n", $ctx->{-linkify}->to_html($bdy)) if length($bdy); + $bdy = ''; open my $fh, '<:utf8', "$ctx->{-tmp}/p" or die "open $ctx->{-tmp}/p: $!"; if (-s $fh > $MAX_SIZE) { $ctx->zmore("---\n patch is too large to show\n"); } else { # prepare flush_diff: - $ctx->{obuf} = \$x; + read($fh, $x, -s _); + $ctx->{obuf} = \$bdy; $ctx->{-apfx} = $ctx->{-spfx} = $upfx; - read($fh, my $bdy, -s _); - $bdy =~ s/\r?\n/\n/gs; - $ctx->{-anchors} = {} if $bdy =~ /^diff --git /sm; - flush_diff($ctx, \$bdy); # undefs $bdy - $ctx->zmore($x); - undef $x; + $x =~ s/\r?\n/\n/gs; + $ctx->{-anchors} = {} if $x =~ /^diff --git /sm; + flush_diff($ctx, \$x); # undefs $x + $ctx->zmore($bdy); + undef $bdy; # TODO: should there be another textarea which attempts to # search for the exact email which was applied to make this # commit? @@ -226,7 +238,24 @@ id=related>EOM } @@ -332,7 +434,7 @@ EOM } $bin and return html_page($ctx, 200, - "find related emails, including ancestors/descendants/conflicts EOM } } - $x = $ctx->zflush($ctx->_html_end); + chop($x = <+blob $oid $size bytes $raw_link 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 + $x = $ctx->zflush($x, $ctx->_html_end); my $res_hdr = delete $ctx->{-res_hdr}; push @$res_hdr, 'Content-Length', length($x); delete($ctx->{env}->{'qspawn.wcb'})->([200, $res_hdr, [$x]]); @@ -289,7 +318,7 @@ sub show_other ($$) { my ($ctx, $res) = @_; my ($git, $oid, $type, $size) = @$res; $size > $MAX_SIZE and return html_page($ctx, 200, - "$oid is too big to show\n". dbg_log($ctx)); + 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); @@ -297,6 +326,78 @@ sub show_other ($$) { $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) = @_; @@ -306,6 +407,7 @@ sub solve_result { 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)"; @@ -313,7 +415,7 @@ sub solve_result { 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. Too big to show, download available -"$oid $type $size bytes $raw_link $oid $type $size bytes (binary)" . + "blob $oid $size bytes (binary)" . " $raw_link".dbg_log($ctx)); # TODO: detect + convert to ensure validity @@ -348,7 +450,7 @@ EOM $$blob = ascii_html($$blob); } - my $x = "$oid $type $size bytes $raw_link" . + my $x = "blob $oid $size bytes $raw_link" . "". "
"; $x .= sprintf("% ${pad}u\n", $_) for (1..$nl);