Skip to content

Commit 927a13f

Browse files
peffgitster
authored andcommitted
contrib: add diff highlight script
This is a simple and stupid script for highlighting differing parts of lines in a unified diff. See the README for a discussion of the limitations. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 08cfdbb commit 927a13f

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed

contrib/diff-highlight/README

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
diff-highlight
2+
==============
3+
4+
Line oriented diffs are great for reviewing code, because for most
5+
hunks, you want to see the old and the new segments of code next to each
6+
other. Sometimes, though, when an old line and a new line are very
7+
similar, it's hard to immediately see the difference.
8+
9+
You can use "--color-words" to highlight only the changed portions of
10+
lines. However, this can often be hard to read for code, as it loses
11+
the line structure, and you end up with oddly formatted bits.
12+
13+
Instead, this script post-processes the line-oriented diff, finds pairs
14+
of lines, and highlights the differing segments. It's currently very
15+
simple and stupid about doing these tasks. In particular:
16+
17+
1. It will only highlight a pair of lines if they are the only two
18+
lines in a hunk. It could instead try to match up "before" and
19+
"after" lines for a given hunk into pairs of similar lines.
20+
However, this may end up visually distracting, as the paired
21+
lines would have other highlighted lines in between them. And in
22+
practice, the lines which most need attention called to their
23+
small, hard-to-see changes are touching only a single line.
24+
25+
2. It will find the common prefix and suffix of two lines, and
26+
consider everything in the middle to be "different". It could
27+
instead do a real diff of the characters between the two lines and
28+
find common subsequences. However, the point of the highlight is to
29+
call attention to a certain area. Even if some small subset of the
30+
highlighted area actually didn't change, that's OK. In practice it
31+
ends up being more readable to just have a single blob on the line
32+
showing the interesting bit.
33+
34+
The goal of the script is therefore not to be exact about highlighting
35+
changes, but to call attention to areas of interest without being
36+
visually distracting. Non-diff lines and existing diff coloration is
37+
preserved; the intent is that the output should look exactly the same as
38+
the input, except for the occasional highlight.
39+
40+
Use
41+
---
42+
43+
You can try out the diff-highlight program with:
44+
45+
---------------------------------------------
46+
git log -p --color | /path/to/diff-highlight
47+
---------------------------------------------
48+
49+
If you want to use it all the time, drop it in your $PATH and put the
50+
following in your git configuration:
51+
52+
---------------------------------------------
53+
[pager]
54+
log = diff-highlight | less
55+
show = diff-highlight | less
56+
diff = diff-highlight | less
57+
---------------------------------------------
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/perl
2+
3+
# Highlight by reversing foreground and background. You could do
4+
# other things like bold or underline if you prefer.
5+
my $HIGHLIGHT = "\x1b[7m";
6+
my $UNHIGHLIGHT = "\x1b[27m";
7+
my $COLOR = qr/\x1b\[[0-9;]*m/;
8+
9+
my @window;
10+
11+
while (<>) {
12+
# We highlight only single-line changes, so we need
13+
# a 4-line window to make a decision on whether
14+
# to highlight.
15+
push @window, $_;
16+
next if @window < 4;
17+
if ($window[0] =~ /^$COLOR*(\@| )/ &&
18+
$window[1] =~ /^$COLOR*-/ &&
19+
$window[2] =~ /^$COLOR*\+/ &&
20+
$window[3] !~ /^$COLOR*\+/) {
21+
print shift @window;
22+
show_pair(shift @window, shift @window);
23+
}
24+
else {
25+
print shift @window;
26+
}
27+
28+
# Most of the time there is enough output to keep things streaming,
29+
# but for something like "git log -Sfoo", you can get one early
30+
# commit and then many seconds of nothing. We want to show
31+
# that one commit as soon as possible.
32+
#
33+
# Since we can receive arbitrary input, there's no optimal
34+
# place to flush. Flushing on a blank line is a heuristic that
35+
# happens to match git-log output.
36+
if (!length) {
37+
local $| = 1;
38+
}
39+
}
40+
41+
# Special case a single-line hunk at the end of file.
42+
if (@window == 3 &&
43+
$window[0] =~ /^$COLOR*(\@| )/ &&
44+
$window[1] =~ /^$COLOR*-/ &&
45+
$window[2] =~ /^$COLOR*\+/) {
46+
print shift @window;
47+
show_pair(shift @window, shift @window);
48+
}
49+
50+
# And then flush any remaining lines.
51+
while (@window) {
52+
print shift @window;
53+
}
54+
55+
exit 0;
56+
57+
sub show_pair {
58+
my @a = split_line(shift);
59+
my @b = split_line(shift);
60+
61+
# Find common prefix, taking care to skip any ansi
62+
# color codes.
63+
my $seen_plusminus;
64+
my ($pa, $pb) = (0, 0);
65+
while ($pa < @a && $pb < @b) {
66+
if ($a[$pa] =~ /$COLOR/) {
67+
$pa++;
68+
}
69+
elsif ($b[$pb] =~ /$COLOR/) {
70+
$pb++;
71+
}
72+
elsif ($a[$pa] eq $b[$pb]) {
73+
$pa++;
74+
$pb++;
75+
}
76+
elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {
77+
$seen_plusminus = 1;
78+
$pa++;
79+
$pb++;
80+
}
81+
else {
82+
last;
83+
}
84+
}
85+
86+
# Find common suffix, ignoring colors.
87+
my ($sa, $sb) = ($#a, $#b);
88+
while ($sa >= $pa && $sb >= $pb) {
89+
if ($a[$sa] =~ /$COLOR/) {
90+
$sa--;
91+
}
92+
elsif ($b[$sb] =~ /$COLOR/) {
93+
$sb--;
94+
}
95+
elsif ($a[$sa] eq $b[$sb]) {
96+
$sa--;
97+
$sb--;
98+
}
99+
else {
100+
last;
101+
}
102+
}
103+
104+
print highlight(\@a, $pa, $sa);
105+
print highlight(\@b, $pb, $sb);
106+
}
107+
108+
sub split_line {
109+
local $_ = shift;
110+
return map { /$COLOR/ ? $_ : (split //) }
111+
split /($COLOR*)/;
112+
}
113+
114+
sub highlight {
115+
my ($line, $prefix, $suffix) = @_;
116+
117+
return join('',
118+
@{$line}[0..($prefix-1)],
119+
$HIGHLIGHT,
120+
@{$line}[$prefix..$suffix],
121+
$UNHIGHLIGHT,
122+
@{$line}[($suffix+1)..$#$line]
123+
);
124+
}

0 commit comments

Comments
 (0)