summaryrefslogtreecommitdiff
path: root/gitweb/gitweb.perl
diff options
context:
space:
mode:
authorJakub Narebski <jnareb@gmail.com>2009-09-01 11:39:17 (GMT)
committerJunio C Hamano <gitster@pobox.com>2009-09-01 15:34:24 (GMT)
commit4af819d4cad206648b832f4d6103547981ab8004 (patch)
treea2a911920ed7cdcd21cd78bffaa2adf1de787c9a /gitweb/gitweb.perl
parentaa7dd05e6a622ec17d034980c4440b0aed583c6e (diff)
downloadgit-4af819d4cad206648b832f4d6103547981ab8004.zip
git-4af819d4cad206648b832f4d6103547981ab8004.tar.gz
git-4af819d4cad206648b832f4d6103547981ab8004.tar.bz2
gitweb: Incremental blame (using JavaScript)
Add 'blame_incremental' view, which uses "git blame --incremental" and JavaScript (Ajax), where 'blame' use "git blame --porcelain". * gitweb generates initial info by putting file contents (from "git cat-file") together with line numbers in blame table * then gitweb makes web browser JavaScript engine call startBlame() function from gitweb.js * startBlame() opens XMLHttpRequest connection to 'blame_data' view, which in turn calls "git blame --incremental" for a file, and streams output of git-blame to JavaScript (gitweb.js) * XMLHttpRequest event handler updates line info in blame view as soon as it gets data from 'blame_data' (from server), and it also updates progress info * when 'blame_data' ends, and gitweb.js finishes updating line info, it fixes colors to match (as far as possible) ordinary 'blame' view, and updates information about how long it took to generate page. Gitweb deals with streamed 'blame_data' server errors by displaying them in the progress info area (just in case). The 'blame_incremental' view tries to be equivalent to 'blame' action; there are however a few differences in output between 'blame' and 'blame_incremental' view: * 'blame_incremental' always used query form for this part of link(s) which is generated by JavaScript code. The difference is visible if we use path_info link (pass some or all arguments in path_info). Changing this would require implementing something akin to href() subroutine from gitweb.perl in JavaScript (in gitweb.js). * 'blame_incremental' always uses "rowspan" attribute, even if rowspan="1". This simplifies code, and is not visible to user. * The progress bar and progress info are still there even after JavaScript part of 'blame_incremental' finishes work. Note that currently no link generated by gitweb leads to this new view. This code is based on patch by Petr Baudis <pasky@suse.cz> patch, which in turn was tweaked up version of Fredrik Kuivinen <frekui@gmail.com>'s proof of concept patch. This patch adds GITWEB_JS compile configuration option, and modifies git-instaweb.sh to take gitweb.js into account. The code for git-instaweb.sh was taken from Pasky's patch. Signed-off-by: Fredrik Kuivinen <frekui@gmail.com> Signed-off-by: Petr Baudis <pasky@suse.cz> Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'gitweb/gitweb.perl')
-rwxr-xr-xgitweb/gitweb.perl274
1 files changed, 187 insertions, 87 deletions
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 18cbf35..6cdd8c3 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -96,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/";
@@ -564,6 +566,8 @@ 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,
@@ -4787,7 +4791,9 @@ sub git_tag {
git_footer_html();
}
-sub git_blame {
+sub git_blame_common {
+ my $format = shift || 'porcelain';
+
# permissions
gitweb_check_feature('blame')
or die_error(403, "Blame view not allowed");
@@ -4809,10 +4815,43 @@ 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();
@@ -4823,109 +4862,170 @@ sub git_blame {
$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',-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 };
- }
- 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 ($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!;
+ }
+
+ } 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 $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'}++;
+ }
}
- }
- 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 $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";
}
- 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";
+ # '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
+ if ($format eq 'incremental') {
+ print qq!<script type="text/javascript" src="$javascript"></script>\n!.
+ qq!<script type="text/javascript">\n!.
+ qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
+ qq! "!. href() .qq!");\n!.
+ qq!</script>\n!;
+ }
+
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();