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{ (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>
find related emails, including ancestors/descendants/conflicts
 EOM
 		}
 	}
-	$x = $ctx->zflush($ctx->_html_end);
+	chop($x = <
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 .= <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.
+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, <Too big to show, download available -"$oid $type $size bytes $raw_link
+blob $oid $size bytes $raw_link
EOM } @@ -332,7 +434,7 @@ EOM } $bin and return html_page($ctx, 200, - "
$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);