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>
Jeff King committed
Oct 18, 2011 at 00:52 UTC
927a13fe87e4b2e0edabb2f8615f0851f70fbbd8
2 files changed
+181
contrib/diff-highlight/README
new
+57
@@ -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
+---------------------------------------------
contrib/diff-highlight/diff-highlight
new
+124
@@ -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
+}