gitweb: use blame --porcelain
[gitweb.git] / gitweb / gitweb.perl
old mode 100644 (file)
new mode 100755 (executable)
index 671a4e6..68347ac
 # replace this with something more descriptive for clearer bookmarks
 our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled";
 
+# filename of html text to include at top of each page
+our $site_header = "++GITWEB_SITE_HEADER++";
 # html text to include at home page
 our $home_text = "++GITWEB_HOMETEXT++";
+# filename of html text to include at bottom of each page
+our $site_footer = "++GITWEB_SITE_FOOTER++";
+
+# URI of stylesheets
+our @stylesheets = ("++GITWEB_CSS++");
+our $stylesheet;
+# default is not to define style sheet, but it can be overwritten later
+undef $stylesheet;
 
-# URI of default stylesheet
-our $stylesheet = "++GITWEB_CSS++";
 # URI of GIT logo
 our $logo = "++GITWEB_LOGO++";
 # URI of GIT favicon, assumed to be image/png type
 our $favicon = "++GITWEB_FAVICON++";
 
+our $githelp_url = "http://git.or.cz/";
+our $githelp_label = "git homepage";
+
 # source of projects list
 our $projects_list = "++GITWEB_LIST++";
 
@@ -963,6 +974,9 @@ sub parse_date {
        $date{'hour_local'} = $hour;
        $date{'minute_local'} = $min;
        $date{'tz_local'} = $tz;
+       $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
+                                  1900+$year, $mon+1, $mday,
+                                  $hour, $min, $sec, $tz);
        return %date;
 }
 
