]> Lady’s Gitweb - Gitweb/blobdiff - gitweb.perl
gitweb: Refinement highlightning in combined diffs
[Gitweb] / gitweb.perl
index 738a0051efeff231363f31e8516b71d6a8a1d4fb98603151e425314110ec1448..656ac981da93103884aa81152609b6d6456050c54475d47059f1150eff6d01ed 100755 (executable)
@@ -2431,26 +2431,32 @@ sub format_cc_diff_chunk_header {
 }
 
 # process patch (diff) line (not to be used for diff headers),
 }
 
 # process patch (diff) line (not to be used for diff headers),
-# returning class and HTML-formatted (but not wrapped) line
-sub process_diff_line {
-       my $line = shift;
-       my ($from, $to) = @_;
-
-       my $diff_class = diff_line_class($line, $from, $to);
-
-       chomp $line;
-       $line = untabify($line);
+# returning HTML-formatted (but not wrapped) line.
+# If the line is passed as a reference, it is treated as HTML and not
+# esc_html()'ed.
+sub format_diff_line {
+       my ($line, $diff_class, $from, $to) = @_;
+
+       if (ref($line)) {
+               $line = $$line;
+       } else {
+               chomp $line;
+               $line = untabify($line);
 
 
-       if ($from && $to && $line =~ m/^\@{2} /) {
-               $line = format_unidiff_chunk_header($line, $from, $to);
-               return $diff_class, $line;
+               if ($from && $to && $line =~ m/^\@{2} /) {
+                       $line = format_unidiff_chunk_header($line, $from, $to);
+               } elsif ($from && $to && $line =~ m/^\@{3}/) {
+                       $line = format_cc_diff_chunk_header($line, $from, $to);
+               } else {
+                       $line = esc_html($line, -nbsp=>1);
+               }
+       }
 
 
-       } elsif ($from && $to && $line =~ m/^\@{3}/) {
-               $line = format_cc_diff_chunk_header($line, $from, $to);
-               return $diff_class, $line;
+       my $diff_classes = "diff";
+       $diff_classes .= " $diff_class" if ($diff_class);
+       $line = "<div class=\"$diff_classes\">$line</div>\n";
 
 
-       }
-       return $diff_class, esc_html($line, -nbsp=>1);
+       return $line;
 }
 
 # Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
 }
 
 # Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@ -5057,9 +5063,117 @@ sub print_inline_diff_lines {
        print @$ctx, @$rem, @$add;
 }
 
        print @$ctx, @$rem, @$add;
 }
 
