How to improve git's diff highlighting?
Asked Answered
P

7

46

The output of git diff is optimized for code which tends to be one statement per line whereas text can (if authors like me are too lazy to use line breaks) cause diff output which is very hard to read and more of a "Where's Wally?" search than reading diff output

enter image description here

whereas highlighting as done on GitLab's or GitHub's web frontend shows the difference immediately

enter image description here

I'm aware that I'm comparing HTML and plain text (apples and oranges), however it should be possible to improve the git diff output by using different colors or adding marker characters around a change (JUnit uses [] around insertions which isn't great to read, but an example for what I mean) and it would be the first time that there's something I expect to be somewhere available in git that actually was not.

Presidentelect answered 14/3, 2018 at 13:6 Comment(1)
K
18

You could use the --word-diff[=<mode>] option to make it easier to see which words have changed within a line. This is described in the man page as

Show a word diff, using the <mode> to delimit changed words. By default, words are delimited by whitespace; see --word-diff-regex below. The <mode> defaults to plain, and must be one of:

  • color – Highlight changed words using only colors. Implies --color.

  • plain – Show words as [-removed-] and {+added+}. Makes no attempts to escape the delimiters if they appear in the input, so the output may be ambiguous.

  • porcelain – Use a special line-based format intended for script consumption. Added/removed/unchanged runs are printed in the usual unified diff format, starting with a +/-/` ` character at the beginning of the line and extending to the end of the line. Newlines in the input are represented by a tilde ~ on a line of its own.

  • none – Disable word diff again.

Note that despite the name of the first mode, color is used to highlight the changed parts in all modes if enabled.

Kalisz answered 14/3, 2018 at 15:15 Comment(1)
I tried adding a [pager] section in my ~/.gitconfig file containing the line ` diff = git diff --word-diff=plain --color -- "$@" | less` but typing git diff myFile prints the differences for ALL the files I've changed inside my repo. Do you have some ~/.gitconfig idea to automate a git diff --word-diff=plain --color -- "$@" when I type git diff anyFile ?Minerva
S
50

The word-diff suggested in the other answer isn't exactly what gitlab/github do. To get same effect, you can use diff-highlight script that is distributed with git.

  1. First find path to diff-highlight script. It varies between systems, and is not usually in $PATH. You can find it with your package manager, for example:

    1. Fedora: rpm -ql git | grep diff-highlight
    2. Debian/Ubuntu/Mint: dpkg -L git | grep diff-highlight
    3. Archlinux: pacman -Ql git | grep diff-highlight
  2. Now, execute the following two commands, which will add to your ~/.gitconfig the necessary settings:

    $ git config --global core.pager 'perl /usr/share/git/diff-highlight/diff-highlight | less'
    $ git config --global interactive.difffilter 'perl /usr/share/git/diff-highlight/diff-highlight'
    

    I'm using perl here instead of calling the script directly because some distros, it seems, do not set executable bit on the script. IMO this is a package bug which should be reported. Anyway, this answer should work regardless.

Now log, diff, show commands should show difference word-by-word. Screenshot:

git log -1 -p

Scarbrough answered 28/4, 2019 at 14:28 Comment(10)
My answer is very close to yours and it was pointed rightfully that I should probably remove it. Would you be ok indicating how to find the path (find -L /usr -name diff-highlight -type f) and make the script executable (sudo chmod +x /usr/share/doc/git/contrib/diff-highlight/diff-highlight) and I will then remove my answer?Paola
@Paola find /usr … may not work if packager of your distro decided to put this script somewhere under /lib or whatever other dir. Besides, what if you're configuring a new system, and you didn't install git yet? So, the correct way of finding the script is by listing the files of your git installation, which is specific to a system you use. I mentioned that in the answer. About not having an executable bit set: by virtue of being a script, it should have it set by default. If you stumbled upon a distro where it isn't, IMO you should report this as a bug to your distro.Scarbrough
@Paola to give you an example of a distro where the path is different: on NixOS it is /nix/store/g8z167wz75247v7jxmnib1f1w4ky3h1m-git-2.23.0/share/git/contrib/diff-highlight/diff-highlight. So if you really want to stick to find command, you'd have to use find / instead. Which may take really long time.Scarbrough
Ok. But I guess most users that are a bit n00bs, like me, ie the ones that also need help with finding the paths, use some of the mainstream distros (Ubuntu / Debian / Mint / etc), and then the find is useful I guess. Whatsoever, I guess I do not care, I was not able to get it work with your post, I had to spend time to find my solution, I wanted to help others, but for all matters my problem is solved so I can as well remove my post, people can just figure it out themselves as I did, not my problem...Paola
@Paola okay, so, I incorporated your suggestions. I was considering find command with a few hacks to get the path which would be the /usr for us, and would work on a wider range of distros. But after I run find /usr, and it didn't return for 10 seconds till I stopped it, I figured using find is a bad idea. It is slow and it pollutes files cache for no reason. I added a few examples of package manager usage instead. Regarding the executable bit not being set, note that after git package update your changes gonna be lost. To work around that I'm calling perl directly in the answer.Scarbrough
The recent edits have really improved this answer. For what it's worth, I had used the locate utility to get the path for the diff-highlight script (it was already executable on recent Fedora but not on Ubuntu 16.04).Kalisz
@AnthonyGeoghegan yeah, I've considered the alternative of using locate in the answer. But for this to work it first has to be installed, and second it has to have up to date database (which implies that if you just installed git package, you gotta run updatedb command next, otherwise locate won't find anything). I deemed that to be too much of a nuisance. With package manager it just works™.Scarbrough
On Debian testing (bullseye) it's in doc (?!) and there's no diff-highlight/diff-highlight; closest i could see was diff-highlight/diff-highlight.perl and that returns this error: Undefined subroutine &DiffHighlight::highlight_stdin called at /usr/share/doc/git/contrib/diff-highlight/diff-highlight.perl line 7.Legionnaire
Update to above comment regarding Debian testing: cd /usr/share/doc/git/contrib/diff-highlight then: sudo make And i have my executable diff-highlight.Legionnaire
Here's how to reference diff-highlight on MacOS: coderwall.com/p/nl-bdg/more-readable-git-word-diff-on-osxMaidamaidan
S
37

