On the Matter of Beautiful git Diffs

git is really one of my favorite tools these days. I love so much about it – the DAG, the way it makes my life easier by protecting changes, and even the CLI workflow (once you adjust to its initially bizarre behaviors, it’s great – how many tools do you use that are that fast these days?). I feel like it inspires a lot of creative thinking in engineers due to its well-designed core mechanic and rock-solid reliability.

But surely some of you out there are saying: “git is cool, but what about the diffs, Nate? It could look way better. Like if it showed the part of the line that changed in green instead of the whole line.”

This article is for you folks!

Download diff-highlight from the git contrib repository

diff-highlight is so great! It’s a Perl script written to solve the exact problem mentioned above.

To download, you could do something like:

Inspect the script using $EDITOR.

You’ll see that it’s a tidy little Perl script, using a fairly simple algorithm to evaluate more specific diffs if the hunks (consecutive diff sections in the code) are even in the positive and negative numbers for a given section of lines. e.g., the main loop:

while (<>) {
    if (!$in_hunk) {
        print;
        $in_hunk = /^$COLOR*\@/;
    }
    elsif (/^$COLOR*-/) {
        push @removed, $_;
    }
    elsif (/^$COLOR*\+/) {
        push @added, $_;
    }
    else {
        show_hunk(\@removed, \@added);
        @removed = ();
        @added = ();

        print;
        $in_hunk = /^$COLOR*[\@ ]/;
    }

    # Most of the time there is enough output to keep things streaming,
    # but for something like "git log -Sfoo", you can get one early
    # commit and then many seconds of nothing. We want to show
    # that one commit as soon as possible.
    #
    # Since we can receive arbitrary input, there's no optimal
    # place to flush. Flushing on a blank line is a heuristic that
    # happens to match git-log output.
    if (!length) {
        local $| = 1;
    }
}

Take a look at that first else block above. You can see that this loops over all the lines (while (<>) {) and is printing a kind of “streaming” result when the conditions align properly in that else block. Then the script resets the @removed and @added arrays.

Anyway, you can change the permissions on this script if it meets your approval, like so:

And pipe the output of git commands into it, e.g. git diff or git log -p:

Set your git config

If you love these diffs as much as I do and want them every time, you can set your ~/.gitconfig file pager setting to do so!

[core]
    pager = diff-highlight | less -RFX

(less options – -R for colors to persist, -F to exit immediately if the output is less than one screen, and -X which honestly I just cargo culted in).

Spoke the manual:

I wonder if it has something to do with diff-highlight’s streaming shenanigans.

Additionally, there are some settings that may help reduce diff noise if enabled. I have them turned on because if I can avoid a case where the curly brace from one function acidentally is used for the closing block of another unrelated function in a diff (you’ve had that happen, right?), then hell yeah I want to do so.

[diff]
    algorithm = minimal
    compactionHeuristic = true
    renames = true

(compactionHeuristic is new in git 2.9)

The cherry on top is that there are some settings for how to color the git diffs. I am cheesin' it up with the Matrix greens and reds but I bet they could be used to enable different color schemes if you get creative. Post your screenshots on Twitter and cc @dotpem!

Save these in your ~/.gitconfig file and you can have Matrix colors too.

[color "diff"]
        frag = magenta bold
        old = red bold
        new = green bold
        whitespace = red reverse

[color "diff-highlight"]
        oldNormal = red bold
        oldHighlight = "red bold 52"
        newNormal = "green bold"
        newHighlight = "green bold 22"

I did try a tool called diff-so-fancy but it was a little bit too much for my taste. I like my pluses and minuses. Besides which, it seemed a little slow for my extremely impatient, high-twitch git log -p / git diff workflow.

fin

Have fun with your diffs and as always, stay sassy Internet.

  • Nathan
I want to help you become an elite engineer. Subscribe to follow my work with containers, observability, and languages like Go, Rust, and Python on Gumroad.

If you find a mistake or issue in this article, please fix it and submit a pull request on Github (must be signed in to your GitHub account).

I offer a bounty of one coffee, beer, or tea for each pull request that gets merged in. :) Make sure to cc @nathanleclaire in the PR.