Skip to content

Commit 5fb6ddf

Browse files
mkiedrowiczgitster
authored andcommitted
gitweb: Highlight interesting parts of diff
Reading diff output is sometimes very hard, even if it's colored, especially if lines differ only in few characters. This is often true when a commit fixes a typo or renames some variables or functions. This commit teaches gitweb to highlight characters that are different between old and new line with a light green/red background. This should work in the similar manner as in Trac or GitHub. The algorithm that compares lines is based on contrib/diff-highlight. Basically, it works by determining common prefix/suffix of corresponding lines and highlightning only the middle part of lines. For more information, see contrib/diff-highlight/README. Combined diffs are not supported but a following commit will change it. Since we need to pass esc_html()'ed or esc_html_hl_regions()'ed lines to format_diff_lines(), so it was taught to accept preformatted lines passed as a reference. Signed-off-by: Michał Kiedrowicz <[email protected]> Acked-by: Jakub Narębski <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f4a8102 commit 5fb6ddf

File tree

2 files changed

+103
-12
lines changed

2 files changed

+103
-12
lines changed

gitweb/gitweb.perl

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2430,19 +2430,25 @@ sub format_cc_diff_chunk_header {
24302430
}
24312431

24322432
# process patch (diff) line (not to be used for diff headers),
2433-
# returning HTML-formatted (but not wrapped) line
2433+
# returning HTML-formatted (but not wrapped) line.
2434+
# If the line is passed as a reference, it is treated as HTML and not
2435+
# esc_html()'ed.
24342436
sub format_diff_line {
24352437
my ($line, $diff_class, $from, $to) = @_;
24362438

2437-
chomp $line;
2438-
$line = untabify($line);
2439-
2440-
if ($from && $to && $line =~ m/^\@{2} /) {
2441-
$line = format_unidiff_chunk_header($line, $from, $to);
2442-
} elsif ($from && $to && $line =~ m/^\@{3}/) {
2443-
$line = format_cc_diff_chunk_header($line, $from, $to);
2439+
if (ref($line)) {
2440+
$line = $$line;
24442441
} else {
2445-
$line = esc_html($line, -nbsp=>1);
2442+
chomp $line;
2443+
$line = untabify($line);
2444+
2445+
if ($from && $to && $line =~ m/^\@{2} /) {
2446+
$line = format_unidiff_chunk_header($line, $from, $to);
2447+
} elsif ($from && $to && $line =~ m/^\@{3}/) {
2448+
$line = format_cc_diff_chunk_header($line, $from, $to);
2449+
} else {
2450+
$line = esc_html($line, -nbsp=>1);
2451+
}
24462452
}
24472453

24482454
my $diff_classes = "diff";
@@ -5055,10 +5061,89 @@ sub print_inline_diff_lines {
50555061
print @$ctx, @$rem, @$add;
50565062
}
50575063

5064+
# Format removed and added line, mark changed part and HTML-format them.
5065+
# Implementation is based on contrib/diff-highlight
5066+
sub format_rem_add_lines_pair {
5067+
my ($rem, $add) = @_;
5068+
5069+
# We need to untabify lines before split()'ing them;
5070+
# otherwise offsets would be invalid.
5071+
chomp $rem;
5072+
chomp $add;
5073+
$rem = untabify($rem);
5074+
$add = untabify($add);
5075+
5076+
my @rem = split(//, $rem);
5077+
my @add = split(//, $add);
5078+
my ($esc_rem, $esc_add);
5079+
# Ignore +/- character, thus $prefix_len is set to 1.
5080+
my ($prefix_len, $suffix_len) = (1, 0);
5081+
my ($prefix_has_nonspace, $suffix_has_nonspace);
5082+
5083+
my $shorter = (@rem < @add) ? @rem : @add;
5084+
while ($prefix_len < $shorter) {
5085+
last if ($rem[$prefix_len] ne $add[$prefix_len]);
5086+
5087+
$prefix_has_nonspace = 1 if ($rem[$prefix_len] !~ /\s/);
5088+
$prefix_len++;
5089+
}
5090+
5091+
while ($prefix_len + $suffix_len < $shorter) {
5092+
last if ($rem[-1 - $suffix_len] ne $add[-1 - $suffix_len]);
5093+
5094+
$suffix_has_nonspace = 1 if ($rem[-1 - $suffix_len] !~ /\s/);
5095+
$suffix_len++;
5096+
}
5097+
5098+
# Mark lines that are different from each other, but have some common
5099+
# part that isn't whitespace. If lines are completely different, don't
5100+
# mark them because that would make output unreadable, especially if
5101+
# diff consists of multiple lines.
5102+
if ($prefix_has_nonspace || $suffix_has_nonspace) {
5103+
$esc_rem = esc_html_hl_regions($rem, 'marked',
5104+
[$prefix_len, @rem - $suffix_len], -nbsp=>1);
5105+
$esc_add = esc_html_hl_regions($add, 'marked',
5106+
[$prefix_len, @add - $suffix_len], -nbsp=>1);
5107+
} else {
5108+
$esc_rem = esc_html($rem, -nbsp=>1);
5109+
$esc_add = esc_html($add, -nbsp=>1);
5110+
}
5111+
5112+
return format_diff_line(\$esc_rem, 'rem'),
5113+
format_diff_line(\$esc_add, 'add');
5114+
}
5115+
5116+
# HTML-format diff context, removed and added lines.
5117+
sub format_ctx_rem_add_lines {
5118+
my ($ctx, $rem, $add, $is_combined) = @_;
5119+
my (@new_ctx, @new_rem, @new_add);
5120+
5121+
# Highlight if every removed line has a corresponding added line.
5122+
# Combined diffs are not supported at this moment.
5123+
if (!$is_combined && @$add > 0 && @$add == @$rem) {
5124+
for (my $i = 0; $i < @$add; $i++) {
5125+
my ($line_rem, $line_add) = format_rem_add_lines_pair(
5126+
$rem->[$i], $add->[$i]);
5127+
push @new_rem, $line_rem;
5128+
push @new_add, $line_add;
5129+
}
5130+
} else {
5131+
@new_rem = map { format_diff_line($_, 'rem') } @$rem;
5132+
@new_add = map { format_diff_line($_, 'add') } @$add;
5133+
}
5134+
5135+
@new_ctx = map { format_diff_line($_, 'ctx') } @$ctx;
5136+
5137+
return (\@new_ctx, \@new_rem, \@new_add);
5138+
}
5139+
50585140
# Print context lines and then rem/add lines.
50595141
sub print_diff_lines {
50605142
my ($ctx, $rem, $add, $diff_style, $is_combined) = @_;
50615143

5144+
($ctx, $rem, $add) = format_ctx_rem_add_lines($ctx, $rem, $add,
5145+
$is_combined);
5146+
50625147
if ($diff_style eq 'sidebyside' && !$is_combined) {
50635148
print_sidebyside_diff_lines($ctx, $rem, $add);
50645149
} else {
@@ -5090,11 +5175,9 @@ sub print_diff_chunk {
50905175
foreach my $line_info (@chunk) {
50915176
my ($class, $line) = @$line_info;
50925177

5093-
$line = format_diff_line($line, $class, $from, $to);
5094-
50955178
# print chunk headers
50965179
if ($class && $class eq 'chunk_header') {
5097-
print $line;
5180+
print format_diff_line($line, $class, $from, $to);
50985181
next;
50995182
}
51005183

gitweb/static/gitweb.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,10 @@ div.diff.add {
438438
color: #008800;
439439
}
440440

441+
div.diff.add span.marked {
442+
background-color: #aaffaa;
443+
}
444+
441445
div.diff.from_file a.path,
442446
div.diff.from_file {
443447
color: #aa0000;
@@ -447,6 +451,10 @@ div.diff.rem {
447451
color: #cc0000;
448452
}
449453

454+
div.diff.rem span.marked {
455+
background-color: #ffaaaa;
456+
}
457+
450458
div.diff.chunk_header a,
451459
div.diff.chunk_header {
452460
color: #990099;

0 commit comments

Comments
 (0)