git rebase interactive: squash merge commits together

Mildred picture Mildred · Nov 12, 2009 · Viewed 28.5k times · Source

I wanted to have a simple solution to squash two merge commits together during an interactive rebase.

My repository looks like:

   X --- Y --------- M1 -------- M2 (my-feature)
  /                 /           /
 /                 /           /
a --- b --- c --- d --- e --- f (stable)

That is, I have a my-feature branch that has been merged twice recently, with no real commits in between. I don't just want to rebase the my-feature branch since it is a published branch of its own, I just want to squash together the last two merge commits into one (haven't published those commits yet)

   X --- Y ---- M (my-feature)
  /            /
 /            /
a --- ... -- f (stable)

I tried:

git rebase -p -i M1^

But I got:

Refusing to squash a merge: M2

What I finally did is:

git checkout my-feature
git reset --soft HEAD^  # remove the last commit (M2) but keep the changes in the index
git commit -m toto      # redo the commit M2, this time it is not a merge commit
git rebase -p -i M1^    # do the rebase and squash the last commit
git diff M2 HEAD        # test the commits are the same

Now, the new merge commit is not considered a merge commit anymore (it only kept the first parent). So:

git reset --soft HEAD^               # get ready to modify the commit
git stash                            # put away the index
git merge -s ours --no-commit stable # regenerate merge information (the second parent)
git stash apply                      # get the index back with the real merge in it
git commit -a                        # commit your merge
git diff M2 HEAD                     # test that you have the same commit again

But this can get complicated if I have many commits, do you have a better solution ? Thanks.

Mildred

Answer

Matt picture Matt · Nov 9, 2010

This is an old topic, but I just ran across it while looking for similar information.

A trick similar to the one described in Subtree octopus merge is a really good solution to this type of problem:

git checkout my-feature
git reset --soft Y
git rev-parse f > .git/MERGE_HEAD
git commit

That will take the index as it exists at the tip of my-feature, and use it to create a new commit off of Y, with 'f' as a second parent. The result is the same as if you'd never performed M1, but gone straight to performing M2.