about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2015-12-23 10:00:20 +0000
committerEric Wong <e@80x24.org>2016-04-05 18:58:27 +0000
commit1f803ace908120c8e048c3ae40f8e64651e8f6f1 (patch)
treeaf437aa9b203c0035cc3130cacc96bd4db087dab
parent3e79993321bd0e5a12b104730638eb87947ec86b (diff)
downloadpublic-inbox-1f803ace908120c8e048c3ae40f8e64651e8f6f1.tar.gz
A work-in-progress, but this provides useful links to the
exact line number of modified lines instead of blindly linking
to the top-of-the-file.  Avoid flooding the user with links
but give hints by bolding the final resulting file or SHA-1
object ID.

Do other repository viewers do this?
-rw-r--r--lib/PublicInbox/Hval.pm6
-rw-r--r--lib/PublicInbox/RepoBrowseGitCommit.pm190
2 files changed, 165 insertions, 31 deletions
diff --git a/lib/PublicInbox/Hval.pm b/lib/PublicInbox/Hval.pm
index 2e8b1d60..a16cd4a1 100644
--- a/lib/PublicInbox/Hval.pm
+++ b/lib/PublicInbox/Hval.pm
@@ -74,6 +74,12 @@ sub ascii_html {
 sub as_html { ascii_html($_[0]->{raw}) }
 sub as_href { ascii_html(uri_escape_utf8($_[0]->{href})) }
 
+sub as_path {
+        my $p = uri_escape_utf8($_[0]->{href});
+        $p =~ s!%2[fF]!/!g;
+        ascii_html($p);
+}
+
 sub raw {
         if (defined $_[1]) {
                 $_[0]->{raw} = $_[1];
diff --git a/lib/PublicInbox/RepoBrowseGitCommit.pm b/lib/PublicInbox/RepoBrowseGitCommit.pm
index 324c60a1..2814d53f 100644
--- a/lib/PublicInbox/RepoBrowseGitCommit.pm
+++ b/lib/PublicInbox/RepoBrowseGitCommit.pm
@@ -6,13 +6,17 @@ use strict;
 use warnings;
 use base qw(PublicInbox::RepoBrowseBase);
 use PublicInbox::Git;
+use PublicInbox::RepoBrowseGit qw(git_unquote);
 
 use constant GIT_FMT => '--pretty=format:'.join('%n',
-        '%H', '%s', '%an <%ae>', '%ai', '%cn <%ce>', '%ci',
+        '%H', '%h', '%s', '%an <%ae>', '%ai', '%cn <%ce>', '%ci',
         '%t', '%p', '%D', '%b%x00');
 
+*ascii_html = *PublicInbox::Hval::ascii_html;
+
 sub git_commit_stream {
         my ($req, $q, $H, $log, $fh) = @_;
+        chomp(my $h = <$log>); # abbreviated commit
         my $l;
         my $s = PublicInbox::Hval->new_oneline($l = <$log>)->as_html; # subject
         my $au = PublicInbox::Hval->new_oneline($l = <$log>)->as_html; # author
@@ -35,47 +39,45 @@ sub git_commit_stream {
                 $x .= '   parent ';
                 $x .= qq(<a\nhref="${rel}commit?id=$p[0]">$p[0]</a>\n);
         } elsif (scalar(@p) > 1) {
-                $x .= '    merge ';
-                $x .= join(' ', map {
-                        qq(<a\nhref="${rel}commit?id=$_">$_</a>)
-                        } @p);
-                $x .= "\n";
+                foreach my $p (@p) {
+                        $x .= '    merge ';
+                        $x .= "<a\nhref=\"${rel}commit?id=$p\">$p</a> (";
+                        $x .= "<a\nhref=\"${rel}diff?id=$p&id2=$h\">";
+                        $x .= "diff</a>)\n";
+                }
         }
         $fh->write($x .= "\n$s\n\n");
 
         # body:
         local $/ = "\0";
-        $x = PublicInbox::Hval->new($l = <$log>)->as_html; # body
-        $fh->write($x);
+        $l = <$log>;
+        chomp $l;
+        $x = PublicInbox::Hval->new_bin($l)->as_html; # body
+        $fh->write($x."\n");
+
+        git_show_diffstat($fh, $log);
 
         # diff
         local $/ = "\n";
         my $cmt = '[a-f0-9]+';
-        my @href;
+        my $diff = { h => $h, p => \@p, rel => $rel };
         while (defined($l = <$log>)) {
-                if ($l =~ /^index ($cmt)\.\.($cmt)(.*)$/o) { # regular
-                        my $end = $3;
-                        my @l = ($1, $2);
-                        @href = git_blob_hrefs($rel, @l);
-                        @l = git_blob_links(\@href, \@l);
-                        $l = "index $l[0]..$l[1]$end\n";
+                if ($l =~ m{^diff --git ("?a/.+) ("?b/.+)$}) { # regular
+                        $l = git_diff_ab_hdr($diff, $1, $2) . "\n";
+                } elsif ($l =~ m{^diff --cc (.+)$}) { # --cc
+                        $l = git_diff_cc_hdr($diff, $1) . "\n";
+                } elsif ($l =~ /^index ($cmt)\.\.($cmt)(.*)$/o) { # regular
+                        $l = git_diff_ab_index($diff, $1, $2, $3) . "\n";
                 } elsif ($l =~ /^@@ (\S+) (\S+) @@(.*)$/) { # regular
-                        my $ctx = $3;
-                        my @l = ($1, $2);
-                        @l = git_blob_links(\@href, \@l);
-                        $l = "@@ $l[0] $l[1] \@\@$ctx\n";
+                        $l = git_diff_ab_hunk($diff, $1, $2, $3) . "\n";
+                } elsif ($l =~ /^\+/) { # added hunk
+                        chomp $l;
+                        $l = '<b>'.PublicInbox::Hval->new_bin($l)->as_html.
+                                "</b>\n";
                 } elsif ($l =~ /^index ($cmt,[^\.]+)\.\.($cmt)(.*)$/o) { # --cc
-                        my @l = (split(',', $1), $2);
-                        my $end = $3;
-                        @href = git_blob_hrefs($rel, @l);
-                        @l = git_blob_links(\@href, \@l);
-                        my $res = pop @l;
-                        $l = 'index '.join(',', @l)."..$res$end\n";
+                        $l = git_diff_cc_index($diff, $1, $2, $3) . "\n";
                 } elsif ($l =~ /^(@@@+) (\S+.*\S+) @@@+(.*)$/) { # --cc
-                        my ($at, $ctx) = ($1, $3);
-                        my @l = split(' ', $2);
-                        @l = git_blob_links(\@href, \@l);
-                        $l = join(' ', $at, @l, $at) . $ctx . "\n";
+                        $l = git_diff_cc_hunk($diff, $1, $2, $3) . "\n";
                 } else {
                         $l = PublicInbox::Hval->new_bin($l)->as_html;
                 }
@@ -93,8 +95,8 @@ sub call_git_commit {
         my $id = $q->{id};
         $id eq '' and $id = 'HEAD';
         my $git = $repo_info->{git} ||= PublicInbox::Git->new($path);
-        my $log = $git->popen(qw(show --no-notes --no-color
-                                 --abbrev=16 --irreversible-delete),
+        my $log = $git->popen(qw(show -z --numstat -p --no-notes --no-color
+                                 --abbrev=16),
                                  GIT_FMT, $id);
         my $H = <$log>;
         defined $H or return;
@@ -117,4 +119,130 @@ sub git_blob_links {
         map { $hrefs->[$i++].">$_</a>" } @$labels;
 }
 
+sub git_show_diffstat {
+        my ($fh, $log) = @_;
+        local $/ = "\0\0";
+        my $l = <$log>;
+        chomp $l;
+        my @stat = split("\0", $l);
+        my $nr = 0;
+        my ($nadd, $ndel) = (0, 0);
+        while (defined($l = shift @stat)) {
+                $l =~ s/\n?(\S+)\t+(\S+)\t+// or next;
+                my ($add, $del) = ($1, $2);
+                if ($add =~ /\A\d+\z/) {
+                        $nadd += $add;
+                        $ndel += $del;
+                        $add = "+$add";
+                        $del = "-$del";
+                }
+                my $num = sprintf('% 6s/%-6s', $del, $add);
+                unless (length $l) {
+                        my $from = shift @stat;
+                        my $to = shift @stat;
+                        $l = "$from => $to";
+                }
+                $l = PublicInbox::Hval->new_bin($l)->as_html;
+                ++$nr;
+                $fh->write($num."\t".$l."\n");
+        }
+        $l = "\n$nr ";
+        $l .= $nr == 1 ? 'file changed, ' : 'files changed, ';
+        $l .= $nadd;
+        $l .= $nadd == 1 ? ' insertion(+), ' : ' insertions(+), ';
+        $l .= $ndel;
+        $l .= $ndel == 1 ? " deletion(-)\n\n" : " deletions(-)\n\n";
+        $fh->write($l);
+}
+
+# index abcdef89..01234567
+sub git_diff_ab_index {
+        my ($diff, $xa, $xb, $end) = @_;
+        # not wasting bandwidth on links here, yet
+        # links in hunk headers are far more useful with line offsets
+        $end = PublicInbox::Hval->new_bin($end)->as_html;
+        "index $xa..<b>$xb</b>$end";
+}
+
+# diff --git a/foo.c b/bar.c
+sub git_diff_ab_hdr {
+        my ($diff, $fa, $fb) = @_;
+        my $html_a = PublicInbox::Hval->new_bin($fa)->as_html;
+        my $html_b = PublicInbox::Hval->new_bin($fb)->as_html;
+        $fa = git_unquote($fa);
+        $fb = git_unquote($fb);
+        $fa =~ s!\Aa/!!;
+        $fb =~ s!\Ab/!!;
+        $fa = $diff->{fa} = PublicInbox::Hval->new_bin($fa);
+        $fb = $diff->{fb} = PublicInbox::Hval->new_bin($fb);
+        $diff->{path_a} = $fa->as_path;
+        $diff->{path_b} = $fb->as_path;
+
+        # not wasting bandwidth on links here, yet
+        # links in hunk headers are far more useful with line offsets
+        "diff --git $html_a <b>$html_b</b>";
+}
+
+# @@ -1,2 +3,4 @@ (regular diff)
+sub git_diff_ab_hunk {
+        my ($diff, $ca, $cb, $ctx) = @_;
+        my ($na) = ($ca =~ /\A-(\d+),/);
+        my ($nb) = ($cb =~ /\A\+(\d+),/);
+
+        my $rel = $diff->{rel};
+        my $p = $diff->{p}->[0];
+        my $h = $diff->{h};
+
+        '@@ ' .
+        "<a\nhref=\"${rel}tree/$diff->{path_a}?id=$p#n$na\">$ca</a> " .
+        "<a\nhref=\"${rel}tree/$diff->{path_b}?id=$h#n$nb\"><b>$cb</b></a> " .
+        '@@' . PublicInbox::Hval->new_bin($ctx)->as_html;
+}
+
+sub git_diff_cc_hdr {
+        my ($diff, $path) = @_;
+        my $html_path = PublicInbox::Hval->new_bin($path)->as_html;
+        my $cc = $diff->{cc} = PublicInbox::Hval->new_bin(git_unquote($path));
+        $diff->{path_cc} = $cc->as_path;
+        "diff --cc <b>$html_path</b>";
+}
+
+# index abcdef09,01234567..76543210
+sub git_diff_cc_index {
+        my ($diff, $before, $last, $end) = @_;
+        $end = PublicInbox::Hval->new_bin($end)->as_html;
+        $diff->{pobj_cc} = [ split(',', $before) ];
+
+        # not wasting bandwidth on links here, yet
+        # links in hunk headers are far more useful with line offsets
+        "index $before..<b>$last</b>$end";
+}
+
+# @@@ -1,2 -3,4 +5,6 @@@ (combined diff)
+sub git_diff_cc_hunk {
+        my ($diff, $at, $offs, $ctx) = @_;
+        my @offs = split(' ', $offs);
+        my $last = pop @offs;
+        my @p = @{$diff->{p}};
+        my @pobj = @{$diff->{pobj_cc}};
+        my $path = $diff->{path_cc};
+        my $rel = $diff->{rel};
+        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
+                $rv .= " <a\nhref=\"$ppath?id=$p&obj=$obj#n$n\">$off</a>";
+        }
+
+        # we can use the normal 'tree' endpoint for the result
+        my ($n) = ($last =~ /\A\+(\d+),/); # line number
+        my $h = $diff->{h};
+        $rv .= " <a\nhref=\"${rel}tree/$path?id=$h#n$n\"><b>$last</b></a>";
+        $rv .= " $at" . PublicInbox::Hval->new_bin($ctx)->as_html;
+}
+
 1;