Also worth mentioning is diffr. It's written in Rust and uses Myers longest common subsequence algorithm. Compared to git's diff-highlight it gives better results, see:

git's diff-highlight:

diff-highlight

diffr:

diffr

Once installed, making use of it is similar to that of diff-highlight, so for example execute these two commands to add diffr to your global config:

$ git config --global core.pager 'diffr | less'
$ git config --global interactive.difffilter diffr
Scarbrough answered 29/9, 2019 at 19:19 Comment(7)
Can confirm that diffr provides a much better within-line change indication than diff-highlight. That screenshot didn't sell me but the first thing i was trying to do, the entire 500 character block of text was highlighted by diff-highlight while the only actual changes— two extra " \n" —were highlighted by diffr. Thanks Hi-Angel!Legionnaire
@Legionnaire you are welcome! Check out also options diffr supports, you might be interested in making use of some of them. E.g. I personally use --line-numbers aligned option.Scarbrough
See GitHub page (diffr) for installation. For Ubuntu, I installed with $ cargo install diffr, and modified .gitconfig as stated in Integration with git.Chemurgy
Adding the [pager] section to the ~/.gitconfig file did not work with my Ubuntu 20.04 system. Then I installed diffr and followed instructions in the diffr GitHub repo. Now my git command works flawlessly with diffr. Thank you @ChemurgyLuciferous
@TomNguyen by any chance, perhaps do you have another [pager] section elsewhere in the .gitconfig? I am not sure what else could go wrong. I just tested my answer by renaming the older ~/.gitconfig, and creating a new one with the sole content from the post, and it works for me. Although I'm testing on Archlinux, but that shouldn't really matter, the behavior related to git configs should be the same between distros.Scarbrough
@Scarbrough I use git version 2.25.1 and my ~/.gitconfig file does not have any [pager] section . I dont know why adding [pager] didn't work. But the git config --global core.pager 'diffr | less -R' and git config --global interactive.difffilter diffr commands work for me. I found those commands in the Integration with git section.Luciferous
The corresponding ~/.gitconfig section would be [core] pager = 'diffr | less'Unilocular
K
18

You could use the --word-diff[=<mode>] option to make it easier to see which words have changed within a line. This is described in the man page as

Show a word diff, using the <mode> to delimit changed words. By default, words are delimited by whitespace; see --word-diff-regex below. The <mode> defaults to plain, and must be one of:

  • color – Highlight changed words using only colors. Implies --color.

  • plain – Show words as [-removed-] and {+added+}. Makes no attempts to escape the delimiters if they appear in the input, so the output may be ambiguous.

  • porcelain – Use a special line-based format intended for script consumption. Added/removed/unchanged runs are printed in the usual unified diff format, starting with a +/-/` ` character at the beginning of the line and extending to the end of the line. Newlines in the input are represented by a tilde ~ on a line of its own.

  • none – Disable word diff again.

Note that despite the name of the first mode, color is used to highlight the changed parts in all modes if enabled.

Kalisz answered 14/3, 2018 at 15:15 Comment(1)
I tried adding a [pager] section in my ~/.gitconfig file containing the line ` diff = git diff --word-diff=plain --color -- "$@" | less` but typing git diff myFile prints the differences for ALL the files I've changed inside my repo. Do you have some ~/.gitconfig idea to automate a git diff --word-diff=plain --color -- "$@" when I type git diff anyFile ?Minerva
D
8

Delta is a modern alternative to the postprocessing tools in other answers.