+# Format removed and added line, mark changed part and HTML-format them.
+# Implementation is based on contrib/diff-highlight
+sub format_rem_add_lines_pair {
+       my ($rem, $add, $num_parents) = @_;
+
+       # We need to untabify lines before split()'ing them;
+       # otherwise offsets would be invalid.
+       chomp $rem;
+       chomp $add;
+       $rem = untabify($rem);
+       $add = untabify($add);
+
+       my @rem = split(//, $rem);
+       my @add = split(//, $add);
+       my ($esc_rem, $esc_add);
+       # Ignore leading +/- characters for each parent.
+       my ($prefix_len, $suffix_len) = ($num_parents, 0);
+       my ($prefix_has_nonspace, $suffix_has_nonspace);
+
+       my $shorter = (@rem < @add) ? @rem : @add;
+       while ($prefix_len < $shorter) {
+               last if ($rem[$prefix_len] ne $add[$prefix_len]);
+
+               $prefix_has_nonspace = 1 if ($rem[$prefix_len] !~ /\s/);
+               $prefix_len++;
+       }
+
+       while ($prefix_len + $suffix_len < $shorter) {
+               last if ($rem[-1 - $suffix_len] ne $add[-1 - $suffix_len]);
+
+               $suffix_has_nonspace = 1 if ($rem[-1 - $suffix_len] !~ /\s/);
+               $suffix_len++;
+       }
+
+       # Mark lines that are different from each other, but have some common
+       # part that isn't whitespace.  If lines are completely different, don't
+       # mark them because that would make output unreadable, especially if
+       # diff consists of multiple lines.
+       if ($prefix_has_nonspace || $suffix_has_nonspace) {
+               $esc_rem = esc_html_hl_regions($rem, 'marked',
+                       [$prefix_len, @rem - $suffix_len], -nbsp=>1);
+               $esc_add = esc_html_hl_regions($add, 'marked',
+                       [$prefix_len, @add - $suffix_len], -nbsp=>1);
+       } else {
+               $esc_rem = esc_html($rem, -nbsp=>1);
+               $esc_add = esc_html($add, -nbsp=>1);
+       }
+
+       return format_diff_line(\$esc_rem, 'rem'),
+              format_diff_line(\$esc_add, 'add');
+}
+
+# HTML-format diff context, removed and added lines.
+sub format_ctx_rem_add_lines {
+       my ($ctx, $rem, $add, $num_parents) = @_;
+       my (@new_ctx, @new_rem, @new_add);
+       my $can_highlight = 0;
+       my $is_combined = ($num_parents > 1);
+
+       # Highlight if every removed line has a corresponding added line.
+       if (@$add > 0 && @$add == @$rem) {
+               $can_highlight = 1;
+
+               # Highlight lines in combined diff only if the chunk contains
+               # diff between the same version, e.g.
+               #
+               #    - a
+               #   -  b
+               #    + c
+               #   +  d
+               #
+               # Otherwise the highlightling would be confusing.
+               if ($is_combined) {
+                       for (my $i = 0; $i < @$add; $i++) {
+                               my $prefix_rem = substr($rem->[$i], 0, $num_parents);
+                               my $prefix_add = substr($add->[$i], 0, $num_parents);
+
+                               $prefix_rem =~ s/-/+/g;
+
+                               if ($prefix_rem ne $prefix_add) {
+                                       $can_highlight = 0;
+                                       last;
+                               }
+                       }
+               }
+       }
+
+       if ($can_highlight) {
+               for (my $i = 0; $i < @$add; $i++) {
+                       my ($line_rem, $line_add) = format_rem_add_lines_pair(
+                               $rem->[$i], $add->[$i], $num_parents);
+                       push @new_rem, $line_rem;
+                       push @new_add, $line_add;
+               }
+       } else {
+               @new_rem = map { format_diff_line($_, 'rem') } @$rem;
+               @new_add = map { format_diff_line($_, 'add') } @$add;
+       }
+
+       @new_ctx = map { format_diff_line($_, 'ctx') } @$ctx;
+
+       return (\@new_ctx, \@new_rem, \@new_add);
+}
+
 # Print context lines and then rem/add lines.
 sub print_diff_lines {
 # Print context lines and then rem/add lines.
 sub print_diff_lines {
-       my ($ctx, $rem, $add, $diff_style, $is_combined) = @_;
+       my ($ctx, $rem, $add, $diff_style, $num_parents) = @_;
+       my $is_combined = $num_parents > 1;
+
+       ($ctx, $rem, $add) = format_ctx_rem_add_lines($ctx, $rem, $add,
+               $num_parents);
 
        if ($diff_style eq 'sidebyside' && !$is_combined) {
                print_sidebyside_diff_lines($ctx, $rem, $add);
 
        if ($diff_style eq 'sidebyside' && !$is_combined) {
                print_sidebyside_diff_lines($ctx, $rem, $add);
@@ -5070,7 +5184,7 @@ sub print_diff_lines {
 }
 
 sub print_diff_chunk {
 }
 
 sub print_diff_chunk {
-       my ($diff_style, $is_combined, @chunk) = @_;
+       my ($diff_style, $num_parents, $from, $to, @chunk) = @_;
        my (@ctx, @rem, @add);
 
        # The class of the previous line.
        my (@ctx, @rem, @add);
 
        # The class of the previous line.
@@ -5094,7 +5208,7 @@ sub print_diff_chunk {
 
                # print chunk headers
                if ($class && $class eq 'chunk_header') {
 
                # print chunk headers
                if ($class && $class eq 'chunk_header') {
-                       print $line;
+                       print format_diff_line($line, $class, $from, $to);
                        next;
                }
 
                        next;
                }
 
@@ -5105,7 +5219,7 @@ sub print_diff_chunk {
                if (!$class || ((@rem || @add) && $class eq 'ctx') ||
                    (@rem && @add && $class ne $prev_class)) {
                        print_diff_lines(\@ctx, \@rem, \@add,
                if (!$class || ((@rem || @add) && $class eq 'ctx') ||
                    (@rem && @add && $class ne $prev_class)) {
                        print_diff_lines(\@ctx, \@rem, \@add,
-                                        $diff_style, $is_combined);
+                                        $diff_style, $num_parents);
                        @ctx = @rem = @add = ();
                }
 
                        @ctx = @rem = @add = ();
                }
 
@@ -5245,22 +5359,19 @@ sub git_patchset_body {
 
                        next PATCH if ($patch_line =~ m/^diff /);
 
 
                        next PATCH if ($patch_line =~ m/^diff /);
 
-                       my ($class, $line) = process_diff_line($patch_line, \%from, \%to);
-                       my $diff_classes = "diff";
-                       $diff_classes .= " $class" if ($class);
-                       $line = "<div class=\"$diff_classes\">$line</div>\n";
+                       my $class = diff_line_class($patch_line, \%from, \%to);
 
                        if ($class eq 'chunk_header') {
 
                        if ($class eq 'chunk_header') {
-                               print_diff_chunk($diff_style, $is_combined, @chunk);
+                               print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
                                @chunk = ();
                        }
 
                                @chunk = ();
                        }
 
-                       push @chunk, [ $class, $line ];
+                       push @chunk, [ $class, $patch_line ];
                }
 
        } continue {
                if (@chunk) {
                }
 
        } continue {
                if (@chunk) {
-                       print_diff_chunk($diff_style, $is_combined, @chunk);
+                       print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
                        @chunk = ();
                }
                print "</div>\n"; # class="patch"
                        @chunk = ();
                }
                print "</div>\n"; # class="patch"
This page took 0.327634 seconds and 4 git commands to generate.