diff options
Diffstat (limited to 'contrib/diff-highlight')
-rw-r--r-- | contrib/diff-highlight/.gitignore | 2 | ||||
-rw-r--r--[-rwxr-xr-x] | contrib/diff-highlight/DiffHighlight.pm (renamed from contrib/diff-highlight/diff-highlight) | 112 | ||||
-rw-r--r-- | contrib/diff-highlight/Makefile | 23 | ||||
-rw-r--r-- | contrib/diff-highlight/README | 30 | ||||
-rw-r--r-- | contrib/diff-highlight/diff-highlight.perl | 8 | ||||
-rw-r--r-- | contrib/diff-highlight/t/.gitignore | 2 | ||||
-rw-r--r-- | contrib/diff-highlight/t/Makefile | 22 | ||||
-rwxr-xr-x | contrib/diff-highlight/t/t9400-diff-highlight.sh | 341 |
8 files changed, 515 insertions, 25 deletions
diff --git a/contrib/diff-highlight/.gitignore b/contrib/diff-highlight/.gitignore new file mode 100644 index 0000000000..c07454824e --- /dev/null +++ b/contrib/diff-highlight/.gitignore @@ -0,0 +1,2 @@ +shebang.perl +diff-highlight diff --git a/contrib/diff-highlight/diff-highlight b/contrib/diff-highlight/DiffHighlight.pm index ffefc31a98..536754583b 100755..100644 --- a/contrib/diff-highlight/diff-highlight +++ b/contrib/diff-highlight/DiffHighlight.pm @@ -1,4 +1,4 @@ -#!/usr/bin/perl +package DiffHighlight; use 5.008; use warnings FATAL => 'all'; @@ -24,28 +24,78 @@ my $BORING = qr/$COLOR|\s/; my @removed; my @added; my $in_hunk; +my $graph_indent = 0; -# Some scripts may not realize that SIGPIPE is being ignored when launching the -# pager--for instance scripts written in Python. -$SIG{PIPE} = 'DEFAULT'; +our $line_cb = sub { print @_ }; +our $flush_cb = sub { local $| = 1 }; + +# Count the visible width of a string, excluding any terminal color sequences. +sub visible_width { + local $_ = shift; + my $ret = 0; + while (length) { + if (s/^$COLOR//) { + # skip colors + } elsif (s/^.//) { + $ret++; + } + } + return $ret; +} + +# Return a substring of $str, omitting $len visible characters from the +# beginning, where terminal color sequences do not count as visible. +sub visible_substr { + my ($str, $len) = @_; + while ($len > 0) { + if ($str =~ s/^$COLOR//) { + next + } + $str =~ s/^.//; + $len--; + } + return $str; +} + +sub handle_line { + my $orig = shift; + local $_ = $orig; + + # match a graph line that begins a commit + if (/^(?:$COLOR?\|$COLOR?[ ])* # zero or more leading "|" with space + $COLOR?\*$COLOR?[ ] # a "*" with its trailing space + (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|" + [ ]* # trailing whitespace for merges + /x) { + my $graph_prefix = $&; + + # We must flush before setting graph indent, since the + # new commit may be indented differently from what we + # queued. + flush(); + $graph_indent = visible_width($graph_prefix); + + } elsif ($graph_indent) { + if (length($_) < $graph_indent) { + $graph_indent = 0; + } else { + $_ = visible_substr($_, $graph_indent); + } + } -while (<>) { if (!$in_hunk) { - print; - $in_hunk = /^$COLOR*\@/; + $line_cb->($orig); + $in_hunk = /^$COLOR*\@\@ /; } elsif (/^$COLOR*-/) { - push @removed, $_; + push @removed, $orig; } elsif (/^$COLOR*\+/) { - push @added, $_; + push @added, $orig; } else { - show_hunk(\@removed, \@added); - @removed = (); - @added = (); - - print; + flush(); + $line_cb->($orig); $in_hunk = /^$COLOR*[\@ ]/; } @@ -58,15 +108,24 @@ while (<>) { # place to flush. Flushing on a blank line is a heuristic that # happens to match git-log output. if (!length) { - local $| = 1; + $flush_cb->(); } } -# Flush any queued hunk (this can happen when there is no trailing context in -# the final diff of the input). -show_hunk(\@removed, \@added); +sub flush { + # Flush any queued hunk (this can happen when there is no trailing + # context in the final diff of the input). + show_hunk(\@removed, \@added); + @removed = (); + @added = (); +} -exit 0; +sub highlight_stdin { + while (<STDIN>) { + handle_line($_); + } + flush(); +} # Ideally we would feed the default as a human-readable color to # git-config as the fallback value. But diff-highlight does @@ -84,7 +143,7 @@ sub show_hunk { # If one side is empty, then there is nothing to compare or highlight. if (!@$a || !@$b) { - print @$a, @$b; + $line_cb->(@$a, @$b); return; } @@ -93,17 +152,17 @@ sub show_hunk { # stupid, and only handle multi-line hunks that remove and add the same # number of lines. if (@$a != @$b) { - print @$a, @$b; + $line_cb->(@$a, @$b); return; } my @queue; for (my $i = 0; $i < @$a; $i++) { my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); - print $rm; + $line_cb->($rm); push @queue, $add; } - print @queue; + $line_cb->(@queue); } sub highlight_pair { @@ -163,6 +222,9 @@ sub highlight_pair { } } +# we split either by $COLOR or by character. This has the side effect of +# leaving in graph cruft. It works because the graph cruft does not contain "-" +# or "+" sub split_line { local $_ = shift; return utf8::decode($_) ? @@ -211,8 +273,8 @@ sub is_pair_interesting { my $suffix_a = join('', @$a[($sa+1)..$#$a]); my $suffix_b = join('', @$b[($sb+1)..$#$b]); - return $prefix_a !~ /^$COLOR*-$BORING*$/ || - $prefix_b !~ /^$COLOR*\+$BORING*$/ || + return visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ || + visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\+$BORING*$/ || $suffix_a !~ /^$BORING*$/ || $suffix_b !~ /^$BORING*$/; } diff --git a/contrib/diff-highlight/Makefile b/contrib/diff-highlight/Makefile new file mode 100644 index 0000000000..f2be7cc924 --- /dev/null +++ b/contrib/diff-highlight/Makefile @@ -0,0 +1,23 @@ +all: diff-highlight + +PERL_PATH = /usr/bin/perl +-include ../../config.mak + +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) + +diff-highlight: shebang.perl DiffHighlight.pm diff-highlight.perl + cat $^ >$@+ + chmod +x $@+ + mv $@+ $@ + +shebang.perl: FORCE + @echo '#!$(PERL_PATH_SQ)' >$@+ + @cmp $@+ $@ >/dev/null 2>/dev/null || mv $@+ $@ + +test: all + $(MAKE) -C t + +clean: + $(RM) diff-highlight + +.PHONY: FORCE diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README index 836b97a730..d4c2343175 100644 --- a/contrib/diff-highlight/README +++ b/contrib/diff-highlight/README @@ -99,6 +99,36 @@ newHighlight = "black #aaffaa" --------------------------------------------- +Using diff-highlight as a module +-------------------------------- + +If you want to pre- or post- process the highlighted lines as part of +another perl script, you can use the DiffHighlight module. You can +either "require" it or just cat the module together with your script (to +avoid run-time dependencies). + +Your script may set up one or more of the following variables: + + - $DiffHighlight::line_cb - this should point to a function which is + called whenever DiffHighlight has lines (which may contain + highlights) to output. The default function prints each line to + stdout. Note that the function may be called with multiple lines. + + - $DiffHighlight::flush_cb - this should point to a function which + flushes the output (because DiffHighlight believes it has completed + processing a logical chunk of input). The default function flushes + stdout. + +The script may then feed lines, one at a time, to DiffHighlight::handle_line(). +When lines are done processing, they will be fed to $line_cb. Note that +DiffHighlight may queue up many input lines (to analyze a whole hunk) +before calling $line_cb. After providing all lines, call +DiffHighlight::flush() to flush any unprocessed lines. + +If you just want to process stdin, DiffHighlight::highlight_stdin() +is a convenience helper which will loop and flush for you. + + Bugs ---- diff --git a/contrib/diff-highlight/diff-highlight.perl b/contrib/diff-highlight/diff-highlight.perl new file mode 100644 index 0000000000..9b3e9c1f4d --- /dev/null +++ b/contrib/diff-highlight/diff-highlight.perl @@ -0,0 +1,8 @@ +package main; + +# Some scripts may not realize that SIGPIPE is being ignored when launching the +# pager--for instance scripts written in Python. +$SIG{PIPE} = 'DEFAULT'; + +DiffHighlight::highlight_stdin(); +exit 0; diff --git a/contrib/diff-highlight/t/.gitignore b/contrib/diff-highlight/t/.gitignore new file mode 100644 index 0000000000..7dcbb232cd --- /dev/null +++ b/contrib/diff-highlight/t/.gitignore @@ -0,0 +1,2 @@ +/trash directory* +/test-results diff --git a/contrib/diff-highlight/t/Makefile b/contrib/diff-highlight/t/Makefile new file mode 100644 index 0000000000..5ff5275496 --- /dev/null +++ b/contrib/diff-highlight/t/Makefile @@ -0,0 +1,22 @@ +-include ../../../config.mak.autogen +-include ../../../config.mak + +# copied from ../../t/Makefile +SHELL_PATH ?= $(SHELL) +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) + +all: test +test: $(T) + +.PHONY: help clean all test $(T) + +help: + @echo 'Run "$(MAKE) test" to launch test scripts' + @echo 'Run "$(MAKE) clean" to remove trash folders' + +$(T): + @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) + +clean: + $(RM) -r 'trash directory'.* diff --git a/contrib/diff-highlight/t/t9400-diff-highlight.sh b/contrib/diff-highlight/t/t9400-diff-highlight.sh new file mode 100755 index 0000000000..f6f5195d00 --- /dev/null +++ b/contrib/diff-highlight/t/t9400-diff-highlight.sh @@ -0,0 +1,341 @@ +#!/bin/sh + +test_description='Test diff-highlight' + +CURR_DIR=$(pwd) +TEST_OUTPUT_DIRECTORY=$(pwd) +TEST_DIRECTORY="$CURR_DIR"/../../../t +DIFF_HIGHLIGHT="$CURR_DIR"/../diff-highlight + +CW="$(printf "\033[7m")" # white +CR="$(printf "\033[27m")" # reset + +. "$TEST_DIRECTORY"/test-lib.sh + +if ! test_have_prereq PERL +then + skip_all='skipping diff-highlight tests; perl not available' + test_done +fi + +# dh_test is a test helper function which takes 3 file names as parameters. The +# first 2 files are used to generate diff and commit output, which is then +# piped through diff-highlight. The 3rd file should contain the expected output +# of diff-highlight (minus the diff/commit header, ie. everything after and +# including the first @@ line). +dh_test () { + a="$1" b="$2" && + + cat >patch.exp && + + { + cat "$a" >file && + git add file && + git commit -m "Add a file" && + + cat "$b" >file && + git diff file >diff.raw && + git commit -a -m "Update a file" && + git show >commit.raw + } >/dev/null && + + "$DIFF_HIGHLIGHT" <diff.raw | test_strip_patch_header >diff.act && + "$DIFF_HIGHLIGHT" <commit.raw | test_strip_patch_header >commit.act && + test_cmp patch.exp diff.act && + test_cmp patch.exp commit.act +} + +test_strip_patch_header () { + sed -n '/^@@/,$p' $* +} + +# dh_test_setup_history generates a contrived graph such that we have at least +# 1 nesting (E) and 2 nestings (F). +# +# A---B master +# / +# D---E---F branch +# +# git log --all --graph +# * commit +# | B +# | * commit +# | | F +# * | commit +# | | A +# | * commit +# |/ +# | E +# * commit +# D +# +dh_test_setup_history () { + echo file1 >file && + git add file && + test_tick && + git commit -m "D" && + + git checkout -b branch && + echo file2 >file && + test_tick && + git commit -a -m "E" && + + git checkout master && + echo file2 >file && + test_tick && + git commit -a -m "A" && + + git checkout branch && + echo file3 >file && + test_tick && + git commit -a -m "F" && + + git checkout master && + echo file3 >file && + test_tick && + git commit -a -m "B" +} + +left_trim () { + "$PERL_PATH" -pe 's/^\s+//' +} + +trim_graph () { + # graphs start with * or | + # followed by a space or / or \ + "$PERL_PATH" -pe 's@^((\*|\|)( |/|\\))+@@' +} + +test_expect_success 'diff-highlight highlights the beginning of a line' ' + cat >a <<-\EOF && + aaa + bbb + ccc + EOF + + cat >b <<-\EOF && + aaa + 0bb + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -${CW}b${CR}bb + +${CW}0${CR}bb + ccc + EOF +' + +test_expect_success 'diff-highlight highlights the end of a line' ' + cat >a <<-\EOF && + aaa + bbb + ccc + EOF + + cat >b <<-\EOF && + aaa + bb0 + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -bb${CW}b${CR} + +bb${CW}0${CR} + ccc + EOF +' + +test_expect_success 'diff-highlight highlights the middle of a line' ' + cat >a <<-\EOF && + aaa + bbb + ccc + EOF + + cat >b <<-\EOF && + aaa + b0b + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -b${CW}b${CR}b + +b${CW}0${CR}b + ccc + EOF +' + +test_expect_success 'diff-highlight does not highlight whole line' ' + cat >a <<-\EOF && + aaa + bbb + ccc + EOF + + cat >b <<-\EOF && + aaa + 000 + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -bbb + +000 + ccc + EOF +' + +test_expect_failure 'diff-highlight highlights mismatched hunk size' ' + cat >a <<-\EOF && + aaa + bbb + EOF + + cat >b <<-\EOF && + aaa + b0b + ccc + EOF + + dh_test a b <<-EOF + @@ -1,3 +1,3 @@ + aaa + -b${CW}b${CR}b + +b${CW}0${CR}b + +ccc + EOF +' + +# These two code points share the same leading byte in UTF-8 representation; +# a naive byte-wise diff would highlight only the second byte. +# +# - U+00f3 ("o" with acute) +o_accent=$(printf '\303\263') +# - U+00f8 ("o" with stroke) +o_stroke=$(printf '\303\270') + +test_expect_success 'diff-highlight treats multibyte utf-8 as a unit' ' + echo "unic${o_accent}de" >a && + echo "unic${o_stroke}de" >b && + dh_test a b <<-EOF + @@ -1 +1 @@ + -unic${CW}${o_accent}${CR}de + +unic${CW}${o_stroke}${CR}de + EOF +' + +# Unlike the UTF-8 above, these are combining code points which are meant +# to modify the character preceding them: +# +# - U+0301 (combining acute accent) +combine_accent=$(printf '\314\201') +# - U+0302 (combining circumflex) +combine_circum=$(printf '\314\202') + +test_expect_failure 'diff-highlight treats combining code points as a unit' ' + echo "unico${combine_accent}de" >a && + echo "unico${combine_circum}de" >b && + dh_test a b <<-EOF + @@ -1 +1 @@ + -unic${CW}o${combine_accent}${CR}de + +unic${CW}o${combine_circum}${CR}de + EOF +' + +test_expect_success 'diff-highlight works with the --graph option' ' + dh_test_setup_history && + + # date-order so that the commits are interleaved for both + # trim graph elements so we can do a diff + # trim leading space because our trim_graph is not perfect + git log --branches -p --date-order | + "$DIFF_HIGHLIGHT" | left_trim >graph.exp && + git log --branches -p --date-order --graph | + "$DIFF_HIGHLIGHT" | trim_graph | left_trim >graph.act && + test_cmp graph.exp graph.act +' + +# Just reuse the previous graph test, but with --color. Our trimming +# doesn't know about color, so just sanity check that something got +# highlighted. +test_expect_success 'diff-highlight works with color graph' ' + git log --branches -p --date-order --graph --color | + "$DIFF_HIGHLIGHT" | trim_graph | left_trim >graph && + grep "\[7m" graph +' + +# Most combined diffs won't meet diff-highlight's line-number filter. So we +# create one here where one side drops a line and the other modifies it. That +# should result in a diff like: +# +# - modified content +# ++resolved content +# +# which naively looks like one side added "+resolved". +test_expect_success 'diff-highlight ignores combined diffs' ' + echo "content" >file && + git add file && + git commit -m base && + + >file && + git commit -am master && + + git checkout -b other HEAD^ && + echo "modified content" >file && + git commit -am other && + + test_must_fail git merge master && + echo "resolved content" >file && + git commit -am resolved && + + cat >expect <<-\EOF && + --- a/file + +++ b/file + @@@ -1,1 -1,0 +1,1 @@@ + - modified content + ++resolved content + EOF + + git show -c | "$DIFF_HIGHLIGHT" >actual.raw && + sed -n "/^---/,\$p" <actual.raw >actual && + test_cmp expect actual +' + +test_expect_success 'diff-highlight handles --graph with leading dash' ' + cat >file <<-\EOF && + before + the old line + -leading dash + EOF + git add file && + git commit -m before && + + sed s/old/new/ <file >file.tmp && + mv file.tmp file && + git add file && + git commit -m after && + + cat >expect <<-EOF && + --- a/file + +++ b/file + @@ -1,3 +1,3 @@ + before + -the ${CW}old${CR} line + +the ${CW}new${CR} line + -leading dash + EOF + git log --graph -p -1 | "$DIFF_HIGHLIGHT" >actual.raw && + trim_graph <actual.raw | sed -n "/^---/,\$p" >actual && + test_cmp expect actual +' + +test_done |