I want to perform a three-way diff between two git branches with a common merge base, and view it with kdiff3.
I've found lots of guidance on SO (and a few very similar questions (1, 2, 3) ) but I haven't found a direct answer. Notably, a comment on this answer implies that what I want is possible, but it didn't work for me. Hopefully that user might chime in here :)
For background, when I perform merges I use a "diff3" conflict style:
git config --global merge.conflictstyle diff3
And I have git mergetool
configured to use kdiff3.
When resolving merge conflicts this shows me four files:
- The current branch's file ($LOCAL)
- The other branch's file ($REMOTE)
- The file which is the common ancestor of the two branches ($BASE)
- The merged output file ($MERGED)
However, git difftool
only will pull up the two branch tips. I want to see the base file, too. To be clear, I want to be able to perform this diff before merging, including on files without merge conflicts. (git mergetool
only shows the three-way diffs if there are conflicts).
Partial Solution #1:
With an individual file, I can export the three versions and manually call the diff:
git show local_branch:filename > localfile
git show remote_branch:filename > remotefile
git show `git merge-base local_branch remote_branch`:filename > basefile
{kdiff3_path}/kdiff3.exe --L1 "Base" --L2 "Local" --L3 "Remote" -o "outputfile" basefile localfile remotefile &
There are two problems with this:
- I want it to work for the whole project, not just a specific file.
- This is ugly! I can script it, but I hope there's a much cleaner way using standard git processes.
Partial Solution #2:
Thanks to this answer and comment for the inspiration.
Create a custom merge driver that always returns "false", which creates a conflicted merge state without actually doing any auto-merging. Then perform the diff using git mergetool
. Then abort the merge when you're finished.
Add to
.git/config
:[merge "assert_conflict_states"] name = assert_conflict_states driver = false
Create (or append to)
.git/info/attributes
to cause all merges to use the new driver:* merge=assert_conflict_states
Perform the merge, which now doesn't do any automerging.
Do the diff. In my case:
git mergetool
which brings up the kdiff3 three-way merge.When done, abort the merge:
git merge --abort
.Undo step #2.
This would (sorta) work except that kdiff3 performs an automerge when called, so I still can't see the pre-merged diffs. I can fix this, though, by changing Git's stock kdiff3 driver file (.../git-core/mergetools/kdiff3
by removing the --auto
switch.
Even so, this has the following show-stopping problems:
- This only works when both files have changed! In the case where only one file changed, the updated file replaces the older file and the merge is never called.
- I have to modify the Git kdiff3 driver, which isn't portable at all.
- I have to modify
attributes
before and after doing the diff. - And, of course, I was hoping to do this without merging :)
Information for the bounty:
According to answers given, this isn't possible with standard Git. So now I'm looking for a more out-of-the-box solution: How can I tweak Git to make this happen?
Here's one lead: Apparently, if only one of the three files has changed, this newer file is used in the merge result without actually calling the merge driver. This means that my custom "conflict-creating" merge driver is never called in this case. If it was, then my "Partial Solution #2" would actually function.
Could this behavior be changed by tweaking files or configurations? Or perhaps there's a way to create a custom diff driver? I'm not ready to start playing with the Git source code...
Any clever ideas?