Merge branch 'jn/gitweb-blame'
* jn/gitweb-blame: gitweb: Add link to other blame implementation in blame views gitweb: Make linking to actions requiring JavaScript a feature gitweb.js: fix padLeftStr() and its usage gitweb.js: Harden setting blamed commit info in incremental blame gitweb.js: fix null object exception in initials calculation gitweb: Minify gitweb.js if JSMIN is defined gitweb: Create links leading to 'blame_incremental' using JavaScript gitweb: Colorize 'blame_incremental' view during processing gitweb: Incremental blame (using JavaScript) gitweb: Add optional "time to generate page" info in footer Conflicts: Makefile gitweb/gitweb.css
This commit is contained in:
@ -18,6 +18,12 @@ use File::Find qw();
|
||||
use File::Basename qw(basename);
|
||||
binmode STDOUT, ':utf8';
|
||||
|
||||
our $t0;
|
||||
if (eval { require Time::HiRes; 1; }) {
|
||||
$t0 = [Time::HiRes::gettimeofday()];
|
||||
}
|
||||
our $number_of_git_cmds = 0;
|
||||
|
||||
BEGIN {
|
||||
CGI->compile() if $ENV{'MOD_PERL'};
|
||||
}
|
||||
@ -90,6 +96,8 @@ our $stylesheet = undef;
|
||||
our $logo = "++GITWEB_LOGO++";
|
||||
# URI of GIT favicon, assumed to be image/png type
|
||||
our $favicon = "++GITWEB_FAVICON++";
|
||||
# URI of gitweb.js (JavaScript code for gitweb)
|
||||
our $javascript = "++GITWEB_JS++";
|
||||
|
||||
# URI and label (title) of GIT logo link
|
||||
#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
|
||||
@ -417,6 +425,20 @@ our %feature = (
|
||||
'sub' => \&feature_avatar,
|
||||
'override' => 0,
|
||||
'default' => ['']},
|
||||
|
||||
# Enable displaying how much time and how many git commands
|
||||
# it took to generate and display page. Disabled by default.
|
||||
# Project specific override is not supported.
|
||||
'timed' => {
|
||||
'override' => 0,
|
||||
'default' => [0]},
|
||||
|
||||
# Enable turning some links into links to actions which require
|
||||
# JavaScript to run (like 'blame_incremental'). Not enabled by
|
||||
# default. Project specific override is currently not supported.
|
||||
'javascript-actions' => {
|
||||
'override' => 0,
|
||||
'default' => [0]},
|
||||
);
|
||||
|
||||
sub gitweb_get_feature {
|
||||
@ -531,6 +553,7 @@ if (-e $GITWEB_CONFIG) {
|
||||
|
||||
# version of the core git binary
|
||||
our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
|
||||
$number_of_git_cmds++;
|
||||
|
||||
$projects_list ||= $projectroot;
|
||||
|
||||
@ -568,12 +591,16 @@ our @cgi_param_mapping = (
|
||||
snapshot_format => "sf",
|
||||
extra_options => "opt",
|
||||
search_use_regexp => "sr",
|
||||
# this must be last entry (for manipulation from JavaScript)
|
||||
javascript => "js"
|
||||
);
|
||||
our %cgi_param_mapping = @cgi_param_mapping;
|
||||
|
||||
# we will also need to know the possible actions, for validation
|
||||
our %actions = (
|
||||
"blame" => \&git_blame,
|
||||
"blame_incremental" => \&git_blame_incremental,
|
||||
"blame_data" => \&git_blame_data,
|
||||
"blobdiff" => \&git_blobdiff,
|
||||
"blobdiff_plain" => \&git_blobdiff_plain,
|
||||
"blob" => \&git_blob,
|
||||
@ -2006,6 +2033,7 @@ sub get_feed_info {
|
||||
|
||||
# returns path to the core git executable and the --git-dir parameter as list
|
||||
sub git_cmd {
|
||||
$number_of_git_cmds++;
|
||||
return $GIT, '--git-dir='.$git_dir;
|
||||
}
|
||||
|
||||
@ -3281,10 +3309,36 @@ sub git_footer_html {
|
||||
}
|
||||
print "</div>\n"; # class="page_footer"
|
||||
|
||||
if (defined $t0 && gitweb_check_feature('timed')) {
|
||||
print "<div id=\"generating_info\">\n";
|
||||
print 'This page took '.
|
||||
'<span id="generating_time" class="time_span">'.
|
||||
Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
|
||||
' seconds </span>'.
|
||||
' and '.
|
||||
'<span id="generating_cmd">'.
|
||||
$number_of_git_cmds.
|
||||
'</span> git commands '.
|
||||
" to generate.\n";
|
||||
print "</div>\n"; # class="page_footer"
|
||||
}
|
||||
|
||||
if (-f $site_footer) {
|
||||
insert_file($site_footer);
|
||||
}
|
||||
|
||||
print qq!<script type="text/javascript" src="$javascript"></script>\n!;
|
||||
if ($action eq 'blame_incremental') {
|
||||
print qq!<script type="text/javascript">\n!.
|
||||
qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
|
||||
qq! "!. href() .qq!");\n!.
|
||||
qq!</script>\n!;
|
||||
} elsif (gitweb_check_feature('javascript-actions')) {
|
||||
print qq!<script type="text/javascript">\n!.
|
||||
qq!window.onload = fixLinks;\n!.
|
||||
qq!</script>\n!;
|
||||
}
|
||||
|
||||
print "</body>\n" .
|
||||
"</html>";
|
||||
}
|
||||
@ -4881,7 +4935,13 @@ sub git_tag {
|
||||
git_footer_html();
|
||||
}
|
||||
|
||||
sub git_blame {
|
||||
sub git_blame_common {
|
||||
my $format = shift || 'porcelain';
|
||||
if ($format eq 'porcelain' && $cgi->param('js')) {
|
||||
$format = 'incremental';
|
||||
$action = 'blame_incremental'; # for page title etc
|
||||
}
|
||||
|
||||
# permissions
|
||||
gitweb_check_feature('blame')
|
||||
or die_error(403, "Blame view not allowed");
|
||||
@ -4903,123 +4963,220 @@ sub git_blame {
|
||||
}
|
||||
}
|
||||
|
||||
# run git-blame --porcelain
|
||||
open my $fd, "-|", git_cmd(), "blame", '-p',
|
||||
$hash_base, '--', $file_name
|
||||
or die_error(500, "Open git-blame failed");
|
||||
my $fd;
|
||||
if ($format eq 'incremental') {
|
||||
# get file contents (as base)
|
||||
open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
|
||||
or die_error(500, "Open git-cat-file failed");
|
||||
} elsif ($format eq 'data') {
|
||||
# run git-blame --incremental
|
||||
open $fd, "-|", git_cmd(), "blame", "--incremental",
|
||||
$hash_base, "--", $file_name
|
||||
or die_error(500, "Open git-blame --incremental failed");
|
||||
} else {
|
||||
# run git-blame --porcelain
|
||||
open $fd, "-|", git_cmd(), "blame", '-p',
|
||||
$hash_base, '--', $file_name
|
||||
or die_error(500, "Open git-blame --porcelain failed");
|
||||
}
|
||||
|
||||
# incremental blame data returns early
|
||||
if ($format eq 'data') {
|
||||
print $cgi->header(
|
||||
-type=>"text/plain", -charset => "utf-8",
|
||||
-status=> "200 OK");
|
||||
local $| = 1; # output autoflush
|
||||
print while <$fd>;
|
||||
close $fd
|
||||
or print "ERROR $!\n";
|
||||
|
||||
print 'END';
|
||||
if (defined $t0 && gitweb_check_feature('timed')) {
|
||||
print ' '.
|
||||
Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
|
||||
' '.$number_of_git_cmds;
|
||||
}
|
||||
print "\n";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# page header
|
||||
git_header_html();
|
||||
my $formats_nav =
|
||||
$cgi->a({-href => href(action=>"blob", -replay=>1)},
|
||||
"blob") .
|
||||
" | ";
|
||||
if ($format eq 'incremental') {
|
||||
$formats_nav .=
|
||||
$cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
|
||||
"blame") . " (non-incremental)";
|
||||
} else {
|
||||
$formats_nav .=
|
||||
$cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
|
||||
"blame") . " (incremental)";
|
||||
}
|
||||
$formats_nav .=
|
||||
" | " .
|
||||
$cgi->a({-href => href(action=>"history", -replay=>1)},
|
||||
"history") .
|
||||
" | " .
|
||||
$cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
|
||||
$cgi->a({-href => href(action=>$action, file_name=>$file_name)},
|
||||
"HEAD");
|
||||
git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
|
||||
git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
|
||||
git_print_page_path($file_name, $ftype, $hash_base);
|
||||
|
||||
# page body
|
||||
if ($format eq 'incremental') {
|
||||
print "<noscript>\n<div class=\"error\"><center><b>\n".
|
||||
"This page requires JavaScript to run.\n Use ".
|
||||
$cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},
|
||||
'this page').
|
||||
" instead.\n".
|
||||
"</b></center></div>\n</noscript>\n";
|
||||
|
||||
print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
|
||||
}
|
||||
|
||||
print qq!<div class="page_body">\n!;
|
||||
print qq!<div id="progress_info">... / ...</div>\n!
|
||||
if ($format eq 'incremental');
|
||||
print qq!<table id="blame_table" class="blame" width="100%">\n!.
|
||||
#qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
|
||||
qq!<thead>\n!.
|
||||
qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
|
||||
qq!</thead>\n!.
|
||||
qq!<tbody>\n!;
|
||||
|
||||
my @rev_color = qw(light dark);
|
||||
my $num_colors = scalar(@rev_color);
|
||||
my $current_color = 0;
|
||||
my %metainfo = ();
|
||||
|
||||
print <<HTML;
|
||||
<div class="page_body">
|
||||
<table class="blame">
|
||||
<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
|
||||
HTML
|
||||
LINE:
|
||||
while (my $line = <$fd>) {
|
||||
chomp $line;
|
||||
# the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
|
||||
# no <lines in group> for subsequent lines in group of lines
|
||||
my ($full_rev, $orig_lineno, $lineno, $group_size) =
|
||||
($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
|
||||
if (!exists $metainfo{$full_rev}) {
|
||||
$metainfo{$full_rev} = { 'nprevious' => 0 };
|
||||
if ($format eq 'incremental') {
|
||||
my $color_class = $rev_color[$current_color];
|
||||
|
||||
#contents of a file
|
||||
my $linenr = 0;
|
||||
LINE:
|
||||
while (my $line = <$fd>) {
|
||||
chomp $line;
|
||||
$linenr++;
|
||||
|
||||
print qq!<tr id="l$linenr" class="$color_class">!.
|
||||
qq!<td class="sha1"><a href=""> </a></td>!.
|
||||
qq!<td class="linenr">!.
|
||||
qq!<a class="linenr" href="">$linenr</a></td>!;
|
||||
print qq!<td class="pre">! . esc_html($line) . "</td>\n";
|
||||
print qq!</tr>\n!;
|
||||
}
|
||||
my $meta = $metainfo{$full_rev};
|
||||
my $data;
|
||||
while ($data = <$fd>) {
|
||||
chomp $data;
|
||||
last if ($data =~ s/^\t//); # contents of line
|
||||
if ($data =~ /^(\S+)(?: (.*))?$/) {
|
||||
$meta->{$1} = $2 unless exists $meta->{$1};
|
||||
|
||||
} else { # porcelain, i.e. ordinary blame
|
||||
my %metainfo = (); # saves information about commits
|
||||
|
||||
# blame data
|
||||
LINE:
|
||||
while (my $line = <$fd>) {
|
||||
chomp $line;
|
||||
# the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
|
||||
# no <lines in group> for subsequent lines in group of lines
|
||||
my ($full_rev, $orig_lineno, $lineno, $group_size) =
|
||||
($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
|
||||
if (!exists $metainfo{$full_rev}) {
|
||||
$metainfo{$full_rev} = { 'nprevious' => 0 };
|
||||
}
|
||||
if ($data =~ /^previous /) {
|
||||
$meta->{'nprevious'}++;
|
||||
}
|
||||
}
|
||||
my $short_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 + 1) % $num_colors;
|
||||
}
|
||||
my $tr_class = $rev_color[$current_color];
|
||||
$tr_class .= ' boundary' if (exists $meta->{'boundary'});
|
||||
$tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
|
||||
$tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
|
||||
print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
|
||||
if ($group_size) {
|
||||
print "<td class=\"sha1\"";
|
||||
print " title=\"". esc_html($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($short_rev));
|
||||
if ($group_size >= 2) {
|
||||
my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
|
||||
if (@author_initials) {
|
||||
print "<br />" .
|
||||
esc_html(join('', @author_initials));
|
||||
# or join('.', ...)
|
||||
my $meta = $metainfo{$full_rev};
|
||||
my $data;
|
||||
while ($data = <$fd>) {
|
||||
chomp $data;
|
||||
last if ($data =~ s/^\t//); # contents of line
|
||||
if ($data =~ /^(\S+)(?: (.*))?$/) {
|
||||
$meta->{$1} = $2 unless exists $meta->{$1};
|
||||
}
|
||||
if ($data =~ /^previous /) {
|
||||
$meta->{'nprevious'}++;
|
||||
}
|
||||
}
|
||||
print "</td>\n";
|
||||
}
|
||||
# 'previous' <sha1 of parent commit> <filename at commit>
|
||||
if (exists $meta->{'previous'} &&
|
||||
$meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
|
||||
$meta->{'parent'} = $1;
|
||||
$meta->{'file_parent'} = unquote($2);
|
||||
}
|
||||
my $linenr_commit =
|
||||
exists($meta->{'parent'}) ?
|
||||
$meta->{'parent'} : $full_rev;
|
||||
my $linenr_filename =
|
||||
exists($meta->{'file_parent'}) ?
|
||||
$meta->{'file_parent'} : unquote($meta->{'filename'});
|
||||
my $blamed = href(action => 'blame',
|
||||
file_name => $linenr_filename,
|
||||
hash_base => $linenr_commit);
|
||||
print "<td class=\"linenr\">";
|
||||
print $cgi->a({ -href => "$blamed#l$orig_lineno",
|
||||
-class => "linenr" },
|
||||
esc_html($lineno));
|
||||
print "</td>";
|
||||
print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
|
||||
print "</tr>\n";
|
||||
my $short_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 + 1) % $num_colors;
|
||||
}
|
||||
my $tr_class = $rev_color[$current_color];
|
||||
$tr_class .= ' boundary' if (exists $meta->{'boundary'});
|
||||
$tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
|
||||
$tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
|
||||
print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
|
||||
if ($group_size) {
|
||||
print "<td class=\"sha1\"";
|
||||
print " title=\"". esc_html($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($short_rev));
|
||||
if ($group_size >= 2) {
|
||||
my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
|
||||
if (@author_initials) {
|
||||
print "<br />" .
|
||||
esc_html(join('', @author_initials));
|
||||
# or join('.', ...)
|
||||
}
|
||||
}
|
||||
print "</td>\n";
|
||||
}
|
||||
# 'previous' <sha1 of parent commit> <filename at commit>
|
||||
if (exists $meta->{'previous'} &&
|
||||
$meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
|
||||
$meta->{'parent'} = $1;
|
||||
$meta->{'file_parent'} = unquote($2);
|
||||
}
|
||||
my $linenr_commit =
|
||||
exists($meta->{'parent'}) ?
|
||||
$meta->{'parent'} : $full_rev;
|
||||
my $linenr_filename =
|
||||
exists($meta->{'file_parent'}) ?
|
||||
$meta->{'file_parent'} : unquote($meta->{'filename'});
|
||||
my $blamed = href(action => 'blame',
|
||||
file_name => $linenr_filename,
|
||||
hash_base => $linenr_commit);
|
||||
print "<td class=\"linenr\">";
|
||||
print $cgi->a({ -href => "$blamed#l$orig_lineno",
|
||||
-class => "linenr" },
|
||||
esc_html($lineno));
|
||||
print "</td>";
|
||||
print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
|
||||
print "</tr>\n";
|
||||
} # end while
|
||||
|
||||
}
|
||||
print "</table>\n";
|
||||
print "</div>";
|
||||
|
||||
# footer
|
||||
print "</tbody>\n".
|
||||
"</table>\n"; # class="blame"
|
||||
print "</div>\n"; # class="blame_body"
|
||||
close $fd
|
||||
or print "Reading blob failed\n";
|
||||
|
||||
# page footer
|
||||
git_footer_html();
|
||||
}
|
||||
|
||||
sub git_blame {
|
||||
git_blame_common();
|
||||
}
|
||||
|
||||
sub git_blame_incremental {
|
||||
git_blame_common('incremental');
|
||||
}
|
||||
|
||||
sub git_blame_data {
|
||||
git_blame_common('data');
|
||||
}
|
||||
|
||||
sub git_tags {
|
||||
my $head = git_get_head_hash($project);
|
||||
git_header_html();
|
||||
|
||||
Reference in New Issue
Block a user