@@ -1366,8 +1380,17 @@ sub git_header_html {
 <meta name="generator" content="gitweb/$version git/$git_version"/>
 <meta name="robots" content="index, nofollow"/>
 <title>$title</title>
-<link rel="stylesheet" type="text/css" href="$stylesheet"/>
 EOF
+# print out each stylesheet that exist
+       if (defined $stylesheet) {
+#provides backwards capability for those people who define style sheet in a config file
+               print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+       } else {
+               foreach my $stylesheet (@stylesheets) {
+                       next unless $stylesheet;
+                       print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n";
+               }
+       }
        if (defined $project) {
                printf('<link rel="alternate" title="%s log" '.
                       'href="%s" type="application/rss+xml"/>'."\n",
@@ -1385,9 +1408,18 @@ sub git_header_html {
        }
 
        print "</head>\n" .
-             "<body>\n" .
-             "<div class=\"page_header\">\n" .
-             "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" .
+             "<body>\n";
+
+       if (-f $site_header) {
+               open (my $fd, $site_header);
+               print <$fd>;
+               close $fd;
+       }
+
+       print "<div class=\"page_header\">\n" .
+             "<a href=\"" . esc_html($githelp_url) .
+             "\" title=\"" . esc_html($githelp_label) .
+             "\">" .
              "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" .
              "</a>\n";
        print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
@@ -1437,8 +1469,15 @@ sub git_footer_html {
                print $cgi->a({-href => href(project=>undef, action=>"project_index"),
                              -class => "rss_logo"}, "TXT") . "\n";
        }
-       print "</div>\n" .
-             "</body>\n" .
+       print "</div>\n" ;
+
+       if (-f $site_footer) {
+               open (my $fd, $site_footer);
+               print <$fd>;
+               close $fd;
+       }
+
+       print "</body>\n" .
              "</html>";
 }
 
@@ -2444,64 +2483,9 @@ sub git_tag {
        git_footer_html();
 }
 
-sub git_blame_flush_chunk {
-       my ($name, $revdata, $color, $rev, @line) = @_;
-       my $label = substr($rev, 0, 8);
-       my $line = scalar(@line);
-       my $cnt = 0;
-       my $pop = '';
-
-       if ($revdata->{$rev} ne '') {
-               $pop = ' title="' . esc_html($revdata->{$rev}) . '"';
-       }
-
-       for (@line) {
-               my ($lineno, $data) = @$_;
-               $cnt++;
-               print "<tr class=\"$color\">\n";
-               if ($cnt == 1) {
-                       print "<td class=\"sha1\"$pop";
-                       if ($line > 1) {
-                               print " rowspan=\"$line\"";
-                       }
-                       print ">";
-                       print $cgi->a({-href => href(action=>"commit",
-                                                    hash=>$rev,
-                                                    file_name=>$name)},
-                                     $label);
-                       print "</td>\n";
-               }
-               print "<td class=\"linenr\">".
-                   "<a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" .
-                   esc_html($lineno) . "</a></td>\n";
-               print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
-               print "</tr>\n";
-       }
-}
-
-# We can have up to N*2 lines.  If it is more than N lines, split it
-# into two to avoid orphans.
-sub git_blame_flush_chunk_1 {
-       my ($chunk_cap, $name, $revdata, $color, $rev, @chunk) = @_;
-       if ($chunk_cap < @chunk) {
-               my @first = splice(@chunk, 0, @chunk/2);
-               git_blame_flush_chunk($name,
-                                     $revdata,
-                                     $color,
-                                     $rev,
-                                     @first);
-       }
-       git_blame_flush_chunk($name,
-                             $revdata,
-                             $color,
-                             $rev,
-                             @chunk);
-}
-
 sub git_blame2 {
        my $fd;
        my $ftype;
-       my $chunk_cap = 20;
 
        my ($have_blame) = gitweb_check_feature('blame');
        if (!$have_blame) {
@@ -2520,7 +2504,8 @@ sub git_blame2 {
        if ($ftype !~ "blob") {
                die_error("400 Bad Request", "Object is not a blob");
        }
-       open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base)
+       open ($fd, "-|", git_cmd(), "blame", '--porcelain', '--',
+             $file_name, $hash_base)
                or die_error(undef, "Open git-blame failed");
        git_header_html();
        my $formats_nav =
@@ -2544,45 +2529,54 @@ sub git_blame2 {
 <table class="blame">
 <tr><th>Commit</th><th>Line</th><th>Data</th></tr>
 HTML
-       my @chunk = ();
-       my %revdata = ();
-       while (<$fd>) {
-               /^([0-9a-fA-F]{40}).*?(\d+)\)\s{1}(\s*.*)/;
-               my ($full_rev, $author, $date, $lineno, $data) =
-                   /^([0-9a-f]{40}).*?\s\((.*?)\s+([-\d]+ [:\d]+ [-+\d]+)\s+(\d+)\)\s(.*)/;
-               if (!exists $revdata{$full_rev}) {
-                       $revdata{$full_rev} = "$author, $date";
-               }
-               if (!defined $last_rev) {
-                       $last_rev = $full_rev;
-               } elsif ($last_rev ne $full_rev) {
-                       git_blame_flush_chunk_1($chunk_cap,
-                                               $file_name,
-                                               \%revdata,
-                                               $rev_color[$current_color],
-                                               $last_rev, @chunk);
-                       @chunk = ();
-                       $last_rev = $full_rev;
+       my %metainfo = ();
+       while (1) {
+               $_ = <$fd>;
+               last unless defined $_;
+               my ($full_rev, $lineno, $orig_lineno, $group_size) =
+                   /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/;
+               if (!exists $metainfo{$full_rev}) {
+                       $metainfo{$full_rev} = {};
+               }
+               my $meta = $metainfo{$full_rev};
+               while (<$fd>) {
+                       last if (s/^\t//);
+                       if (/^(\S+) (.*)$/) {
+                               $meta->{$1} = $2;
+                       }
+               }
+               my $data = $_;
+               my $rev = substr($full_rev, 0, 8);
+               my $author = $meta->{'author'};
+               my %date = parse_date($meta->{'author-time'},
+                                     $meta->{'author-tz'});
+               my $date = $date{'iso-tz'};
+               if ($group_size) {
                        $current_color = ++$current_color % $num_colors;
                }
-               elsif ($chunk_cap * 2 < @chunk) {
-                       # We have more than N*2 lines from the same
-                       # revision.  Flush N lines and leave N lines
-                       # in @chunk to avoid orphaned lines.
-                       my @first = splice(@chunk, 0, $chunk_cap);
-                       git_blame_flush_chunk($file_name,
-                                             \%revdata,
-                                             $rev_color[$current_color],
-                                             $last_rev, @first);
-               }
-               push @chunk, [$lineno, $data];
-       }
-       if (@chunk) {
-               git_blame_flush_chunk_1($chunk_cap,
-                                       $file_name,
-                                       \%revdata,
-                                       $rev_color[$current_color],
-                                       $last_rev, @chunk);
+               print "<tr class=\"$rev_color[$current_color]\">\n";
+               if ($group_size) {
+                       print "<td class=\"sha1\"";
+                       print " title=\"$author, $date\"";
+                       print " rowspan=\"$group_size\"" if ($group_size > 1);
+                       print ">";
+                       print $cgi->a({-href => href(action=>"commit",
+                                                    hash=>$full_rev,
+                                                    file_name=>$file_name)},
+                                     esc_html($rev));
+                       print "</td>\n";
+               }
+               my $blamed = href(action => 'blame',
+                                 file_name => $meta->{'filename'},
+                                 hash_base => $full_rev);
+               print "<td class=\"linenr\">";
+               print $cgi->a({ -href => "$blamed#l$orig_lineno",
+                               -id => "l$lineno",
+                               -class => "linenr" },
+                             esc_html($lineno));
+               print "</td>";
+               print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
+               print "</tr>\n";
        }
        print "</table>\n";
        print "</div>";
@@ -2925,9 +2919,12 @@ sub git_snapshot {
                -content_disposition => 'inline; filename="' . "$filename" . '"',
                -status => '200 OK');
 
-       my $git_command = git_cmd_str();
-       open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $command" or
-               die_error(undef, "Execute git-tar-tree failed.");
+       my $git = git_cmd_str();
+       my $name = $project;
+       $name =~ s/\047/\047\\\047\047/g;
+       open my $fd, "-|",
+       "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
+               or die_error(undef, "Execute git-tar-tree failed.");
        binmode STDOUT, ':raw';
        print <$fd>;
        binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
@@ -3024,11 +3021,6 @@ sub git_commit {
                        $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)},
                                "blame");
        }
-       if (defined $co{'parent'}) {
-               push @views_nav,
-                       $cgi->a({-href => href(action=>"shortlog", hash=>$hash)}, "shortlog"),
-                       $cgi->a({-href => href(action=>"log", hash=>$hash)}, "log");
-       }
        git_header_html(undef, $expires);
        git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff',
                           $hash, $co{'tree'}, $hash,