stochastic
stochastic

Reputation: 3413

git merge conflicts: which commit was the common ancestor?

I want to know the identity of the "common ancestor" commit during a git merge conflict resolution.

Said differently: I want to know the hash of the revision that the BASE version is being drawn from when I'm resolving conflicts during a git merge.

Hopefully, there is a command that will tell me this information?

Why I want to know

Helpful (?) related info

$ git ls-files -u | grep "<path>"
100644 <SHA of BASE file> 1 <path>
100644 <SHA of LOCAL file> 2 <path>
100644 <SHA of REMOTE file> 3 <path>
<<<<<<<<< Temporary merge branch 1
<snip>
||||||||| merged common ancestors
=========
<snip>
>>>>>>>>> Temporary merge branch 2

When there is more than one common ancestor that can be used for 3-way merge, it creates a merged tree of the common ancestors and uses that as the reference tree for the 3-way merge.

Upvotes: 6

Views: 1003

Answers (2)

torek
torek

Reputation: 487993

This one is tricky, and a bit nasty. You're completely correct here:

I guess what I am seeing is that the "common ancestor" is a merged hybrid of several commits. Nevertheless, that merged hybrid must have been generated somehow, must have a SHA, and must have parents whose identities I want to know.

As LeGEC said, you do have both HEAD and MERGE_HEAD available when Git stops with this merge conflict.

You can find the hash IDs of the merge bases (plural) with:

git merge-base --all HEAD MERGE_HEAD

Since you are using merge-recursive, what Git did was:

  • Select two of the merge bases.
  • Run git merge-recursive on them. (This may itself find more than two merge bases; if so, see this procedure.)
  • Commit the result. This is now the merge-base-so-far. (This commit has a hash ID.)
  • Pick the next of the merge bases, if there are more than two bases, and merge that with the merge-base-so-far; this is now the new merge-base-so-far.
  • Repeat until all merge bases are used up.

The final output of this process is a commit hash ID. This hash ID is not saved or shown anywhere. You can get all the inputs to this process from git merge-base --all of course.

Normally, when a merge has conflicts, Git stops and makes you fix them. But when merging merge bases produces conflicts, Git just goes ahead and commits the conflicted merge bases. This is ... not good. (I'm not claiming it's bad here, just that it's not good: it gets very messy. The new merge-ort does not do this, I think, but I have yet to digest precisely what it does do.) These conflict markers are indeed what you are seeing.

The tools Git has here are not quite up to the job, but using git merge-base --all, you can at least inspect each of the inputs.

Upvotes: 4

LeGEC
LeGEC

Reputation: 51810

If you are in a conflict triggered by a git merge (e.g : not a cherry-pick or a rebase) :

  • your current commit is still HEAD
  • the merged commit is stored in a special ref .git/MERGE_HEAD

So you can get :

  • your current commit ("mine") : git rev-parse HEAD
  • the other commit ("theirs") : git rev-parse MERGE_HEAD
  • the base commit : git merge-base MERGE_HEAD HEAD

You can actually use HEAD and MERGE_HEAD as valid names to point at these commits, so :

  • git diff HEAD...MERGE_HEAD or git difftool -d HEAD...MERGE_HEAD will show you the changes introduced by "them" since the fork point,
  • git diff MERGE_HEAD...HEAD or git difftool -d MERGE_HEAD...HEAD will show you the changes introduced in "me" since the fork point.

a note about the three dots notation git diff A...B, quoting the docs :

git diff A...B is equivalent to git diff $(git merge-base A B) B


To solve conflicts for individual files :

  • kdiff3 in 3 way merge defaults to showing 4 panes, which allows you to view on the same screen :
    • the diff between LOCAL and BASE
    • the diff between BASE and REMOTE
    • the result of what you are about to save on the bottom pane

I'm not familiar with vimdiff, but there surely is a way to either have this 4-panes view, or commands to toggle between the 3 way merge and either of the two diffs.

  • I use meld as a graphical diff viewer, and I know this one has :
    • a --auto-merge flag, which, in 3 way merge view, automatically combines non conflicting diff chunks,
    • a way to open several diff views from the command line, so I open LOCAL BASE and BASE REMOTE diffs in separate tabs

Here are the configuration settings I added to my gitconfig :

git config --global mergetool.meld3.cmd \
    'meld --auto-merge $LOCAL $BASE $REMOTE -o $MERGED'\
    ' --diff $LOCAL $BASE --diff $BASE $REMOTE'

git config --global merge.tool meld3

I'm pretty sure other diff viewers such as vimdiff3 or kdiff3 have an equivalent --auto-merge option, but I will let you scan the documentation to find out how it's named and how to use it.

Upvotes: 2

Related Questions