65

When diffing files, I prefer to use git diff --color-words. Is there a way to make this the default format for diffs when using git add --patch or git add --interactive?

mybuddymichael
  • 1,660
  • 16
  • 20
  • 1
    OK if I edit this question to cover `--word-diff` in addition to `--color-words`? If so, then my *near* duplicate question [here](https://stackoverflow.com/q/49058817) will be able to be closed as an *exact* duplicate of this one. That will allow answers to be consolidated here, which in turn will be more efficient for the community. –  Sep 12 '18 at 16:33
  • May be a duplicate of what you can find here: https://stackoverflow.com/questions/49278577/how-to-improve-gits-diff-highlighting/60970801#60970801 . Please have a look at the answers there. – Zorglub29 Apr 01 '20 at 12:39

9 Answers9

20

Building off of what VonC said:

Starting with Git 2.9, you can use this command to color words during add --patch:

git -c interactive.diffFilter="git diff --color-words" add -p

This sets the interactive.diffFilter variable for the call to add -p without affecting further calls. For me this is ideal because I usually want to run add -p normally, but sometimes want to run it with --color-words.

You can easily add an alias for this command like so:

git config --global alias.addcw '-c interactive.diffFilter="git diff --color-words" add -p'
adzenith
  • 757
  • 5
  • 8
  • 1
    Nice use of `add --patch` there, and nice alias! +1 – VonC Jun 14 '16 at 19:13
  • I'm getting: Use of uninitialized value $_ in print at /usr/libexec/git-core/git-add--interactive line 1368. (Git 2.10.0) – Pavel Šimerda Nov 11 '16 at 12:49
  • @PavelŠimerda Someone in another comment thread below has figured that one out: http://stackoverflow.com/questions/10873882/how-to-use-color-words-with-git-add-patch/37817331?noredirect=1#comment18417877_12297461 – adzenith Dec 04 '16 at 20:52
  • Using `git -c interactive.diffFilter="git diff --color-words" add -p folder/file` actually patch-adds the first modified file in my (git)root folder instead of `folder/file`. (I'm using Git v2.13.) – ebosi Jun 08 '17 at 15:16
  • 2
    Like already pointed out that answer is (sadly) **wrong**, because the used command `git diff --color-words` will not colourise stdin but get's executed usually. Just execute `echo test | git diff` or `git show | git diff` to get evidence. – doak Jan 12 '18 at 11:32
  • You can just run `git config interactive.diffFilter diff-highlight` once to make it permanent globally. – naught101 Sep 13 '18 at 01:30
  • 13
    Git 2.17.2 on macOS from Command Line Tools, I see `fatal: mismatched output from interactive.diffFilter hint: Your filter must maintain a one-to-one correspondence hint: between its input and output lines.` – Vitaly Zdanevich Nov 08 '18 at 21:59
  • 1
    @VitalyZdanevich: I see the same. – Tim Hopper Sep 18 '19 at 01:34
  • 1
    The same error `fatal: mismatched output` on Ubuntu with git 2.17.1. – Vitaly Zdanevich Sep 19 '19 at 10:33
20

Taking cue from VonC's answer. Here are detailed steps to use --interactive option introduced in git 2.9.

Add diff-highlight to your PATH.

On Ubuntu, diff-highlight comes with git and can be found in /usr/share/git/diff-highlight/diff-highlight.

Otherwise, you can download and set it up manually.

cd ~/bin
curl -LO "https://raw.githubusercontent.com/git/git/master/contrib/diff-highlight/diff-highlight"
chmod u+x diff-highlight

Restart your shell, if necessary.

Then configure Git to filter your diffs whenever it's showing them in a pager:

git config --global pager.log 'diff-highlight | less'
git config --global pager.show 'diff-highlight | less'
git config --global pager.diff 'diff-highlight | less'
git config --global interactive.diffFilter diff-highlight

This will put an extra emphasis on the changed part of a line, which is almost same as --word-diff.

The advantage is you get word diff every where, like git log --patch or git add -p.

Demonstration of diff-highlight in git log --patch

Andrew-Dufresne
  • 5,464
  • 7
  • 46
  • 68
  • This almost worked for me but the URL is no longer valid, so I had to build `diff-highlight` from source (download correct git version -- not sure how much it matters --; `cd /contrib/diff-highlight`; execute `make`; add new `diff-highlight` to your `PATH`) then start from step `chmod` in this answer . Worked this way for me with `git version 2.17.2 (Apple Git-113)`. – shoe Jul 06 '19 at 05:37
  • 2
    You should already have diff-highlight shipping in with your git installation. – Zorglub29 Apr 01 '20 at 12:36
  • On Ubuntu Xenial, it's installed as mode `0644`. Grrrr. – Tom Hale Aug 27 '20 at 14:41
  • For a brew installed git, the path was `/usr/local/Cellar/git/2.27.0//share/git-core/contrib/diff-highlight/diff-highlight` – Ashutosh Jindal Oct 20 '20 at 12:49
  • Also you can install it via "pip3 install --user diff-highlight" with adding "~/.local/bin" to $PATH – Alexey Shrub Feb 25 '21 at 08:02
  • @TomHale That doesn’t matter, because when `diff-highlight` is not executable, you can always just call `perl diff-highlight` instead. – caw Jul 30 '21 at 15:33
  • Is it just me or does the colorized git diff gives an optical illusion of red being at a higher level, green being at a lower level, and white at the normal level. – Sriram Murali Jul 26 '22 at 16:31
16

I recently solved this issue, but it requires modifying a Perl script in git. That's easy and requires no special skill, however.

This solution requires that your git configuration use colorization for screen output, because that's the only circumstance under which git will show a word-based diff.

  1. Copy git-add--interactive from your installation to somewhere in your PATH environment variable and rename it git-add--interactive-words.
  2. Edit a line about half way down to change*
@colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path);

to

@colored = run_cmd_pipe("git", @diff_cmd, qw(--color --color-words --), $path);
  1. You can now run git add-interactive--words to do the equivalent of git add --interactive with colorized word-based diff.
  2. However, combining git add --patch with that is awkward because you need to pass the new script the right parameters. Fortunately, you can create an alias to the magic words in your .gitconfig:
[alias]
iaddpw = add--interactive-words --patch=stage --

which means git iaddpw runs the equivalent of git add --interactive --patch with colorized word-based diff.


*- For Git 2.18, this command is:

my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
Miles
  • 780
  • 7
  • 19
mabraham
  • 2,806
  • 3
  • 28
  • 25
  • 3
    Clever. You should submit a patch to Git. Thanks. – mybuddymichael Sep 09 '12 at 03:03
  • 4
    After my thesis is submitted ;-) – mabraham Sep 11 '12 at 09:18
  • 2
    @mabraham but I get this warning: `Use of uninitialized value $_ in print at /usr/local/Cellar/git/1.8.0/libexec/git-core/git-add--interactive-words line 1339` but using `git add -p` doesn't give me that warning – BPm Nov 20 '12 at 00:42
  • 5
    @BPm @mabraham: You can squelch that message by surrounding line 1339 (which is `print;`) with an if statement to make sure `$_` is defined, i.e. replace line 1339 by `if ($_) { print; }` – Nevik Rehnel Feb 03 '13 at 16:15
  • 3
    The existing colorization code in lines 1270-1340 assumes that the "before" and "after" hunks have content, which is not necessarily the case any more. The fix from @Nevik is effective there and line 1282. – mabraham Jun 15 '13 at 14:10
  • @mybuddymichael I have submitted a patch to git, based on using color.word-diff-in-interactive-add boolean configuration option. – mabraham Jun 15 '13 at 14:12
  • 1
    @K3---rnc I submitted a patch about a year ago, and it got a little bit of discussion. A proper implementation would take a bit more work, and down at the C level, and I have not prioritised it. – mabraham Jul 24 '14 at 16:27
  • On my machine, even the unmodified `git add--interactive --patch=stage --` only works when called from the root directory of my git repository. Can you confirm that? Is there a way to make it work when run from subdirectories? In my case it only outputs one empty line per file. – Edgar Feb 09 '16 at 09:42
