3

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?

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • are you looking for `git difftool` ? – LeGEC Jun 16 '21 at 21:31
  • Ok, I had skipped the line "I don't want git-difftool", but reading the remainder of your question, I do not understand why ? – LeGEC Jun 16 '21 at 21:33
  • @LeGEC I'll update the question. For my part, I didn't properly understand what difftool does. But it looks like it doesn't want to let you use any external difflike program, it seems to be restricted to an approved list? – Steve Summit Jun 16 '21 at 21:36
  • ok, that's not the case. The known list opens each tool with the appropriate options, but you can also add your own command line invocation in the configuration. – LeGEC Jun 16 '21 at 21:36
  • @LeGEC Can I add several at once? (I'm also curious why I should have to configure anything in advance at all, but I guess that can be a question for another day.) – Steve Summit Jun 16 '21 at 21:42
  • The `git cat` you're looking for is spelled `git show`, or `git cat-file -p`. – jthill Jun 16 '21 at 21:52
  • @SteveSummit : you can check the individual files in https://github.com/git/git/tree/master/mergetools to view how each tool is called with specific options. Is the diff program you intend to use a known program ? or a custom script ? – LeGEC Jun 16 '21 at 21:56
  • @jthill Thanks for that tip! Neither of those worked for me at all the first time I tried, but if after reading about them I still can't get them working, I'll ask a separate question. – Steve Summit Jun 16 '21 at 22:23

4 Answers4

5

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
jthill
  • 55,082
  • 5
  • 77
  • 137
  • Yes, or specifically, `git show HEAD:./file`. This appears to be about the best I can do. – Steve Summit Jun 22 '21 at 21:07
  • That `-yx` was the key for me to feed a file list through [jd](https://github.com/josephburnett/jd) thanks! -- `for C in $(git diff --name-only --relative master -- "${DIRECTORY}"); do echo "${C}"; git difftool -yx jd @ master -- "${C}"; echo; done` – Mat Schaffer Dec 01 '21 at 06:38
3

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.

LeGEC
  • 46,477
  • 5
  • 57
  • 104
  • I may have been misled by the error messages. I tried `git difftool -t ndiff`, and it said "Unknown merge tool ndiff", followed by "fatal: external diff died". So I thought it had refused to run my external program. But perhaps it was just that the external tool exited with nonzero status, indicating a difference. – Steve Summit Jun 16 '21 at 22:06
  • ok, you may need to define `git config diff.ndiff.path "path/to/ndiff"`, or `git config diff.ndiff.cmd 'ndiff "$LOCAL" "$REMOTE"'` – LeGEC Jun 16 '21 at 22:08
  • ndiff is available from your PATH, right ? – LeGEC Jun 16 '21 at 22:14
  • *ndiff is available from your PATH, right?* Of course! – Steve Summit Jun 16 '21 at 22:15
  • Thanks for your help. The question you've answered is not the one I asked (I wanted to use an ad-hoc diff tool on the command line without having to configure it in advance, and what you've helped me to do is to configure a different diff tool in advance), but since it looks like the command-line option I was looking for doesn't exist, this has been very helpful, and I appreciate it. – Steve Summit Jun 16 '21 at 22:27
  • @SteveSummit: if your need is narrower, jthill's answer is a very straightforward way to get "the content of *that* file in *that* commit" and insert it in a shell command – LeGEC Jun 17 '21 at 08:33
  • In the end, what I actually needed to configure was `difftool.ndiff.cmd`. And having to go to this much work to explicitly configure something completely defeats the purpose of a one-off from the command line, but I'm not blaming you for this, I believe it's a deficiency in git. Thanks again for your help. – Steve Summit Jun 22 '21 at 21:21
3

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.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • This worked, modulo changing it to `-c difftool.ndiff.cmd=whatever`. It's still too cumbersome for an actual one-off, but it's good to know. Thank you. (And if your first name is Chris, hi there, haven't talked to you in a while! :-) ) – Steve Summit Jun 22 '21 at 21:19
  • That's me... I think the last time I saw you, I lived in Calif, before I moved to Utah, then back to CA, now in WA state. That was quite a while ago... – torek Jun 22 '21 at 22:31
0

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

Samveen
  • 3,482
  • 35
  • 52