I merged a feature branch into master with --no-commit
. When doing a git diff
I see the correct changes: some lines have been added in the feature branch. When using git difftool
I get a reversed result. The tool shows that the new lines were already present and are not there any more.
I checked git config and in fact $BASE refers to the changes and $LOCAL refers to the original version.
[difftool "p4merge"]
path = C:\\Program Files\\Perforce\\p4merge.exe
cmd = p4merge.exe $BASE $BASE
This shows actually all the new changes in the working dir on both sides.
[difftool "p4merge"]
path = C:\\Program Files\\Perforce\\p4merge.exe
cmd = p4merge.exe $LOCAL $LOCAL
This shows the original version on both sides.
[difftool "p4merge"]
path = C:\\Program Files\\Perforce\\p4merge.exe
cmd = p4merge.exe $BASE $LOCAL
This shows the result I was expectiong but it's the wrong order?
Eclipse shows the expected result not the one from the difftool. I also tried different tools: kdiff3 and p4merge.
According to the documentation:
When git difftool is invoked with this tool (either through the
-t
or--tool
option or thediff.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
.
To unpack that a bit:
git difftool
needs, first, the hash IDs of two commits (or something else suitable; see below). Let's call these the "left" and "right" sides of the diff. When you use:
git diff commit1 commi2
or
git difftool commit1 commit2
the idea is to obtain instructions that would tell you how to change what's in commit1
into what's in commit2
: how to change each file in the left side into each file in the right side.
The stuff inside a commit—the frozen files in the snapshot—is in a form that any program that isn't Git cannot use. So Git is going to extract the files from those commits into some temporary area. These files may have wild temporary names like /tmp/a9ZF3xq
or some such.
$LOCAL
has the wild / unpredictable temporary name of the left-side file.
$REMOTE
has the wild / unpredictable temporary name of the right-side file.
$MERGED
has the actual file name, such as src/sub/somefunc.py
or whatever. If this file exists on disk, its contents may not be those of either the left or the right side file! $MERGED
should be used only to show the name of the file, not to access any contents.
$BASE
really should not be used, but if do you have something that uses it, it's just going to be identical to $MERGED
.
So, reading the contents of a file named $BASE
or $MERGED
is incorrect and will result in the wrong instructions sometimes. Do not use either one—use $LOCAL
and $REMOTE
instead, with $LOCAL
holding the left-side contents and $REMOTE
holding the right-side contents.
git diff
/ git difftool
commands use the work-treeI said above that git difftool
—which is mostly the same as git diff
—needs the hash IDs of two commits, but that's not quite true. What it really needs is two trees to compare.
Every commit has a tree, in Git, because every commit contains a complete snapshot of all files as of their state at the time you (or whoever) made the commit. But there are two more trees you can use:
The index or staging area contains your proposed next commit. That is, it has a copy of every (to-be-committed) file. If you run git commit
right now, the new commit will snapshot the files that are in the index / staging-area.
Your work-tree is a tree of files that you can, using git add
, use to overwrite the files in your index / staging-area.
Both git diff
and git difftool
can use the index and/or the work-tree as left and/or right side. If you use the index as one of the two sides, its files are in the same Git-only format as the ones in commits, so these files must be copied to temporary files that could have wild temporary names. But if and when you use the work-tree as one of the two sides, well, its files are all ordinary everyday files. Git can, and will, just direct the merge tool to look right at those files.
That means that when you use the work-tree as one of the sides (and only in this case), $LOCAL
or $REMOTE
will have the name of a work-tree file in it, and that work-tree file has the contents that your tool is supposed to examine. In this case, $BASE
and $MERGED
will have the same name. This case is very common because, as with git diff
:
git difftool
with no arguments means "compare the index contents to the work-tree contents". With one argument:
git difftool HEAD
Git compares the given commit to the work-tree. That is, in both of these cases, the right-side "commit" is whatever is in the work-tree. So here, $LOCAL
is a temporary file name with what's in the HEAD
commit or the index, but $REMOTE
, $BASE
, and $MERGE
are all just the name of the work-tree file!
Hence, of course:
<command-to-view> $LOCAL
will show you what's in the left side, because that's what $LOCAL
is: a temporary file containing what's in the left side. Meanwhile:
<command-to-view> $BASE
will show you what's in the right side, because $BASE
matches $REMOTE
and $REMOTE
is the name of a file containing what's in the right side. Using:
<command-to-view/compare-both-of> $LOCAL $BASE
will work for this case, but not in general. You must use:
<command-to-view/compare-both-of> $LOCAL $REMOTE
and if that command has the ability to say "this is a comparison of the contents that were stored in a file named ____" (fill in the blank), you can fill in the blank using $BASE
.