11

With git 2.9 (June 2016), you will have a new option: interactive.diffFilter.

See commit 0114384 (27 Feb 2016) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit 2052c52, 03 Apr 2016)

add --interactive: allow custom diff highlighting programs

The patch hunk selector of add--interactive knows how ask git for colorized diffs, and correlate them with the uncolored diffs we apply. But there's not any way for somebody who uses a diff-filter tool like contrib's diff-highlight to see their normal highlighting.

This patch lets users define an arbitrary shell command to pipe the colorized diff through. The exact output shouldn't matter (since we just show the result to humans) as long as it is line-compatible with the original diff (so that hunk-splitting can split the colorized version, too).

You could then pipe that diff to a diff --color-words.

As commented by Andrew Dufresne, the GitHub blog post refers to the contrib script contrib/diff-highlight:

You can use "--color-words" to highlight only the changed portions of lines. However, this can often be hard to read for code, as it loses the line structure, and you end up with oddly formatted bits.

Instead, this script post-processes the line-oriented diff, finds pairs of lines, and highlights the differing segments.

The result puts an extra emphasis on the changed part of a line:

diff colors

Regarding those diffs, "diff-highlight" filter (in contrib/) learned to undertand "git log --graph" output better.

See commit 4551fbb, commit 009a81e, commit fbcf99e, commit 7ce2f4c, commit e28ae50, commit 53ab9f0, commit 5013acc (21 Mar 2018) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit d19e556, 10 Apr 2018)

See more in "diff-highlight: detect --graph by indent"


Note: before Git 2.17 (Q2 2018), The "interactive.diffFilter" used by "git add -i" must retain one-to-one correspondence between its input and output, but it was not enforced and caused end-user confusion.

We now at least make sure the filtered result has the same number of lines as its input to detect a broken filter.

See commit 42f7d45, commit af3570e (03 Mar 2018) by Jeff King (peff).
(Merged by Junio C Hamano -- gitster -- in commit c5e2df0, 14 Mar 2018)


With Git 2.30 (Q1 2021), "git add -i"(man) failed to honor custom colors configured to show patches, which has been corrected.

