From 7c5e2ebb5d8ca3d8428e19593c69a4f329d64855 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 25 Aug 2006 21:13:34 +0200 Subject: [PATCH] gitweb: Use git-diff-tree or git-diff patch output for blobdiff This is second part of removing gitweb dependency on external diff (used in git_diff_print). Get rid of git_diff_print invocation in git_blobdiff, and use either git-diff-tree (when both hash_base and hash_parent_base are provided) patch format or git-diff patch format (when only hash and hash_parent are provided) for output. Supported URI schemes, and output formats: * New URI scheme: both hash_base and hash_parent_base (trees-ish containing blobs versions we want to compare) are provided. Also either filename is provided, or hash (of blob) is provided (we try to find filename then). For this scheme we have copying and renames detection, mode changes, file types etc., and information extended diff header is correct. * Old URI scheme: hash_parent_base is not provided, we use hash and hash_parent to directly compare blobs using git-diff. If no filename is given, blobs hashes are used in place of filenames. This scheme has always "blob" as file type, it cannot detect mode changes, and we rely on CGI parameters to provide name of the file. Added git_to_hash subroutine, which transforms symbolic name or list of symbolic name to hash or list of hashes using git-rev-parse. To have "blob" instead of "unknown" (or "file" regardless of the type) in "gitweb diff header" for legacy scheme, file_type function now returns its argument if it is not octal string. Added support for fake "2" status code in git_patchset_body. Such code is generated by git_blobdiff in legacy scheme case. ATTENTION: The order of arguments (operands) to git-diff is reversed (sic!) to have correct diff in the legacy (no hash_parent_base) case. $hash_parent, $hash ordering is commented out, as it gives reversed patch (at least for git version 1.4.1.1) as compared to output in new scheme and output of older gitweb version. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 149 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 132 insertions(+), 17 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 40f65bee0e..0fa35f3369 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -454,7 +454,13 @@ sub mode_str { # convert file mode in octal to file type string sub file_type { - my $mode = oct shift; + my $mode = shift; + + if ($mode !~ m/^[0-7]+$/) { + return $mode; + } else { + $mode = oct $mode; + } if (S_ISDIR($mode & S_IFMT)) { return "directory"; @@ -618,6 +624,26 @@ sub git_get_hash_by_path { return $3; } +# converts symbolic name to hash +sub git_to_hash { + my @params = @_; + return undef unless @params; + + open my $fd, "-|", $GIT, "rev-parse", @params + or return undef; + my @hashes = map { chomp; $_ } <$fd>; + close $fd; + + if (wantarray) { + return @hashes; + } elsif (scalar(@hashes) == 1) { + # single hash + return $hashes[0]; + } else { + return \@hashes; + } +} + ## ...................................................................... ## git utility functions, directly accessing git repository @@ -1672,7 +1698,8 @@ sub git_patchset_body { "\n"; # class="diff_info" } elsif ($diffinfo->{'status'} eq "R" || # renamed - $diffinfo->{'status'} eq "C") { # copied + $diffinfo->{'status'} eq "C" || # copied + $diffinfo->{'status'} eq "2") { # with two filenames (from git_blobdiff) print "
" . file_type($diffinfo->{'from_mode'}) . ":" . $cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent, @@ -2788,14 +2815,102 @@ sub git_commit { } sub git_blobdiff { - mkdir($git_temp, 0700); - git_header_html(); + my $fd; + my @difftree; + my %diffinfo; + + # preparing $fd and %diffinfo for git_patchset_body + # new style URI + if (defined $hash_base && defined $hash_parent_base) { + if (defined $file_name) { + # read raw output + open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', $hash_parent_base, $hash_base, + "--", $file_name + or die_error(undef, "Open git-diff-tree failed"); + @difftree = map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-diff-tree failed"); + @difftree + or die_error('404 Not Found', "Blob diff not found"); + + } elsif (defined $hash) { # try to find filename from $hash + if ($hash !~ /[0-9a-fA-F]{40}/) { + $hash = git_to_hash($hash); + } + + # read filtered raw output + open $fd, "-|", $GIT, "diff-tree", '-r', '-M', '-C', $hash_parent_base, $hash_base + or die_error(undef, "Open git-diff-tree failed"); + @difftree = + # ':100644 100644 03b21826... 3b93d5e7... M ls-files.c' + # $hash == to_id + grep { /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} $hash/ } + map { chomp; $_ } <$fd>; + close $fd + or die_error(undef, "Reading git-diff-tree failed"); + @difftree + or die_error('404 Not Found', "Blob diff not found"); + + } else { + die_error('404 Not Found', "Missing one of the blob diff parameters"); + } + + if (@difftree > 1) { + die_error('404 Not Found', "Ambiguous blob diff specification"); + } + + %diffinfo = parse_difftree_raw_line($difftree[0]); + $file_parent ||= $diffinfo{'from_file'} || $file_name || $diffinfo{'file'}; + $file_name ||= $diffinfo{'to_file'} || $diffinfo{'file'}; + + $hash_parent ||= $diffinfo{'from_id'}; + $hash ||= $diffinfo{'to_id'}; + + # open patch output + open $fd, "-|", $GIT, "diff-tree", '-r', '-p', '-M', '-C', $hash_parent_base, $hash_base, + "--", $file_name + or die_error(undef, "Open git-diff-tree failed"); + } + + # old/legacy style URI + if (!%diffinfo && # if new style URI failed + defined $hash && defined $hash_parent) { + # fake git-diff-tree raw output + $diffinfo{'from_mode'} = $diffinfo{'to_mode'} = "blob"; + $diffinfo{'from_id'} = $hash_parent; + $diffinfo{'to_id'} = $hash; + if (defined $file_name) { + if (defined $file_parent) { + $diffinfo{'status'} = '2'; + $diffinfo{'from_file'} = $file_parent; + $diffinfo{'to_file'} = $file_name; + } else { # assume not renamed + $diffinfo{'status'} = '1'; + $diffinfo{'from_file'} = $file_name; + $diffinfo{'to_file'} = $file_name; + } + } else { # no filename given + $diffinfo{'status'} = '2'; + $diffinfo{'from_file'} = $hash_parent; + $diffinfo{'to_file'} = $hash; + } + + #open $fd, "-|", $GIT, "diff", '-p', $hash_parent, $hash + open $fd, "-|", $GIT, "diff", '-p', $hash, $hash_parent + or die_error(undef, "Open git-diff failed"); + } else { + die_error('404 Not Found', "Missing one of the blob diff parameters") + unless %diffinfo; + } + + # header my $formats_nav = $cgi->a({-href => href(action=>"blobdiff_plain", hash=>$hash, hash_parent=>$hash_parent, hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, file_name=>$file_name, file_parent=>$file_parent)}, "plain"); + git_header_html(); if (defined $hash_base && (my %co = parse_commit($hash_base))) { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); @@ -2803,19 +2918,19 @@ sub git_blobdiff { print "

$formats_nav
\n"; print "
$hash vs $hash_parent
\n"; } - git_print_page_path($file_name, "blob", $hash_base); - print "
\n" . - "
blob:" . - $cgi->a({-href => href(action=>"blob", hash=>$hash_parent, - hash_base=>$hash_parent_base, file_name=>($file_parent || $file_name))}, - $hash_parent) . - " -> blob:" . - $cgi->a({-href => href(action=>"blob", hash=>$hash, - hash_base=>$hash_base, file_name=>$file_name)}, - $hash) . - "
\n"; - git_diff_print($hash_parent, $file_name || $hash_parent, $hash, $file_name || $hash); - print "
"; # page_body + if (defined $file_name) { + git_print_page_path($file_name, "blob", $hash_base); + } else { + print "
\n"; + } + + # patch + print "
\n"; + + git_patchset_body($fd, [ \%diffinfo ], $hash_base, $hash_parent_base); + close $fd; + + print "
\n"; # class="page_body" git_footer_html(); }