git diff, with external diff program, just once

2.8k views Asked by At

When doing a git diff, sometimes I want to use a different diff tool (like, for example, cmp, or a homebrew word-diff program I have in my own bin directory).

Many diff-like programs have an option along the lines of

-D <diffprog>

to tell them to use that other diff or diff-like program instead.

I am aware of git-difftool and this question, but that's not quite what I was looking for. I'd very much rather not have to configure something in advance, and the nonstandard command I want to use today might not be the nonstandard command I want to use tomorrow. Ideally I'd like to be able to just do it, on a one-time basis, from the command line.

(Is it possible to configure difftool to be able to use multiple nonstandard tools not on the official list?)

If it's too much trouble to get git diff to use my choice of diff tool, I could also use something like

git cat file | mydiff - file

I know there's no such command as "git cat". But the idea here is to manually compare the repo version of the file with the local one, given a git command to simply emit the repo (or, in other circumstances, the index) version of the file. Is there any such command?

Finally, if there isn't the hypothetical git diff -D diffprog option I was asking about, and if there isn't anything like the "git cat" command I was hypothesizing, am I wrong to be wishing for them?

4

There are 4 answers

7
LeGEC On

The integrated way in git to open a diff in an external viewer is git difftool.

As far as cli options go, git difftool takes the same arguments as git diff, and opens the resulting diff in the external viewer.
One extra useful option is -d | --dir-diff, which will create two checkouts of your repo in a temporary directory and open the external viewer in "directory comparison mode" :

git difftool -d <something> <something else>

As far as the choice for external tool goes :

  • you can configure a default difftool : git config diff.tool kdiff3
  • you can select any tool you want on a one of basis using -t | --tool :
git difftool -t meld <something> <something else>
  • you can also define custom commands by creating difftool.<tool>.cmd entries in your git configuration

quoting the docs of the -t|--tool section :

You can explicitly provide a full path to the tool by setting the configuration variable difftool.<tool>.path. For example, you can configure the absolute path to kdiff3 by setting difftool.kdiff3.path. Otherwise, git difftool assumes the tool is available in PATH.

Instead of running one of the known diff tools, git difftool can be customized to run an alternative program by specifying the command line to invoke in a configuration variable difftool.<tool>.cmd.

When git difftool is invoked with this tool (either through the -t or --tool option or the diff.tool configuration variable) the configured command line will be invoked with the following variables available: $LOCAL is set to the name of the temporary file containing the contents of the diff pre-image and $REMOTE is set to the name of the temporary file containing the contents of the diff post-image. $MERGED is the name of the file which is being compared. $BASE is provided for compatibility with custom merge tool commands and has the same value as $MERGED.

2
jthill On

I think you might be looking for something like

git show somecommit:path/to/file | mydiff - path/to/file

or

mydiff  <(git show somecommit:path/to/file) \
        <(git show someothercommit:path/to/file)

but a little manpage reading says difftool does accept path limiters, and also has the -x option, so I don't see any problem there. For the case in your comment I'd use

git difftool -yx mydiff @ -- file
2
torek On

Besides the raw git show or git cat-file -p trick mentioned in jthill's answer, you can use git difftool as described in LeGEC's answer. To avoid stumbling over configurations or requiring special one-time configuration files, consider adding -c options to the front end git command:

git -c diff.tool=whatever ...

This pattern works in general: if you want to pretend that the config file(s) add up to having some particular x set to y, git config -c x=y does the trick. Multiple -c options are gathered together and used as-if-configured.

(I believe this adds on to configurations and relies on the "last config overrides earlier ones" stuff, so for multivalued settings like remote.remote.fetch, if you need to avoid picking up user configurations, you currently have to use tricks like modifying $HOME, which has ... drawbacks. The upcoming Git release has ways to skip both system and user configurations, for special purposes including Git's own self-tests.)

0
Samveen On

Caveat Emptor: This solution is specific to people using git-1.8.3.1 (the version of git available from base/updates yum repositories on RHEL7/Centos 7/OtherClone 7, where git doesn't support --ignore-white-space (which was introduced in git-1.8.4).

For me, the following works to create the output as if git supports --ignore-blank-lines using GNU diff:

git diff --name-only |xargs -rn1 bash -c 'git diff $0 |head -n4; git show HEAD:$0 | diff -U3 -b - $0|tail -n+3'

Explaination:

  • git diff --name-only gives me the list of changed lines
  • | xargs -rn1 ... takes the list of files and runs ... for each input, passing the input as the last argument to ...
  • bash -c 'git diff $0 |head -n4; git show HEAD:$0 | diff -U3 -b - $0|tail -n+3' takes the argument '$0' (which contains a path) and runs 2 subcommands on it's first parameter $0:
    • runs git diff $0 on the path ($0), to get the header, discarding the actual diff
    • get the checked in version of the file with git show HEAD:$0 piped through | diff -U3 -b -B - $0 to generate the diff and then piped through | tail -n+3 to discard the header generated by GNU diff

The combination of git diff header along with the body generated by GNU diff gives me the output as I desired.

Hopefully with the explanation, this solution can be dis-assembled and used as required in many use cases