See commit 96386fa, commit 890b68b, commit 0cb8939, commit afae3cb, commit 6681e36 (16 Nov 2020), commit 25d9e5c, commit c62cd17, commit 6f1a5ca, commit decc9ee (11 Nov 2020), and commit cb581b1, commit d34e450 (10 Nov 2020) by Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster -- in commit e0d2568, 08 Dec 2020)

add -p: prefer color.diff.context over color.diff.plain

Signed-off-by: Johannes Schindelin

Git's diff machinery allows users to override the colors to use in diffs, even the plain-colored context lines. As of 8dbf3eb6850 (diff.h: rename DIFF_PLAIN color slot to DIFF_CONTEXT, 2015-05-27, Git v2.4.5), the preferred name of the config setting is color.diff.context, although Git still allows color.diff.plain.

In the context of git add -p(man), this logic is a bit hard to replicate: git_diff_basic_config() reads all config values sequentially and if it sees any color.diff.context or color.diff.plain, it accepts the new color.
The Perl version of git add -p(man) needs to go through git config --get-color(man), though, which allows only one key to be specified.
The same goes for the built-in version of git add -p(man), which has to go through repo_config_get_value().

The best we can do here is to look for .context and if none is found, fall back to looking for .plain, and if still not found, fall back to the hard-coded default (which in this case is simply the empty string, as context lines are typically rendered without colored).

This still leads to inconsistencies when both config names are used: the initial diff will be colored by the diff machinery.
Once edited by a user, a hunk has to be re-colored by git add -p(man), though, which would then use the other setting to color the context lines.

In practice, this is not all that bad. The git config(man) manual says this in the color.diff.<slot>:

`context` (context text - `plain` is a historical synonym)  

We should therefore assume that users use either one or the other, but not both names.
Besides, it is relatively uncommon to look at a hunk after editing it because it is immediately staged by default.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
9

Solution

Use diff-highlight | less -FRX --tabs=4 as your diffFilter:

git -c interactive.diffFilter="diff-highlight | less -FRX --tabs=4" add --patch

For more on diff-highlight: source, a quick primer

Homebrew

If you're using Homebrew (OS X), you can put the following in your .gitconfig (to use the already installed diff-highlight):

    [interactive]
        diffFilter = "$(git --exec-path | sed 's/libexec/share/')/contrib/diff-highlight/diff-highlight | less -FRX --tabs=4"

1-1 correspondence between input and output

As of git 2.17, the word diff solution must keep a 1-1 correspondence between input and output lines to avoid:

$ git -c interactive.diffFilter="git diff --word-diff --color" add --patch
fatal: mismatched output from interactive.diffFilter
hint: Your filter must maintain a one-to-one correspondence
hint: between its input and output lines.

diff-so-fancy now supports this (as of v1.4.0) since https://github.com/so-fancy/diff-so-fancy/issues/35 has been closed. See this.

Ashutosh Jindal
  • 18,501
  • 4
  • 62
  • 91
  • 1
    Nice use of `interactive.diffFilter`, that I described [in my own answer](https://stackoverflow.com/a/36437292/6309). +1 – VonC Sep 08 '18 at 03:43
  • macOS Mojave - `No such file or directory` with the default preinstalled git. – Vitaly Zdanevich Oct 04 '18 at 11:26
  • I've only tested on OS X with Homebrew's install, indeed. Here are some instructions from `git` themselves on how to do so: https://git-scm.com/book/en/v1/Getting-Started-Installing-Git#Installing-on-Mac – Olivier Le Floch Oct 06 '18 at 00:27
5

As mentioned earlier adding diff-highlight to the interactive.diffFilter config key is the easiest option (since Git 2.9). The following comand does the trick on Debian/Ubuntu without copying scripts, changing permissions or mangling $PATH:

git config interactive.diffFilter "perl /usr/share/doc/git/contrib/diff-highlight/diff-highlight"

Things like git -c interactive.diffFilter="git diff --color-words" add -p or git config interactive.diffFilter "git diff --color-words" don't work properly: add -p always keeps suggesting the first modified file.

sgtpep
  • 819
  • 1
  • 7
  • 3
3

This tool does it well https://github.com/mookid/diffr

[core]
    pager = diffr | less -R
[interactive]
    diffFilter = diffr
Paddy Roddy
  • 135
  • 2
  • 10
2

This is also possible via delta:

[interactive]
    diffFilter = delta --color-only --features=interactive

and via diff-so-fancy:

[interactive]
    diffFilter = diff-so-fancy --patch
joelostblom
  • 43,590
  • 17
  • 150
  • 159
-5

In your $(HOME)/.gitconfig file add this

[color]
        diff = auto
        interactive = auto

This should do.

positron
  • 1,652
  • 1
  • 11
  • 14
  • 2
    Thanks, but that's not what I'm talking about. It's not the *color*, but rather the word-by-word diff that I'm looking for. – mybuddymichael Jun 07 '12 at 05:05
  • 2
    No. I'm talking about a character by character diff, rather than a line by line diff. Try `git diff --color-words` and you'll see what I mean. – mybuddymichael Jun 08 '12 at 04:08