gitweb: Create special from-file/to-file header for combined diff
[gitweb.git] / gitweb / gitweb.perl
index e92596c295576d20d76eef23c004e7d2a5db6a3a..13114bc9c62015ebbc50ded7caf2912aea5ee069 100755 (executable)
@@ -954,7 +954,165 @@ sub format_subject_html {
        }
 }
 
-# format patch (diff) line (rather not to be used for diff headers)
+# format git diff header line, i.e. "diff --(git|combined|cc) ..."
+sub format_git_diff_header_line {
+       my $line = shift;
+       my $diffinfo = shift;
+       my ($from, $to) = @_;
+
+       if ($diffinfo->{'nparents'}) {
+               # combined diff
+               $line =~ s!^(diff (.*?) )"?.*$!$1!;
+               if ($to->{'href'}) {
+                       $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
+                                        esc_path($to->{'file'}));
+               } else { # file was deleted (no href)
+                       $line .= esc_path($to->{'file'});
+               }
+       } else {
+               # "ordinary" diff
+               $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
+               if ($from->{'href'}) {
+                       $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
+                                        'a/' . esc_path($from->{'file'}));
+               } else { # file was added (no href)
+                       $line .= 'a/' . esc_path($from->{'file'});
+               }
+               $line .= ' ';
+               if ($to->{'href'}) {
+                       $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
+                                        'b/' . esc_path($to->{'file'}));
+               } else { # file was deleted
+                       $line .= 'b/' . esc_path($to->{'file'});
+               }
+       }
+
+       return "<div class=\"diff header\">$line</div>\n";
+}
+
+# format extended diff header line, before patch itself
+sub format_extended_diff_header_line {
+       my $line = shift;
+       my $diffinfo = shift;
+       my ($from, $to) = @_;
+
+       # match <path>
+       if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
+               $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
+                                      esc_path($from->{'file'}));
+       }
+       if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
+               $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
+                                esc_path($to->{'file'}));
+       }
+       # match single <mode>
+       if ($line =~ m/\s(\d{6})$/) {
+               $line .= '<span class="info"> (' .
+                        file_type_long($1) .
+                        ')</span>';
+       }
+       # match <hash>
+       if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
+               # can match only for combined diff
+               $line = 'index ';
+               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+                       if ($from->{'href'}[$i]) {
+                               $line .= $cgi->a({-href=>$from->{'href'}[$i],
+                                                 -class=>"hash"},
+                                                substr($diffinfo->{'from_id'}[$i],0,7));
+                       } else {
+                               $line .= '0' x 7;
+                       }
+                       # separator
+                       $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
+               }
+               $line .= '..';
+               if ($to->{'href'}) {
+                       $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
+                                        substr($diffinfo->{'to_id'},0,7));
+               } else {
+                       $line .= '0' x 7;
+               }
+
+       } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
+               # can match only for ordinary diff
+               my ($from_link, $to_link);
+               if ($from->{'href'}) {
+                       $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
+                                            substr($diffinfo->{'from_id'},0,7));
+               } else {
+                       $from_link = '0' x 7;
+               }
+               if ($to->{'href'}) {
+                       $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
+                                          substr($diffinfo->{'to_id'},0,7));
+               } else {
+                       $to_link = '0' x 7;
+               }
+               my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
+               $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
+       }
+
+       return $line . "<br/>\n";
+}
+
+# format from-file/to-file diff header
+sub format_diff_from_to_header {
+       my ($from_line, $to_line, $diffinfo, $from, $to) = @_;
+       my $line;
+       my $result = '';
+
+       $line = $from_line;
+       #assert($line =~ m/^---/) if DEBUG;
+       # no extra formatting for "^--- /dev/null"
+       if (! $diffinfo->{'nparents'}) {
+               # ordinary (single parent) diff
+               if ($line =~ m!^--- "?a/!) {
+                       if ($from->{'href'}) {
+                               $line = '--- a/' .
+                                       $cgi->a({-href=>$from->{'href'}, -class=>"path"},
+                                               esc_path($from->{'file'}));
+                       } else {
+                               $line = '--- a/' .
+                                       esc_path($from->{'file'});
+                       }
+               }
+               $result .= qq!<div class="diff from_file">$line</div>\n!;
+
+       } else {
+               # combined diff (merge commit)
+               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+                       if ($from->{'href'}[$i]) {
+                               $line = '--- ' .
+                                       ($i+1) . "/" .
+                                       $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
+                                               esc_path($from->{'file'}[$i]));
+                       } else {
+                               $line = '--- /dev/null';
+                       }
+                       $result .= qq!<div class="diff from_file">$line</div>\n!;
+               }
+       }
+
+       $line = $to_line;
+       #assert($line =~ m/^\+\+\+/) if DEBUG;
+       # no extra formatting for "^+++ /dev/null"
+       if ($line =~ m!^\+\+\+ "?b/!) {
+               if ($to->{'href'}) {
+                       $line = '+++ b/' .
+                               $cgi->a({-href=>$to->{'href'}, -class=>"path"},
+                                       esc_path($to->{'file'}));
+               } else {
+                       $line = '+++ b/' .
+                               esc_path($to->{'file'});
+               }
+       }
+       $result .= qq!<div class="diff to_file">$line</div>\n!;
+
+       return $result;
+}
+
+# format patch (diff) line (not to be used for diff headers)
 sub format_diff_line {
        my $line = shift;
        my ($from, $to) = @_;
@@ -1680,6 +1838,48 @@ ($;%)
        return wantarray ? %res : \%res;
 }
 
