Merge commits don't appear in git rebase --interactive

NWard picture NWard · Jul 16, 2014 · Viewed 22.8k times · Source

git log reveals the following:

commit 1abcd[...]
Author: [...]
Date: [...]

    [Useful commit]

commit 2abcd[...]
Author: [...]
Date: [...]

    Merge branch [...] of [etc. etc.]

commit 3abcd[...]
Author: [...]
Date: [...]

    [Useful commit]

That merge commit is useless to me - it doesn't represent a meaningful state of the branch and was generated from a remote pull, so I have the real commits of the remote history - no need for a commit to mark the fact that I pulled. I would like to squash this merge commit. My usual technique for doing a squash is:

git rebase --interactive HEAD~2 (or however far back I need to go)

And then I would squash it into a neighboring commit. I do this some times if for example I make a commit, realize I missed a tiny important detail (single file, or hadn't changed a line in one of the files), and do another commit that's basically just a quick oops. That way when I push my changes back to the remote, everything is nice and clean and tells a cohesive narrative.

However, in this case, when I run the git rebase ... command, commit 2abcd doesn't appear! It seems to skip right over 2abcd and instead displays 1abcd and 3abcd. Is there something special about a merge commit that prevents it from being appearing in git rebase --interactive? What other technique could I use to squash that merge commit?

UPDATE per @Cupcake's request:

The output of git log --graph --oneline --decorate looks like this:

* 1abcd (useful commit)
* 2abcd (merge)
|  \ <-- from remote
|   * 3abcd (useful commit)
|   |

Helpful?

Answer

user456814 picture user456814 · Jul 16, 2014

Rebase doesn't normally preserve merge commits without --preserve-merges

Ok, so I'm not exactly sure what would happen if you tried to squash a merge commit using an interactive rebase with --preserve-merges...but this is how I would remove the merge commit in your case and make your history linear:

  1. Rebase everything before the merge commit on top of the remote branch.

  2. Cherry-pick or rebase everything after the merge commit on top of the previously rebased commits.

If you only have 1 commit after the merge commit

So in terms of commands, that would look something like this:

# Reset to commit before merge commit
git reset --hard <merge>^

# Rebase onto the remote branch
git rebase <remote>/<branch>

# Cherry-pick the last commit
git cherry-pick 1abcd 

If you have more than 1 commit after the merge commit

# Leave a temporary branch at your current commit
git branch temp

# Reset to commit before merge commit
git reset --hard <merge>^

# Rebase onto the remote branch
git rebase <remote>/<branch>

# Cherry-pick the last commits using a commit range.
# The start of the range is exclusive (not included)
git cherry-pick <merge>..temp

# Alternatively to the cherry-pick above, you can instead rebase everything
# from the merge commit to the tip of the temp branch onto the other
# newly rebased commits.
#
# You can also use --preserve-merges to preserve merge commits following
# the first merge commit that you want to get rid of...but if there were
# any conflicts in those merge commits, you'll need to re-resolve them again.
git rebase --preserve-merges --onto <currentBranch> <merge> temp

# These next steps are only necessary if you did the rebase above,
# instead of using the cherry-pick range.
#
# Fast-forward your previous branch and delete temp
git checkout <previousBranch>
git merge temp
git branch -d temp

Documentation