gitweb: Use git-diff-tree patch output for commitdiff
Get rid of git_diff_print invocation in git_commitdiff and therefore external diff (/usr/bin/diff) invocation, and use only git-diff-tree to generate patch. git_commitdiff and git_commitdiff_plain are collapsed into one subroutine git_commitdiff, with format (currently 'html' which is default format corresponding to git_commitdiff, and 'plain' corresponding to git_commitdiff_plain) specified in argument. Separate patch (diff) pretty-printing into git_patchset_body. It is used in git_commitdiff. Separate patch (diff) line formatting from git_diff_print into format_diff_line function. It is used in git_patchset_body. While at it, add $hash parameter to git_difftree_body, according to rule that inner functions should use parameter passing, and not global variables. CHANGES TO OUTPUT: * "commitdiff" now products patches with renaming and copying detection (git-diff-tree is invoked with -M and -C options). Empty patches (mode changes and pure renames and copying) are not written currently. Former version broke renaming and copying, and didn't notice mode changes, like this version. * "commitdiff" output is now divided into several div elements of class "log", "patchset" and "patch". * "commitdiff_plain" now only generates X-Git-Tag: line only if there is tag pointing to the current commit. Former version which wrote first tag following current commit was broken[*1*]; besides we are interested rather in tags _preceding_ the commit, and _heads_ following the commit. X-Git-Url: now is current URL; former version tried[*2*] to output URL to HTML version of commitdiff. * "commitdiff_plain" is generated by git-diff-tree, and has therefore has git specific extensions to diff format: "git diff" header and optional extended header lines. FOOTNOTES [*1*] First it generated rev-list starting from HEAD even if hash_base parameter was set, second it wasn't corrected according to changes made in git_get_references (formerly read_info_ref) output, third even for older version of read_info_ref output it didn't work for multiple tags pointing to the current commit (rare). [*2*] It wrote URL for commitdiff without hash_parent, which produces diff to first parent and is not the same as current diff if it is diff of merge commit to non-first parent. Signed-off-by: Jakub Narebski <jnareb@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
parent
498fe00201
commit
eee08903b2
@ -524,6 +524,27 @@ sub format_subject_html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub format_diff_line {
|
||||||
|
my $line = shift;
|
||||||
|
my $char = substr($line, 0, 1);
|
||||||
|
my $diff_class = "";
|
||||||
|
|
||||||
|
chomp $line;
|
||||||
|
|
||||||
|
if ($char eq '+') {
|
||||||
|
$diff_class = " add";
|
||||||
|
} elsif ($char eq "-") {
|
||||||
|
$diff_class = " rem";
|
||||||
|
} elsif ($char eq "@") {
|
||||||
|
$diff_class = " chunk_header";
|
||||||
|
} elsif ($char eq "\\") {
|
||||||
|
# skip errors (incomplete lines)
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
$line = untabify($line);
|
||||||
|
return "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n";
|
||||||
|
}
|
||||||
|
|
||||||
## ----------------------------------------------------------------------
|
## ----------------------------------------------------------------------
|
||||||
## git utility subroutines, invoking git commands
|
## git utility subroutines, invoking git commands
|
||||||
|
|
||||||
@ -1367,7 +1388,7 @@ sub git_print_simplified_log {
|
|||||||
## functions printing large fragments of HTML
|
## functions printing large fragments of HTML
|
||||||
|
|
||||||
sub git_difftree_body {
|
sub git_difftree_body {
|
||||||
my ($difftree, $parent) = @_;
|
my ($difftree, $hash, $parent) = @_;
|
||||||
|
|
||||||
print "<div class=\"list_head\">\n";
|
print "<div class=\"list_head\">\n";
|
||||||
if ($#{$difftree} > 10) {
|
if ($#{$difftree} > 10) {
|
||||||
@ -1518,6 +1539,101 @@ sub git_difftree_body {
|
|||||||
print "</table>\n";
|
print "</table>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub git_patchset_body {
|
||||||
|
my ($patchset, $difftree, $hash, $hash_parent) = @_;
|
||||||
|
|
||||||
|
my $patch_idx = 0;
|
||||||
|
my $in_header = 0;
|
||||||
|
my $patch_found = 0;
|
||||||
|
my %diffinfo;
|
||||||
|
|
||||||
|
print "<div class=\"patchset\">\n";
|
||||||
|
|
||||||
|
LINE: foreach my $patch_line (@$patchset) {
|
||||||
|
|
||||||
|
if ($patch_line =~ m/^diff /) { # "git diff" header
|
||||||
|
# beginning of patch (in patchset)
|
||||||
|
if ($patch_found) {
|
||||||
|
# close previous patch
|
||||||
|
print "</div>\n"; # class="patch"
|
||||||
|
} else {
|
||||||
|
# first patch in patchset
|
||||||
|
$patch_found = 1;
|
||||||
|
}
|
||||||
|
print "<div class=\"patch\">\n";
|
||||||
|
|
||||||
|
%diffinfo = parse_difftree_raw_line($difftree->[$patch_idx++]);
|
||||||
|
|
||||||
|
# for now, no extended header, hence we skip empty patches
|
||||||
|
# companion to next LINE if $in_header;
|
||||||
|
if ($diffinfo{'from_id'} eq $diffinfo{'to_id'}) { # no change
|
||||||
|
$in_header = 1;
|
||||||
|
next LINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($diffinfo{'status'} eq "A") { # added
|
||||||
|
print "<div class=\"diff_info\">" . file_type($diffinfo{'to_mode'}) . ":" .
|
||||||
|
$cgi->a({-href => href(action=>"blob", hash_base=>$hash,
|
||||||
|
hash=>$diffinfo{'to_id'}, file_name=>$diffinfo{'file'})},
|
||||||
|
$diffinfo{'to_id'}) . "(new)" .
|
||||||
|
"</div>\n"; # class="diff_info"
|
||||||
|
|
||||||
|
} elsif ($diffinfo{'status'} eq "D") { # deleted
|
||||||
|
print "<div class=\"diff_info\">" . file_type($diffinfo{'from_mode'}) . ":" .
|
||||||
|
$cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
|
||||||
|
hash=>$diffinfo{'from_id'}, file_name=>$diffinfo{'file'})},
|
||||||
|
$diffinfo{'from_id'}) . "(deleted)" .
|
||||||
|
"</div>\n"; # class="diff_info"
|
||||||
|
|
||||||
|
} elsif ($diffinfo{'status'} eq "R" || # renamed
|
||||||
|
$diffinfo{'status'} eq "C") { # copied
|
||||||
|
print "<div class=\"diff_info\">" .
|
||||||
|
file_type($diffinfo{'from_mode'}) . ":" .
|
||||||
|
$cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
|
||||||
|
hash=>$diffinfo{'from_id'}, file_name=>$diffinfo{'from_file'})},
|
||||||
|
$diffinfo{'from_id'}) .
|
||||||
|
" -> " .
|
||||||
|
file_type($diffinfo{'to_mode'}) . ":" .
|
||||||
|
$cgi->a({-href => href(action=>"blob", hash_base=>$hash,
|
||||||
|
hash=>$diffinfo{'to_id'}, file_name=>$diffinfo{'to_file'})},
|
||||||
|
$diffinfo{'to_id'});
|
||||||
|
print "</div>\n"; # class="diff_info"
|
||||||
|
|
||||||
|
} else { # modified, mode changed, ...
|
||||||
|
print "<div class=\"diff_info\">" .
|
||||||
|
file_type($diffinfo{'from_mode'}) . ":" .
|
||||||
|
$cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
|
||||||
|
hash=>$diffinfo{'from_id'}, file_name=>$diffinfo{'file'})},
|
||||||
|
$diffinfo{'from_id'}) .
|
||||||
|
" -> " .
|
||||||
|
file_type($diffinfo{'to_mode'}) . ":" .
|
||||||
|
$cgi->a({-href => href(action=>"blob", hash_base=>$hash,
|
||||||
|
hash=>$diffinfo{'to_id'}, file_name=>$diffinfo{'file'})},
|
||||||
|
$diffinfo{'to_id'});
|
||||||
|
print "</div>\n"; # class="diff_info"
|
||||||
|
}
|
||||||
|
|
||||||
|
#print "<div class=\"diff extended_header\">\n";
|
||||||
|
$in_header = 1;
|
||||||
|
next LINE;
|
||||||
|
} # start of patch in patchset
|
||||||
|
|
||||||
|
|
||||||
|
if ($in_header && $patch_line =~ m/^---/) {
|
||||||
|
#print "</div>\n"
|
||||||
|
$in_header = 0;
|
||||||
|
}
|
||||||
|
next LINE if $in_header;
|
||||||
|
|
||||||
|
print format_diff_line($patch_line);
|
||||||
|
}
|
||||||
|
print "</div>\n" if $patch_found; # class="patch"
|
||||||
|
|
||||||
|
print "</div>\n"; # class="patchset"
|
||||||
|
}
|
||||||
|
|
||||||
|
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||||
|
|
||||||
sub git_shortlog_body {
|
sub git_shortlog_body {
|
||||||
# uses global variable $project
|
# uses global variable $project
|
||||||
my ($revlist, $from, $to, $refs, $extra) = @_;
|
my ($revlist, $from, $to, $refs, $extra) = @_;
|
||||||
@ -2556,7 +2672,7 @@ sub git_commit {
|
|||||||
git_print_log($co{'comment'});
|
git_print_log($co{'comment'});
|
||||||
print "</div>\n";
|
print "</div>\n";
|
||||||
|
|
||||||
git_difftree_body(\@difftree, $parent);
|
git_difftree_body(\@difftree, $hash, $parent);
|
||||||
|
|
||||||
git_footer_html();
|
git_footer_html();
|
||||||
}
|
}
|
||||||
@ -2600,7 +2716,7 @@ sub git_blobdiff_plain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub git_commitdiff {
|
sub git_commitdiff {
|
||||||
mkdir($git_temp, 0700);
|
my $format = shift || 'html';
|
||||||
my %co = parse_commit($hash);
|
my %co = parse_commit($hash);
|
||||||
if (!%co) {
|
if (!%co) {
|
||||||
die_error(undef, "Unknown commit object");
|
die_error(undef, "Unknown commit object");
|
||||||
@ -2608,141 +2724,104 @@ sub git_commitdiff {
|
|||||||
if (!defined $hash_parent) {
|
if (!defined $hash_parent) {
|
||||||
$hash_parent = $co{'parent'} || '--root';
|
$hash_parent = $co{'parent'} || '--root';
|
||||||
}
|
}
|
||||||
open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
|
|
||||||
or die_error(undef, "Open git-diff-tree failed");
|
# read commitdiff
|
||||||
my @difftree = map { chomp; $_ } <$fd>;
|
my $fd;
|
||||||
close $fd or die_error(undef, "Reading git-diff-tree failed");
|
my @difftree;
|
||||||
|
my @patchset;
|
||||||
|
if ($format eq 'html') {
|
||||||
|
open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C',
|
||||||
|
"--patch-with-raw", "--full-index", $hash_parent, $hash
|
||||||
|
or die_error(undef, "Open git-diff-tree failed");
|
||||||
|
|
||||||
|
while (chomp(my $line = <$fd>)) {
|
||||||
|
# empty line ends raw part of diff-tree output
|
||||||
|
last unless $line;
|
||||||
|
push @difftree, $line;
|
||||||
|
}
|
||||||
|
@patchset = map { chomp; $_ } <$fd>;
|
||||||
|
|
||||||
|
close $fd
|
||||||
|
or die_error(undef, "Reading git-diff-tree failed");
|
||||||
|
} elsif ($format eq 'plain') {
|
||||||
|
open $fd, "-|", $GIT, "diff-tree", '-r', '-p', '-B', $hash_parent, $hash
|
||||||
|
or die_error(undef, "Open git-diff-tree failed");
|
||||||
|
} else {
|
||||||
|
die_error(undef, "Unknown commitdiff format");
|
||||||
|
}
|
||||||
|
|
||||||
# non-textual hash id's can be cached
|
# non-textual hash id's can be cached
|
||||||
my $expires;
|
my $expires;
|
||||||
if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
|
if ($hash =~ m/^[0-9a-fA-F]{40}$/) {
|
||||||
$expires = "+1d";
|
$expires = "+1d";
|
||||||
}
|
}
|
||||||
my $refs = git_get_references();
|
|
||||||
my $ref = format_ref_marker($refs, $co{'id'});
|
|
||||||
my $formats_nav =
|
|
||||||
$cgi->a({-href => href(action=>"commitdiff_plain", hash=>$hash, hash_parent=>$hash_parent)},
|
|
||||||
"plain");
|
|
||||||
git_header_html(undef, $expires);
|
|
||||||
git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
|
|
||||||
git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
|
|
||||||
print "<div class=\"page_body\">\n";
|
|
||||||
git_print_simplified_log($co{'comment'}, 1); # skip title
|
|
||||||
print "<br/>\n";
|
|
||||||
foreach my $line (@difftree) {
|
|
||||||
# ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c'
|
|
||||||
# ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c'
|
|
||||||
if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
my $from_mode = $1;
|
|
||||||
my $to_mode = $2;
|
|
||||||
my $from_id = $3;
|
|
||||||
my $to_id = $4;
|
|
||||||
my $status = $5;
|
|
||||||
my $file = validate_input(unquote($6));
|
|
||||||
if ($status eq "A") {
|
|
||||||
print "<div class=\"diff_info\">" . file_type($to_mode) . ":" .
|
|
||||||
$cgi->a({-href => href(action=>"blob", hash_base=>$hash,
|
|
||||||
hash=>$to_id, file_name=>$file)},
|
|
||||||
$to_id) . "(new)" .
|
|
||||||
"</div>\n";
|
|
||||||
git_diff_print(undef, "/dev/null", $to_id, "b/$file");
|
|
||||||
} elsif ($status eq "D") {
|
|
||||||
print "<div class=\"diff_info\">" . file_type($from_mode) . ":" .
|
|
||||||
$cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
|
|
||||||
hash=>$from_id, file_name=>$file)},
|
|
||||||
$from_id) . "(deleted)" .
|
|
||||||
"</div>\n";
|
|
||||||
git_diff_print($from_id, "a/$file", undef, "/dev/null");
|
|
||||||
} elsif ($status eq "M") {
|
|
||||||
if ($from_id ne $to_id) {
|
|
||||||
print "<div class=\"diff_info\">" .
|
|
||||||
file_type($from_mode) . ":" .
|
|
||||||
$cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
|
|
||||||
hash=>$from_id, file_name=>$file)},
|
|
||||||
$from_id) .
|
|
||||||
" -> " .
|
|
||||||
file_type($to_mode) . ":" .
|
|
||||||
$cgi->a({-href => href(action=>"blob", hash_base=>$hash,
|
|
||||||
hash=>$to_id, file_name=>$file)},
|
|
||||||
$to_id);
|
|
||||||
print "</div>\n";
|
|
||||||
git_diff_print($from_id, "a/$file", $to_id, "b/$file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print "<br/>\n" .
|
|
||||||
"</div>";
|
|
||||||
git_footer_html();
|
|
||||||
}
|
|
||||||
|
|
||||||
sub git_commitdiff_plain {
|
# write commit message
|
||||||
mkdir($git_temp, 0700);
|
if ($format eq 'html') {
|
||||||
my %co = parse_commit($hash);
|
my $refs = git_get_references();
|
||||||
if (!%co) {
|
my $ref = format_ref_marker($refs, $co{'id'});
|
||||||
die_error(undef, "Unknown commit object");
|
my $formats_nav =
|
||||||
}
|
$cgi->a({-href => href(action=>"commitdiff_plain",
|
||||||
if (!defined $hash_parent) {
|
hash=>$hash, hash_parent=>$hash_parent)},
|
||||||
$hash_parent = $co{'parent'} || '--root';
|
"plain");
|
||||||
}
|
|
||||||
open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash
|
|
||||||
or die_error(undef, "Open git-diff-tree failed");
|
|
||||||
my @difftree = map { chomp; $_ } <$fd>;
|
|
||||||
close $fd or die_error(undef, "Reading diff-tree failed");
|
|
||||||
|
|
||||||
# try to figure out the next tag after this commit
|
git_header_html(undef, $expires);
|
||||||
my $tagname;
|
git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav);
|
||||||
my $refs = git_get_references("tags");
|
git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash);
|
||||||
open $fd, "-|", $GIT, "rev-list", "HEAD";
|
print "<div class=\"page_body\">\n";
|
||||||
my @commits = map { chomp; $_ } <$fd>;
|
print "<div class=\"log\">\n";
|
||||||
close $fd;
|
git_print_simplified_log($co{'comment'}, 1); # skip title
|
||||||
foreach my $commit (@commits) {
|
print "</div>\n"; # class="log"
|
||||||
if (defined $refs->{$commit}) {
|
|
||||||
$tagname = $refs->{$commit}
|
|
||||||
}
|
|
||||||
if ($commit eq $hash) {
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print $cgi->header(-type => "text/plain",
|
} elsif ($format eq 'plain') {
|
||||||
-charset => 'utf-8',
|
my $refs = git_get_references("tags");
|
||||||
-content_disposition => "inline; filename=\"git-$hash.patch\"");
|
my @tagnames;
|
||||||
my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
|
if (exists $refs->{$hash}) {
|
||||||
my $comment = $co{'comment'};
|
@tagnames = map { s|^tags/|| } $refs->{$hash};
|
||||||
print <<TEXT;
|
}
|
||||||
|
my $filename = basename($project) . "-$hash.patch";
|
||||||
|
|
||||||
|
print $cgi->header(
|
||||||
|
-type => 'text/plain',
|
||||||
|
-charset => 'utf-8',
|
||||||
|
-expires => $expires,
|
||||||
|
-content_disposition => qq(inline; filename="$filename"));
|
||||||
|
my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
|
||||||
|
print <<TEXT;
|
||||||
From: $co{'author'}
|
From: $co{'author'}
|
||||||
Date: $ad{'rfc2822'} ($ad{'tz_local'})
|
Date: $ad{'rfc2822'} ($ad{'tz_local'})
|
||||||
Subject: $co{'title'}
|
Subject: $co{'title'}
|
||||||
TEXT
|
TEXT
|
||||||
if (defined $tagname) {
|
foreach my $tag (@tagnames) {
|
||||||
print "X-Git-Tag: $tagname\n";
|
print "X-Git-Tag: $tag\n";
|
||||||
}
|
|
||||||
print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" .
|
|
||||||
"\n";
|
|
||||||
|
|
||||||
foreach my $line (@$comment) {;
|
|
||||||
print "$line\n";
|
|
||||||
}
|
|
||||||
print "---\n\n";
|
|
||||||
|
|
||||||
foreach my $line (@difftree) {
|
|
||||||
if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) {
|
|
||||||
next;
|
|
||||||
}
|
}
|
||||||
my $from_id = $3;
|
print "X-Git-Url: " . $cgi->self_url() . "\n\n";
|
||||||
my $to_id = $4;
|
foreach my $line (@{$co{'comment'}}) {
|
||||||
my $status = $5;
|
print "$line\n";
|
||||||
my $file = $6;
|
|
||||||
if ($status eq "A") {
|
|
||||||
git_diff_print(undef, "/dev/null", $to_id, "b/$file", "plain");
|
|
||||||
} elsif ($status eq "D") {
|
|
||||||
git_diff_print($from_id, "a/$file", undef, "/dev/null", "plain");
|
|
||||||
} elsif ($status eq "M") {
|
|
||||||
git_diff_print($from_id, "a/$file", $to_id, "b/$file", "plain");
|
|
||||||
}
|
}
|
||||||
|
print "---\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# write patch
|
||||||
|
if ($format eq 'html') {
|
||||||
|
#git_difftree_body(\@difftree, $hash, $hash_parent);
|
||||||
|
#print "<br/>\n";
|
||||||
|
|
||||||
|
git_patchset_body(\@patchset, \@difftree, $hash, $hash_parent);
|
||||||
|
|
||||||
|
print "</div>\n"; # class="page_body"
|
||||||
|
git_footer_html();
|
||||||
|
|
||||||
|
} elsif ($format eq 'plain') {
|
||||||
|
local $/ = undef;
|
||||||
|
print <$fd>;
|
||||||
|
close $fd
|
||||||
|
or print "Reading git-diff-tree failed\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub git_commitdiff_plain {
|
||||||
|
git_commitdiff('plain');
|
||||||
}
|
}
|
||||||
|
|
||||||
sub git_history {
|
sub git_history {
|
||||||
|
Loading…
Reference in New Issue
Block a user