+# generates _two_ hashes, references to which are passed as 2 and 3 argument
+sub parse_from_to_diffinfo {
+       my ($diffinfo, $from, $to, @parents) = @_;
+
+       if ($diffinfo->{'nparents'}) {
+               # combined diff
+               $from->{'file'} = [];
+               $from->{'href'} = [];
+               fill_from_file_info($diffinfo, @parents)
+                       unless exists $diffinfo->{'from_file'};
+               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+                       $from->{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
+                       if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
+                               $from->{'href'}[$i] = href(action=>"blob",
+                                                          hash_base=>$parents[$i],
+                                                          hash=>$diffinfo->{'from_id'}[$i],
+                                                          file_name=>$from->{'file'}[$i]);
+                       } else {
+                               $from->{'href'}[$i] = undef;
+                       }
+               }
+       } else {
+               $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
+               if ($diffinfo->{'status'} ne "A") { # not new (added) file
+                       $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
+                                              hash=>$diffinfo->{'from_id'},
+                                              file_name=>$from->{'file'});
+               } else {
+                       delete $from->{'href'};
+               }
+       }
+
+       $to->{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
+       if (!is_deleted($diffinfo)) { # file exists in result
+               $to->{'href'} = href(action=>"blob", hash_base=>$hash,
+                                    hash=>$diffinfo->{'to_id'},
+                                    file_name=>$to->{'file'});
+       } else {
+               delete $to->{'href'};
+       }
+}
+
 ## ......................................................................
 ## parse to array of hashes functions
 
@@ -2387,6 +2587,11 @@ sub from_ids_eq {
        }
 }
 
