gitweb: Adding a `blame' interface.
authorFlorian Forster <octo@verplant.org>
Sun, 11 Jun 2006 15:45:19 +0000 (17:45 +0200)
committerJunio C Hamano <junkio@cox.net>
Sat, 17 Jun 2006 05:45:11 +0000 (22:45 -0700)
This patch adds an interface for `git-blame' to `gitweb.cgi'.
Links to it are placed in `git_blob'.

Internally the code uses `git-annotate' because `git-blame's output
differs for files that have been renamed in the past. However, I like
the term `blame' better.

[jc: blame can be told to produce the compatible format btw...]

Signed-off-by: Florian Forster <octo@verplant.org>
Signed-off-by: Junio C Hamano <junkio@cox.net>
gitweb/gitweb.cgi

index ea21fbe..91c075d 100755 (executable)
@@ -203,6 +203,9 @@ if (!defined $action || $action eq "summary") {
 } elsif ($action eq "tag") {
        git_tag();
        exit;
+} elsif ($action eq "blame") {
+       git_blame();
+       exit;
 } else {
        undef $action;
        die_error(undef, "Unknown action.");
@@ -1228,6 +1231,107 @@ sub git_tag {
        git_footer_html();
 }
 
+sub git_blame {
+       my $fd;
+       die_error('404 Not Found', "What file will it be, master?") if (!$file_name);
+       $hash_base ||= git_read_head($project);
+       die_error(undef, "Reading commit failed.") unless ($hash_base);
+       my %co = git_read_commit($hash_base)
+               or die_error(undef, "Reading commit failed.");
+       if (!defined $hash) {
+               $hash = git_get_hash_by_path($hash_base, $file_name, "blob")
+                       or die_error(undef, "Error lookup file.");
+       }
+       open ($fd, "-|", "$gitbin/git-annotate", '-l', '-t', '-r', $file_name, $hash_base)
+               or die_error(undef, "Open failed.");
+       git_header_html();
+       print "<div class=\"page_nav\">\n" .
+               $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") .
+               " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") .
+               " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") .
+               " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") .
+               " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
+               " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
+       print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") .
+               " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "<br/>\n";
+       print "</div>\n".
+               "<div>" .
+               $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) .
+               "</div>\n";
+       print "<div class=\"page_path\"><b>" . esc_html($file_name) . "</b></div>\n";
+       print "<div class=\"page_body\">\n";
+       print <<HTML;
+<table style="border-collapse: collapse;">
+  <tr>
+    <th>Commit</th>
+    <th>Age</th>
+    <th>Author</th>
+    <th>Line</th>
+    <th>Data</th>
+  </tr>
+HTML
+       my @line_class = (qw(light dark));
+       my $line_class_len = scalar (@line_class);
+       my $line_class_num = $#line_class;
+       while (my $line = <$fd>) {
+               my $long_rev;
+               my $short_rev;
+               my $author;
+               my $time;
+               my $lineno;
+               my $data;
+               my $age;
+               my $age_str;
+               my $age_style;
+
+               chomp $line;
+               $line_class_num = ($line_class_num + 1) % $line_class_len;
+
+               if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) \+\d\d\d\d\t(\d+)\)(.*)$/) {
+                       $long_rev = $1;
+                       $author   = $2;
+                       $time     = $3;
+                       $lineno   = $4;
+                       $data     = $5;
+               } else {
+                       print qq(  <tr><td colspan="5" style="color: red; background-color: yellow;">Unable to parse: $line</td></tr>\n);
+                       next;
+               }
+               $short_rev  = substr ($long_rev, 0, 8);
+               $age        = time () - $time;
+               $age_str    = age_string ($age);
+               $age_str    =~ s/ /&nbsp;/g;
+               $age_style  = 'font-style: italic;';
+               $age_style .= ' color: #009900; background: transparent;' if ($age < 60*60*24*2);
+               $age_style .= ' font-weight: bold;' if ($age < 60*60*2);
+               $author     = esc_html ($author);
+               $author     =~ s/ /&nbsp;/g;
+               # escape tabs
+               while ((my $pos = index($data, "\t")) != -1) {
+                       if (my $count = (8 - ($pos % 8))) {
+                               my $spaces = ' ' x $count;
+                               $data =~ s/\t/$spaces/;
+                       }
+               }
+               $data = esc_html ($data);
+               $data =~ s/ /&nbsp;/g;
+
+               print <<HTML;
+  <tr class="$line_class[$line_class_num]">
+    <td style="font-family: monospace;"><a href="$my_uri?${\esc_param ("p=$project;a=commit;h=$long_rev")}" class="text">$short_rev..</a></td>
+    <td style="$age_style">$age_str</td>
+    <td>$author</td>
+    <td style="text-align: right;"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td>
+    <td style="font-family: monospace;">$data</td>
+  </tr>
+HTML
+       } # while (my $line = <$fd>)
+       print "</table>\n\n";
+       close $fd or print "Reading blob failed.\n";
+       print "</div>";
+       git_footer_html();
+}
+
 sub git_tags {
        my $head = git_read_head($project);
        git_header_html();
@@ -1375,7 +1479,8 @@ sub git_blob {
                      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") .
                      " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n";
                if (defined $file_name) {
-                       print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") .
+                       print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") .
+                       " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") .
                        " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head") . "<br/>\n";
                } else {
                        print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n";
@@ -1496,6 +1601,7 @@ sub git_tree {
                              "</td>\n" .
                              "<td class=\"link\">" .
                              $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob") .
+#                            " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") .
                              " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$hash_base;f=$base$t_name")}, "history") .
                              "</td>\n";
                } elsif ($t_type eq "tree") {