How to squash a merge commit with a normal commit?

Soumya Mohapatra picture Soumya Mohapatra · Dec 19, 2017 · Viewed 7.1k times · Source
commit b0e5db36ed68d4562275adeb08001b1316a4da52
Merge: ea38baa 8220bb1

commit ea38baa3f46a48722987b8dd3892d2b8d81c4d1a

In this case how do I squash these two commits

I am using

git rebase -i HEAD~2

but this doesn't work as it removes the Merge commit and that is not available for squashing

Answer

Mark Adelsberger picture Mark Adelsberger · Dec 19, 2017

So two factors to consider here:

First, in a few ways rebase doesn't always play nice with merge commits. I'll come back to that a couple times.

Second, it's not entirely clear what result you're expecting. If right now you have something like

x -- x -- M -- C <--(master)
 \       /
  A --- B

are you trying to end up with

x -- x -- ABC <--(master)

or

x -- x -- MC <--(master)
 \       /
  A --- B

If you want the version with ABC, it's pretty simple. While M doesn't appear in the TODO list of a rebase, all of the commits that were brought into the mainline by M (i.e. A and B in this example) are. So just mark B and C for "squash". The only thing to remember is that this is a history rewrite, so if you have already pushed any refs that can reach A, B, M, or C then some clean-up may be needed (see the rebase docs under "recovering from upstream rebase").

If you want the version with MC, then there are a lot of problems. Not that you can't get it, but I don't think you can get it by doing a rebase; and also, MC would be an "evil merge", which could cause problems with future rebase attempts as well (among other things).

By default rebase tries to produce a linear history and won't produce merge commits. You can get it to produce merge commits with the --preserve-merges option, but this doesn't interact well with -i (and if you were to try to modify a merge by doing it, I anticipate several possible problems).

If you're not worried about the problems of hiding changes in a merge commit, and really want to produce a commit like MC, then the way to do it is:

First, move the master ref back to M while keeping the changes from C in the index.

git checkout master
git reset --soft HEAD^

Then re-apply the changes directly to the merge commit

git commit --amend