There's no such thing as a partial merge, although one could use that phrase to describe an in-progress or incomplete merge. But in Git, merging actually takes place in Git's index. Until you (or Git) commit the merge, it's just in-progress. When you (or Git) make the merge commit, you have a completed merge: a merge commit, which is a commit with two or more parents. Every commit holds a snapshot of all the files that Git knew about at the time whoever made the commit, made it; and a merge commit is no different. The git commit
command builds the new commit from the files that are in Git's index. So once the merge is all set up in Git's index, you or Git run git commit
, and Git builds the merge commit from the files that are in Git's index. Those are what is in the merge commit, as its snapshot.
However, not all of the changes I've made in the branch are reflected in the master branch.
That is, if you compare an earlier commit in master
to the snapshot in the merge commit, you don't see what you wanted to see. That's certainly possible: the merge commit will contain the files that were in Git's index when you made the merge commit, and if those files weren't the files that you wanted it to contain, the merge commit's snapshot won't be what you wanted it to be.
The question of how that came about is important and useful (to avoid repeating the problem later) but, despite the comments, that's not what you asked:
Any ideas on where I can go from here?
You have two options:
Remove the merge. This is possible, but not always easy. It's not very difficult if you haven't sent the merge commit on to any other Git repository. If you have, that's when it gets hard.
Add new commits. This is easy! But, coming up with what you want to have in those commits may not be so easy.
To remove the merge, you can use git rebase
; rebasing normally drops merge commits, and you can use this to good effect here. The question to which you linked is quite old—it dates back to when Git version 1.7.4 was new, in early 2011—but those methods are still supported. But this brings us back to the general problem of removing a commit that you've given out to other people: you have to convince all of them to remove that commit too.
To correct the problem and add a new commit, you need to come up with the new commit's content. The easiest way to do that is probably just to re-perform the merge, but correctly this time.
You can re-perform the merge by creating a new branch name at the commit where you want the new merge to go. This requires a bit of understanding of how Git's commit graph works, but suppose you currently have this:
...---J---M--N--O <-- master
/
...--K--L <-- feature
that's the result of merging feature
into master
, producing merge commit M
, and then having several more commits added to master
(N
and O
) since then, building on the incorrect merge. Let's draw the above in a different way, that represents the same graph:
...-----J
\
M--N--O <-- master
/
...--K--L <-- feature
We just need to find, now, the hash ID of commit J
, and create a new branch name pointing to that commit. Let's use the name redo
. We will run:
git checkout -b redo <hash-of-J>
which gives us this drawing:
...-----J <-- redo (HEAD)
\
M--N--O <-- master
/
...--K--L <-- feature
We can now run git merge feature
, or, if we no longer have the name feature
to locate commit L
, we can find its hash ID and run git merge hash
.
Git will grind away for a bit and produce the same merge conflicts as last time. This time, as you resolve the merge conflicts and supply a set of files for Git to put into the new commit, be careful to produce the files that you really do want. This usually includes running tests on them,1 examining Git's own merge results, and so on. Try not to rely on the ours
or theirs
flags: git checkout --ours
means thow away their changes, for instance, and even -X ours
or -X theirs
at the git merge
command isn't necessarily right.
Once you do have the correct merge, you can run git merge --continue
or git commit
2 to make the new merge commit:
...-----J-----M2 <-- redo (HEAD)
\ /
M-/--N--O <-- master
/_/
...--K--L <-- feature
You can at this point run git diff hash-of-M HEAD
, for instance, to see what the new merge looks like. Or, git diff hash-of-M redo
produces the same diff, because the name redo
refers to the same commit (M2
) as the special name HEAD
.
At this point you have multiple options, depending on whether commits N
and O
actually exist. One option that works regardless of whether they exist is to take the diff generated by git diff hash-of-M HEAD
here and actually apply that diff on branch master
. Of course, git checkout master
changes the commit that HEAD
refers to, so we might do this as:
git checkout master
git diff <hash-of-M> redo | git apply -3
(note that the -3
option lets git apply
use a three-way merge if needed, so this can result in merge conflicts, if N
and O
conflict with the changes you'll bring in via the updated M2
merge).
Assuming all of this works, you're now ready to git add
and git commit
the result, producing:
...-----J-----M2 <-- redo
\ /
M-/--N--O--P <-- master (HEAD)
/_/
...--K--L <-- feature
which holds commit P
as a fix for the incorrect merge.
Note that you can, instead of the above diff ... | apply -3
, just merge commit M2
(via git checkout master; git merge redo
). This merge is unfortunately quite tricky internally for Git, as the merge base of O
and M2
is both commits J
and L
. The recursive strategy, which is the default, will merge those two commits to produce a virtual merge base, and that particular merge always produces the conflicts that you've already seen twice at this point. The inner recursive merge just commits the conflicts, which guarantees further conflicts with M2
. This tempts one to use git merge -s ours
but that's quite wrong, or to find an equivalent of git merge -s theirs
, but that's wrong too as it undoes commits N
and O
.
You could use git merge -s resolve redo
from master
. This will pick one of J
or L
to use as the merge base. This is still likely to produce some merge conflicts, though. That's why I suggested the git diff ... | git apply -3
approach: it's likely to have the fewest merge conflicts. Unfortunately, it leaves future users of the repository a bit of a mystery, if you delete the branch name redo
at this point, because they won't have a record of the re-merge.
There is yet another workaround for this, using git commit-tree
instead of git commit
after doing the git apply
and git add
. I'll leave this option to someone else for now, though.
1This can be difficult when the only way to run tests is with some distant CI system! This is one reason I dislike CI systems that make it impossible to test locally.
2Running git merge --continue
just makes sure that you will finish a suspended merge, and then runs git commit
, so these will do the same thing at this point.