It is highly configurable (with emulation modes for diff-highlight and diff-so-fancy) and includes many features not found in other tools: side-by-side views, syntax highlighting, and coloring of merge conflicts and git blame output.

The Delta documentation also has an overview of related projects that mentions a few more ad-hoc tools that can produce similar output.

Delta diff formatting example

Dissimulate answered 2/6, 2022 at 12:1 Comment(0)
E
3

To expand on @Hi-Angel answer, we could use a wrapper script to find the diff-highlight contrib script and place it in $PATH. Then use the wrapper script in your .gitconfig.

The script will try to find contrib/diff-highlight. We will name the script wrapper diff-highlight and place it in $PATH:

#!/bin/sh
# diff-highlight locates the diff-highlight script in the git source tree and
# runs it.
# https://github.com/git/git/blob/master/contrib/diff-highlight

hilite=

for prefix in /usr/share /usr/local/share; do
    # First try git-core directory
    if [ -f "$prefix/git-core/contrib/diff-highlight" ]; then
        hilite="/usr/share/git-core/contrib/diff-highlight"
    elif [ -d "$prefix/git-core/contrib/diff-highlight" ]; then
        hilite="$prefix/git-core/contrib/diff-highlight/diff-highlight"
        # Then try git contrib directory
    elif [ -f "$prefix/git/contrib/diff-highlight" ]; then
        hilite="$prefix/git/contrib/diff-highlight"
    elif [ -d "$prefix/git/contrib/diff-highlight" ]; then
        hilite="$prefix/git/contrib/diff-highlight/diff-highlight"
        # Try git directory
    elif [ -f "$prefix/git/diff-highlight" ]; then
        hilite="$prefix/git/diff-highlight"
    elif [ -d "$prefix/git/diff-highlight" ]; then
        hilite="$prefix/git/diff-highlight/diff-highlight"
        # Then try doc directory
    elif [ -f "$prefix/doc/git/contrib/diff-highlight" ]; then
        hilite="$prefix/doc/git/contrib/diff-highlight"
    elif [ -d "$prefix/doc/git/contrib/diff-highlight" ]; then
        hilite="$prefix/doc/git/contrib/diff-highlight/diff-highlight"
    fi
    if [ -n "$hilite" ]; then
        break
    fi
done

if [ -x "$hilite" ]; then
    exec "$hilite" "$@"
elif command -v perl >/dev/null 2>&1; then
    perl "$hilite" "$@"
else
    cat
fi

You can also find this script in my dotfiles.

Then change the git pager to use the wrapper script:

$ git config --global core.pager 'diff-highlight | less'
$ git config --global interactive.diffFilter 'diff-highlight'
Eisele answered 12/6, 2023 at 17:6 Comment(0)
W
2

Another option (in Rust): Wilfred/difftastic

Difftastic is a structural diff tool that compares files based on their syntax.

https://static.mcmap.net/file/mcmap/ZG-AbGLDKwfnZ7-ocV9QWRft/Wilfred/difftastic/raw/master/img/js.png

See Git usage:

One-Off Usage

You can use GIT_EXTERNAL_DIFF for a one-off git command.

View uncommitted changes with difftastic:

$ GIT_EXTERNAL_DIFF=difft git diff

View changes from the most recent commit with difftastic:

$ GIT_EXTERNAL_DIFF=difft git show HEAD --ext-diff

View changes from recent commits on the current branch with difftastic:

$ GIT_EXTERNAL_DIFF=difft git log -p --ext-diff

Regular Usage

If you like difftastic, we recommend that you configure git aliases so you can use difftastic more easily.

Add the following to your ~/.gitconfig to use difftastic as a difftool.

[diff]
        tool = difftastic

[difftool]
        prompt = false

[difftool "difftastic"]
        cmd = difft "$LOCAL" "$REMOTE"

[pager]
        difftool = true

You can now use the following command to see changes with difftastic, equivalent to git diff:

$ git difftool

We recommend that you set up a shorter alias for this command in your ~/.gitconfig:

# `git dft` is less to type than `git difftool`.
[alias]
        dft = difftool

For other commands, we also recommend that you set up aliases that are equivalent to the one-off commands shown above.

# `git dlog` to show `git log -p` with difftastic.
[alias]
        dlog = "!f() { GIT_EXTERNAL_DIFF=difft git log -p --ext-diff $@; }; f"

Difftastic By Default

If you want to use difftastic as your default diff tool, add the following to your ~/.gitconfig.

[diff]
    external = difft

This only applies to git diff. For other git commands, you still need to specify --ext-diff, or use an alias as described above.

$ git diff
$ git show HEAD --ext-diff
$ git log -p --ext-diff
Weatherboarding answered 13/3 at 23:10 Comment(0)
V
1

Sometimes just changing the coloring engine of git helps so much. Try diff-so-fancy.

Vineland answered 14/2, 2023 at 12:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.