+sub is_deleted {
+       my $diffinfo = shift;
+
+       return $diffinfo->{'to_id'} eq ('0' x 40);
+}
 
 sub git_difftree_body {
        my ($difftree, $hash, @parents) = @_;
@@ -2401,6 +2606,26 @@ sub git_difftree_body {
        print "<table class=\"" .
              (@parents > 1 ? "combined " : "") .
              "diff_tree\">\n";
+
+       # header only for combined diff in 'commitdiff' view
+       my $has_header = @parents > 1 && $action eq 'commitdiff';
+       if ($has_header) {
+               # table header
+               print "<thead><tr>\n" .
+                      "<th></th><th></th>\n"; # filename, patchN link
+               for (my $i = 0; $i < @parents; $i++) {
+                       my $par = $parents[$i];
+                       print "<th>" .
+                             $cgi->a({-href => href(action=>"commitdiff",
+                                                    hash=>$hash, hash_parent=>$par),
+                                      -title => 'commitdiff to parent number ' .
+                                                 ($i+1) . ': ' . substr($par,0,7)},
+                                     $i+1) .
+                             "&nbsp;</th>\n";
+               }
+               print "</tr></thead>\n<tbody>\n";
+       }
+
        my $alternate = 1;
        my $patchno = 0;
        foreach my $line (@{$difftree}) {
@@ -2424,7 +2649,7 @@ sub git_difftree_body {
                        fill_from_file_info($diff, @parents)
                                unless exists $diff->{'from_file'};
 
-                       if ($diff->{'to_id'} ne ('0' x 40)) {
+                       if (!is_deleted($diff)) {
                                # file exists in the result (child) commit
                                print "<td>" .
                                      $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
@@ -2673,6 +2898,7 @@ sub git_difftree_body {
                } # we should not encounter Unmerged (U) or Unknown (X) status
                print "</tr>\n";
        }
+       print "</tbody>" if $has_header;
        print "</table>\n";
 }
 
@@ -2744,6 +2970,8 @@ sub git_patchset_body {
                        } else {
                                $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
                        }
+                       # modifies %from, %to hashes
+                       parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
                        if ($diffinfo->{'nparents'}) {
                                # combined diff
                                $from{'file'} = [];
@@ -2773,7 +3001,7 @@ sub git_patchset_body {
                        }
 
                        $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
-                       if ($diffinfo->{'to_id'} ne ('0' x 40)) { # file exists in result
+                       if (!is_deleted($diffinfo)) { # file exists in result
                                $to{'href'} = href(action=>"blob", hash_base=>$hash,
                                                   hash=>$diffinfo->{'to_id'},
                                                   file_name=>$to{'file'});
@@ -2787,105 +3015,15 @@ sub git_patchset_body {
 
                # print "git diff" header
                $patch_line = shift @diff_header;
-               if ($diffinfo->{'nparents'}) {
-
-                       # combined diff
-                       $patch_line =~ s!^(diff (.*?) )"?.*$!$1!;
-                       if ($to{'href'}) {
-                               $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
-                                                      esc_path($to{'file'}));
-                       } else { # file was deleted
-                               $patch_line .= esc_path($to{'file'});
-                       }
-
-               } else {
-
-                       $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
-                       if ($from{'href'}) {
-                               $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
-                                                      'a/' . esc_path($from{'file'}));
-                       } else { # file was added
-                               $patch_line .= 'a/' . esc_path($from{'file'});
-                       }
-                       $patch_line .= ' ';
-                       if ($to{'href'}) {
-                               $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
-                                                      'b/' . esc_path($to{'file'}));
-                       } else { # file was deleted
-                               $patch_line .= 'b/' . esc_path($to{'file'});
-                       }
-
-               }
-               print "<div class=\"diff header\">$patch_line</div>\n";
+               print format_git_diff_header_line($patch_line, $diffinfo,
+                                                 \%from, \%to);
 
                # print extended diff header
                print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
        EXTENDED_HEADER:
                foreach $patch_line (@diff_header) {
-                       # match <path>
-                       if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) {
-                               $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"},
-                                                      esc_path($from{'file'}));
-                       }
-                       if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) {
-                               $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"path"},
-                                                      esc_path($to{'file'}));
-                       }
-                       # match single <mode>
-                       if ($patch_line =~ m/\s(\d{6})$/) {
-                               $patch_line .= '<span class="info"> (' .
-                                              file_type_long($1) .
-                                              ')</span>';
-                       }
-                       # match <hash>
-                       if ($patch_line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
-                               # can match only for combined diff
-                               $patch_line = 'index ';
-                               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
-                                       if ($from{'href'}[$i]) {
-                                               $patch_line .= $cgi->a({-href=>$from{'href'}[$i],
-                                                                       -class=>"hash"},
-                                                                      substr($diffinfo->{'from_id'}[$i],0,7));
-                                       } else {
-                                               $patch_line .= '0' x 7;
-                                       }
-                                       # separator
-                                       $patch_line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
-                               }
-                               $patch_line .= '..';
-                               if ($to{'href'}) {
-                                       $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"hash"},
-                                                              substr($diffinfo->{'to_id'},0,7));
-                               } else {
-                                       $patch_line .= '0' x 7;
-                               }
-
-                       } elsif ($patch_line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
-                               # can match only for ordinary diff
-                               my ($from_link, $to_link);
-                               if ($from{'href'}) {
-                                       $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
-                                                            substr($diffinfo->{'from_id'},0,7));
-                               } else {
-                                       $from_link = '0' x 7;
-                               }
-                               if ($to{'href'}) {
-                                       $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
-                                                          substr($diffinfo->{'to_id'},0,7));
-                               } else {
-                                       $to_link = '0' x 7;
-                               }
-                               #affirm {
-                               #       my ($from_hash, $to_hash) =
-                               #               ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/);
-                               #       my ($from_id, $to_id) =
-                               #               ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
-                               #       ($from_hash eq $from_id) && ($to_hash eq $to_id);
-                               #} if DEBUG;
-                               my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
-                               $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
-                       }
-                       print $patch_line . "<br/>\n";
+                       print format_extended_diff_header_line($patch_line, $diffinfo,
+                                                              \%from, \%to);
                }
                print "</div>\n"  if (@diff_header > 0); # class="diff extended_header"
 
