From 41529f967f919685a3294a9fc4b45140311bebae Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 23 Mar 2026 02:01:58 -0400 Subject: [PATCH 1/8] diff-highlight: mention build instructions Once upon a time, this was just a script in a directory that could be run directly. That changed in 0c977dbc81 (diff-highlight: split code into module, 2017-06-15). Let's update the README to make it more clear that you need to run make. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- contrib/diff-highlight/README | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README index 1db4440e68..9c89146fb0 100644 --- a/contrib/diff-highlight/README +++ b/contrib/diff-highlight/README @@ -39,10 +39,21 @@ visually distracting. Non-diff lines and existing diff coloration is preserved; the intent is that the output should look exactly the same as the input, except for the occasional highlight. +Build/Install +------------- + +You can build the `diff-highlight` script by running `make` from within +the diff-highlight directory. There is no `make install` target; you can +copy the built script to your $PATH. + +You can run diff-highlight's internal tests by running `make test`. Note +that you must also build Git itself first (by running `make` from the +top-level of the project). + Use --- -You can try out the diff-highlight program with: +You can try out the built diff-highlight program with: --------------------------------------------- git log -p --color | /path/to/diff-highlight From 550097a645348426a3e5944d0457925381ff8e23 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 23 Mar 2026 02:02:02 -0400 Subject: [PATCH 2/8] diff-highlight: drop perl version dependency back to 5.8 The diff-highlight code does not rely on any perl features beyond what perl 5.8 provides. We bumped it to v5.26 along with the rest of the project's perl scripts in 702d8c1f3b (Require Perl 5.26.0, 2024-10-23). There's some value in just having a uniform baseline for the project, but I think diff-highlight is special here: - it's in a contrib/ directory that is not frequently touched, so there is little risk of Git developers getting annoyed that modern perl features are not available - it provides a module used by other projects. In particular, diff-so-fancy relies on DiffHighlight.pm but does not otherwise require a perl version more modern than 5.8. Let's drop back to the more conservative requirement. Signed-off-by: Scott Baker Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- contrib/diff-highlight/DiffHighlight.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm index f0607a4b68..a5e5de3b18 100644 --- a/contrib/diff-highlight/DiffHighlight.pm +++ b/contrib/diff-highlight/DiffHighlight.pm @@ -1,6 +1,6 @@ package DiffHighlight; -require v5.26; +require v5.008; use warnings FATAL => 'all'; use strict; From 05002f60565cdfe2b86108c7f7c027a41d534140 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 23 Mar 2026 02:02:05 -0400 Subject: [PATCH 3/8] diff-highlight: check diff-highlight exit status in tests When testing diff-highlight, we pipe the output through a sanitizing function. This loses the exit status of diff-highlight itself, which could mean we are missing cases where it crashes or exits unexpectedly. Use an extra tempfile to avoid the pipe. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- contrib/diff-highlight/t/t9400-diff-highlight.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/diff-highlight/t/t9400-diff-highlight.sh b/contrib/diff-highlight/t/t9400-diff-highlight.sh index 2a9b68cf3b..7ebff8b18f 100755 --- a/contrib/diff-highlight/t/t9400-diff-highlight.sh +++ b/contrib/diff-highlight/t/t9400-diff-highlight.sh @@ -41,8 +41,10 @@ dh_test () { git show >commit.raw } >/dev/null && - "$DIFF_HIGHLIGHT" diff.act && - "$DIFF_HIGHLIGHT" commit.act && + "$DIFF_HIGHLIGHT" diff.hi && + test_strip_patch_header diff.act && + "$DIFF_HIGHLIGHT" commit.hi && + test_strip_patch_header commit.act && test_cmp patch.exp diff.act && test_cmp patch.exp commit.act } From 0c49b950e8a0013c00051ad7a598a5ae0a2cdd07 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 23 Mar 2026 02:02:07 -0400 Subject: [PATCH 4/8] t: add matching negative attributes to test_decode_color Most of the ANSI color attributes have an "off" variant. We don't use these yet in our test suite, so we never bothered to decode them. Add the ones that match the attributes we encode so we can make use of them. There are even more attributes not covered on the positive side, so this is meant to be useful but not all-inclusive. Note that "nobold" and "nodim" are the same code, so I've decoded this as "normal intensity". Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- t/test-lib-functions.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 14e238d24d..f3af10fb7e 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -48,6 +48,9 @@ test_decode_color () { if (n == 2) return "FAINT"; if (n == 3) return "ITALIC"; if (n == 7) return "REVERSE"; + if (n == 22) return "NORMAL_INTENSITY"; + if (n == 23) return "NOITALIC"; + if (n == 27) return "NOREVERSE"; if (n == 30) return "BLACK"; if (n == 31) return "RED"; if (n == 32) return "GREEN"; From e57daf91ed0bde19595543c68c90948d5c47ebc6 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 23 Mar 2026 02:02:10 -0400 Subject: [PATCH 5/8] diff-highlight: use test_decode_color in tests The diff-highlight tests use raw color bytes when comparing expected and actual output. Let's use test_decode_color, which is our usual technique in other tests. It makes reading test output diffs a bit easier, since you're not relying on your terminal to interpret the result (or worse, interpreting characters yourself via "cat -A"). This will also make it easier to add tests with new colors/attributes, without having to pre-define the byte sequences ourselves. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- .../diff-highlight/t/t9400-diff-highlight.sh | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/contrib/diff-highlight/t/t9400-diff-highlight.sh b/contrib/diff-highlight/t/t9400-diff-highlight.sh index 7ebff8b18f..4f3d55a26e 100755 --- a/contrib/diff-highlight/t/t9400-diff-highlight.sh +++ b/contrib/diff-highlight/t/t9400-diff-highlight.sh @@ -7,9 +7,6 @@ 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 - GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . "$TEST_DIRECTORY"/test-lib.sh @@ -42,9 +39,9 @@ dh_test () { } >/dev/null && "$DIFF_HIGHLIGHT" diff.hi && - test_strip_patch_header diff.act && + test_strip_patch_header diff.act && "$DIFF_HIGHLIGHT" commit.hi && - test_strip_patch_header commit.act && + test_strip_patch_header commit.act && test_cmp patch.exp diff.act && test_cmp patch.exp commit.act } @@ -126,8 +123,8 @@ test_expect_success 'diff-highlight highlights the beginning of a line' ' dh_test a b <<-EOF @@ -1,3 +1,3 @@ aaa - -${CW}b${CR}bb - +${CW}0${CR}bb + -bbb + +0bb ccc EOF ' @@ -148,8 +145,8 @@ test_expect_success 'diff-highlight highlights the end of a line' ' dh_test a b <<-EOF @@ -1,3 +1,3 @@ aaa - -bb${CW}b${CR} - +bb${CW}0${CR} + -bbb + +bb0 ccc EOF ' @@ -170,8 +167,8 @@ test_expect_success 'diff-highlight highlights the middle of a line' ' dh_test a b <<-EOF @@ -1,3 +1,3 @@ aaa - -b${CW}b${CR}b - +b${CW}0${CR}b + -bbb + +b0b ccc EOF ' @@ -213,8 +210,8 @@ test_expect_failure 'diff-highlight highlights mismatched hunk size' ' dh_test a b <<-EOF @@ -1,3 +1,3 @@ aaa - -b${CW}b${CR}b - +b${CW}0${CR}b + -bbb + +b0b +ccc EOF ' @@ -232,8 +229,8 @@ test_expect_success 'diff-highlight treats multibyte utf-8 as a unit' ' 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 + -unic${o_accent}de + +unic${o_stroke}de EOF ' @@ -250,8 +247,8 @@ test_expect_failure 'diff-highlight treats combining code points as a unit' ' 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 + -unico${combine_accent}de + +unico${combine_circum}de EOF ' @@ -333,12 +330,12 @@ test_expect_success 'diff-highlight handles --graph with leading dash' ' +++ b/file @@ -1,3 +1,3 @@ before - -the ${CW}old${CR} line - +the ${CW}new${CR} line + -the old line + +the new line -leading dash EOF git log --graph -p -1 | "$DIFF_HIGHLIGHT" >actual.raw && - trim_graph actual && + trim_graph actual && test_cmp expect actual ' From c6bc53ad95d5a7dda3d3a8b1bc984465c9024342 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 23 Mar 2026 02:02:13 -0400 Subject: [PATCH 6/8] diff-highlight: test color config We added configurable colors long ago in bca45fbc1f (diff-highlight: allow configurable colors, 2014-11-20), but never actually tested it. Since we'll be touching the color code in a moment, this is a good time to beef up the tests. Note that we cover both the highlight/reset style used by the default colors, as well as the normal/highlight style added by that commit (which was previously totally untested). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- .../diff-highlight/t/t9400-diff-highlight.sh | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/contrib/diff-highlight/t/t9400-diff-highlight.sh b/contrib/diff-highlight/t/t9400-diff-highlight.sh index 4f3d55a26e..b38fe2196a 100755 --- a/contrib/diff-highlight/t/t9400-diff-highlight.sh +++ b/contrib/diff-highlight/t/t9400-diff-highlight.sh @@ -350,4 +350,32 @@ test_expect_success 'highlight diff that removes final newline' ' EOF ' +test_expect_success 'configure set/reset colors' ' + test_config color.diff-highlight.oldhighlight bold && + test_config color.diff-highlight.oldreset nobold && + test_config color.diff-highlight.newhighlight italic && + test_config color.diff-highlight.newreset noitalic && + echo "prefix a suffix" >a && + echo "prefix b suffix" >b && + dh_test a b <<-\EOF + @@ -1 +1 @@ + -prefix a suffix + +prefix b suffix + EOF +' + +test_expect_success 'configure normal/highlight colors' ' + test_config color.diff-highlight.oldnormal red && + test_config color.diff-highlight.oldhighlight magenta && + test_config color.diff-highlight.newnormal green && + test_config color.diff-highlight.newhighlight yellow && + echo "prefix a suffix" >a && + echo "prefix b suffix" >b && + dh_test a b <<-\EOF + @@ -1 +1 @@ + -prefix a suffix + +prefix b suffix + EOF +' + test_done From bd958e91dffdba00ef94dc9bfc04b46599362f9a Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Mon, 23 Mar 2026 02:02:15 -0400 Subject: [PATCH 7/8] diff-highlight: allow module callers to pass in color config Users of the module may want to pass in their own color config for a few obvious reasons: - they are pulling the config from different variables than diff-highlight itself uses - they are loading the config in a more efficient way (say, by parsing git-config --list) and don't want to incur the six (!) git-config calls that DiffHighlight.pm runs to check all config Let's allow users of the module to pass in the color config, and lazy-load it when needed if they haven't. Signed-off-by: Scott Baker Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- contrib/diff-highlight/DiffHighlight.pm | 41 +++++++++++++++++-------- contrib/diff-highlight/README | 6 ++++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm index a5e5de3b18..96369eadf9 100644 --- a/contrib/diff-highlight/DiffHighlight.pm +++ b/contrib/diff-highlight/DiffHighlight.pm @@ -9,18 +9,11 @@ use File::Spec; my $NULL = File::Spec->devnull(); -# Highlight by reversing foreground and background. You could do -# other things like bold or underline if you prefer. -my @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal'), - color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), - color_config('color.diff-highlight.oldreset', "\x1b[27m") -); -my @NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), - color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), - color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2]) -); +# The color theme is initially set to nothing here to allow outside callers +# to set the colors for their application. If nothing is sent in we use +# colors from git config in load_color_config(). +our @OLD_HIGHLIGHT = (); +our @NEW_HIGHLIGHT = (); my $RESET = "\x1b[m"; my $COLOR = qr/\x1b\[[0-9;]*m/; @@ -170,6 +163,29 @@ sub show_hunk { $line_cb->(@queue); } +sub load_color_config { + # If the colors were NOT set from outside this module we load them on-demand + # from the git config. Note that only one of elements 0 and 2 in each + # array is used (depending on whether you are doing set/unset on an + # attribute, or specifying normal vs highlighted coloring). So we use + # element 1 as our check for whether colors were passed in; it should + # always be set if you want highlighting to do anything. + if (!defined $OLD_HIGHLIGHT[1]) { + @OLD_HIGHLIGHT = ( + color_config('color.diff-highlight.oldnormal'), + color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), + color_config('color.diff-highlight.oldreset', "\x1b[27m") + ); + } + if (!defined $NEW_HIGHLIGHT[1]) { + @NEW_HIGHLIGHT = ( + color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), + color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), + color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2]) + ); + }; +} + sub highlight_pair { my @a = split_line(shift); my @b = split_line(shift); @@ -218,6 +234,7 @@ sub highlight_pair { } if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { + load_color_config(); return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); } diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README index 9c89146fb0..ed8d876a18 100644 --- a/contrib/diff-highlight/README +++ b/contrib/diff-highlight/README @@ -138,6 +138,12 @@ Your script may set up one or more of the following variables: processing a logical chunk of input). The default function flushes stdout. + - @DiffHighlight::OLD_HIGHLIGHT and @DiffHighlight::NEW_HIGHLIGHT - these + arrays specify the normal, highlighted, and reset colors (in that order) + for old/new lines. If unset, values will be retrieved by calling `git + config` (see "Color Config" above). Note that these should be the literal + color bytes (starting with an ANSI escape code), not color names. + 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) From 6689a6ea493b484d6a43601f42c1633706c963d6 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 23 Mar 2026 02:02:18 -0400 Subject: [PATCH 8/8] diff-highlight: fetch all config with one process When diff-highlight was written, there was no way to fetch multiple config keys _and_ have them interpreted as colors. So we were stuck with either invoking git-config once for each config key, or fetching them all and converting human-readable color names into ANSI codes ourselves. I chose the former, but it means that diff-highlight kicks off 6 git-config processes (even if you haven't configured anything, it has to check each one). But since Git 2.18.0, we can do: git config --type=color --get-regexp=^color\.diff-highlight\. to get all of them in one shot. Note that any callers which pass in colors directly to the module via @OLD_HIGHLIGHT and @NEW_HIGHLIGHT (like diff-so-fancy plans to do) are unaffected; those colors suppress any config lookup we'd do ourselves. You can see the effect like: # diff-highlight suppresses git-config's stderr, so dump # trace through descriptor 3 git show d1f33c753d | GIT_TRACE=3 diff-highlight 3>&2 >/dev/null which drops from 6 lines down to 1. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- contrib/diff-highlight/DiffHighlight.pm | 28 ++++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm index 96369eadf9..abe457882e 100644 --- a/contrib/diff-highlight/DiffHighlight.pm +++ b/contrib/diff-highlight/DiffHighlight.pm @@ -131,9 +131,21 @@ sub highlight_stdin { # of it being used in other settings. Let's handle our own # fallback, which means we will work even if git can't be run. sub color_config { + our $cached_config; my ($key, $default) = @_; - my $s = `git config --get-color $key 2>$NULL`; - return length($s) ? $s : $default; + + if (!defined $cached_config) { + $cached_config = {}; + my $data = `git config --type=color --get-regexp '^color\.diff-highlight\.' 2>$NULL`; + for my $line (split /\n/, $data) { + my ($key, $color) = split ' ', $line, 2; + $key =~ s/^color\.diff-highlight\.// or next; + $cached_config->{$key} = $color; + } + } + + my $s = $cached_config->{$key}; + return defined($s) ? $s : $default; } sub show_hunk { @@ -172,16 +184,16 @@ sub load_color_config { # always be set if you want highlighting to do anything. if (!defined $OLD_HIGHLIGHT[1]) { @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal'), - color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), - color_config('color.diff-highlight.oldreset', "\x1b[27m") + color_config('oldnormal'), + color_config('oldhighlight', "\x1b[7m"), + color_config('oldreset', "\x1b[27m") ); } if (!defined $NEW_HIGHLIGHT[1]) { @NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), - color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), - color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2]) + color_config('newnormal', $OLD_HIGHLIGHT[0]), + color_config('newhighlight', $OLD_HIGHLIGHT[1]), + color_config('newreset', $OLD_HIGHLIGHT[2]) ); }; }