A rebase works by "playing back" the changes in each diff, making new commits (leaving the old ones intact, but hidden, and subject to eventual expiration). The method by which a commit is "played back" is available more directly as git cherry-pick
, so I'll describe this first.
Doing a cherry-pick involves extracting the changes made in a commit. The resulting diff can then be applied to ("patched into") some other (different) commit, by using the context included in the diff. But, because a commit is not stored as changes—each commit is a complete "snapshot" of the source—a cherry-pick has to compute the changes provided by a commit. It does so by comparing the commit to its parent commit.
This works fine for linear history, but not so well when there are merges. In particular a merge commit has, by definition, at least two parents (merges with more than two parents are unusual but git allows them). Given a commit with two parents, and no obvious way to choose which parent to consider when making a diff, git cherry-pick
simply halts with a complaint. It makes you provide, with the -m
option, the parent you care about when cherry-picking a merge commit.
The rebase command takes a different approach: by default, it eliminates merges entirely, hoping that they were not necessary. If the merges matter (as apparently they do here) this will result in failing cherry-picks.
The rebase command does have a -p
(aka --preserve-merges
) option. In this case, it attempts to keep the merges. In this particular case, where you're replaying a commit sequence onto the original base commit, a merge-preserving rebase should work.
See the documentation for more details.