@@ -2897,24 +3035,14 @@ sub git_patchset_body {
                }
                next PATCH if ($patch_line =~ m/^diff /);
                #assert($patch_line =~ m/^---/) if DEBUG;
-               if (!$diffinfo->{'nparents'} && # not from-file line for combined diff
-                   $from{'href'} && $patch_line =~ m!^--- "?a/!) {
-                       $patch_line = '--- a/' .
-                                     $cgi->a({-href=>$from{'href'}, -class=>"path"},
-                                             esc_path($from{'file'}));
-               }
-               print "<div class=\"diff from_file\">$patch_line</div>\n";
+               #assert($patch_line eq $last_patch_line) if DEBUG;
 
                $patch_line = <$fd>;
                chomp $patch_line;
+               #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
 
-               #assert($patch_line =~ m/^+++/) if DEBUG;
-               if ($to{'href'} && $patch_line =~ m!^\+\+\+ "?b/!) {
-                       $patch_line = '+++ b/' .
-                                     $cgi->a({-href=>$to{'href'}, -class=>"path"},
-                                             esc_path($to{'file'}));
-               }
-               print "<div class=\"diff to_file\">$patch_line</div>\n";
+               print format_diff_from_to_header($last_patch_line, $patch_line,
+                                                $diffinfo, \%from, \%to);
 
                # the patch itself
        LINE:
@@ -4458,7 +4586,14 @@ sub git_commitdiff {
                                $hash_parent_short = substr($hash_parent, 0, 7);
                        }
                        $formats_nav .=
-                               ' (from: ' .
+                               ' (from';
+                       for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
+                               if ($co{'parents'}[$i] eq $hash_parent) {
+                                       $formats_nav .= ' parent ' . ($i+1);
+                                       last;
+                               }
+                       }
+                       $formats_nav .= ': ' .
                                $cgi->a({-href => href(action=>"commitdiff",
                                                       hash=>$hash_parent)},
                                        esc_html($hash_parent_short)) .