Git three-way merge on files already marked merged

时间:2019-04-16 22:44:19

标签: git merge vimdiff

In order to preserve the conflict resolution history of merging from master branch to development branch, I do a commit of the state as-is, followed by another commit of conflict resolution.

In the first commit, I do nothing to resolve the conflict, so all the "<<<<<<<<=========>>>>>>>>" are kept in source code. In the second commit, I explicitly resolve the conflict.

The problem is, after I mark the file as resolved in the first commit, I don't know how to make a three-way merge on the second step. So how should I make git do a three-way merge on the files already marked merged?

1 个答案:

答案 0 :(得分:1)

There's little to no value to doing things the way you are doing, so I recommend instead that you just complete the merge and commit it. Still, there is a way to do what you want. The problem is that you need to locate the correct three input files. The correct set of input files were computed and were available during the git merge conflict, but are no longer available—you must re-compute them.

The three input files are:

  • the merge base version;
  • the left-hand-side or --ours or HEAD or "local" version; and
  • the right-hand-side or --theirs or "remote" version.

When you edited the work-tree file and saw:

auto-merged result text
<<<<<<< HEAD
some text
=======
different text
>>>>>>> their-branch
more auto-merged text

the text you were seeing was Git's best effort at combining these three inputs. The actual inputs were in the index at the time, as "stage 1", "stage 2", and "stage 3" respectively. If you ran git mergetool, for instance, to complete the merge, the git mergetool command used git checkout-index to extract these three files (which it called BASE, LOCAL, and REMOTE).

When you committed the merge, Git erased the three inputs. They are no longer available. You can usually find them again. Here's where they came from:

  • The merge base file was extracted from the merge base commit.

    To find the merge base commit hash ID, use git merge-base --all. You'll also need the other two commit hash IDs. If the resulting merge base ID is, say, bbbbbbb, you can then run:

    git show bbbbbbb:path/to/file > file.base

    to reconstruct the merge base copy of the file, in the current work-tree.

  • The left-hand-side file probably came out of the commit you were using at the time. (The exception to this rule occurs when you use a modified work-tree file as the left-hand-side file. In this case the left-hand-side version is not recoverable through Git at all.) So you'll need this hash ID. Let's say it's 1111111; then:

    git show 1111111:path/to/file > file.local
    

    would re-create that input in the current work-tree.

  • Finally, the right-hand-side file came out of the commit you were merging. You'll need this last commit hash ID. Let's say it's 2222222; then:

    git show 2222222:path/to/file > file.remote
    

    would re-create that input in the current work-tree.

All of this assumes that there were no file renames detected during the merge process. If there were such renames, choose the correct path names for each of the three input commits.

Once you have all three inputs, you can use the git merge-file command to combine them, the same way git merge would have. This will leave the merged result, complete with conflict markers, in one of those three files. See the linked documentation for details.

Finally, note that you can re-run the merge, by checking out the then-HEAD commit as a detached HEAD. You can find both the then-HEAD and the other commit hash IDs by examining the merge commit: it has two parents, and those two parents are the left and right hand side commits respectively. So if the merge commit has hash ID $M, then:

git checkout $M^1     # detach HEAD at left side commit
git merge $M^2        # re-execute the merge with right hand side commit

will repeat the work that git merge did the first time, computing the same merge base and doing the same merge work and producing the same merge conflicts. See the gitrevisions documentation for more about the <revision>^1 and <revision>^2 notation. When the conflicts recur, the higher stage entries will be in your index once again.