Merge branch 'aw/rebase-i-stop-on-failure-to-amend' into maint
[git] / contrib / diff-highlight / diff-highlight
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 }