Mitch
Mitch

Reputation: 3418

Why does git rebase ignore merge commits?

Recently I had to do some rebasing to resolve some merge conflicts using git rebase master. Git, to my surprise ignored the merge commits causing a lot of headaches where code would just disappear. Eventually I found that -p was what I was looking for but why is the default behaviour of git rebase to ignore merge commits?

Upvotes: 3

Views: 2026

Answers (2)

torek
torek

Reputation: 488183

Any time you ask a why question you get into areas of philosophy, which can be pretty sticky. But I can propose two answers anyway (one of which is supported by the documentation, as in padawin's answer.

The first is that the original idea behind rebasing is that it is something an individual would do just before or during a merge to some sort of more-authoritative repository.

Let's invent two players, Alice and Bob. Alice has the authoritative version: anyone who wants the latest and greatest version of the software goes to Alice.

In Alice's repository, there are various lines of development:

...--o--o--o--o--o   <-- master
            \
             o--o--o   <-- feature/tall

and so on. Bob cloned Alice's repository at some point, perhaps at this point:

...--o--o--o   <-- master, origin/master
            \
             o--o--o   <-- origin/feature/tall

before the last two commits that Alice added to her authoritative master.

Bob then developed his feature, feature/short, from his master, so he has:

             A--B   <-- feature/short
            /
...--o--o--o   <-- master, origin/master
            \
             o--o--o   <-- origin/feature/tall

He thinks he's ready to deliver his result to Alice. So he runs git fetch origin to get any of her updates and now he has this:

             A--B   <-- feature/short
            /
...--o--o--o   <-- master
           |\
           | o--o   <-- origin/master
            \
             o--o--o   <-- origin/feature/tall

He might now update his own master so that it points to the same commit as his origin/master (Alice's current tip of master):

             A--B   <-- feature/short
            /
...--o--o--o--o--o   <-- master, origin/master
            \
             o--o--o   <-- origin/feature/tall

Before delivering his A--B series of commits to Alice, he should make sure they work. So he can git checkout master && git merge feature/short which produces:

             A---B
            /     \
           |       M   <-- feature/short
           |      /
...--o--o--o--o--o   <-- master, origin/master
            \
             o--o--o   <-- origin/feature/tall

Bob can test M and see that it works—so now it's safe to rebase A and B atop the tip of master, giving:

           [old commits, no longer in use]
           |
           |       A'-B'  <-- feature/short
           |      /
...--o--o--o--o--o   <-- master, origin/master
            \
             o--o--o   <-- origin/feature/tall

Note that commit M is gone from the rebased feature/short: Bob should now deliver the new-and-improved A'-B' chain of commits to Alice, and Alice can choose whether to merge them, or fast-forward to B', or whatever she likes.

The second idea here is that it's actually not possible to copy a merge commit. Copying commit A to A' is just a matter of making the same changes that A made as compared to its parent. Copying B to B' is just a matter of making the same changes that B made to A. But you can't copy a merge; you have to do a whole new merge. That is, of course, possible; and that's what the old -p or the new fancy --rebase-merges actually do: they just identify where a merge happened before, and do a new one—with possibly very different results if the new merge base is different—wherever that makes sense.

One of the two parents of the new merge is obvious: it's the rebased copy commit from some original commit that was a parent of the original merge. The other parent—assuming a two-parent merge, anyway—is not really quite as obvious: sometimes it's an unchanged original commit, and sometimes it's a rebased commit. So this job is harder than it might seem at first. The old, non--p rebase code simply said: This is hard, and we don't really want to do it at all in most cases anyway, so let's not bother to try.

(I would argue that this is wrong—that if you're naively rebasing a chain that involves merges, you probably want to copy the merges too—but at the same time, I would argue that if you're naively rebasing a chain that involves merges, you haven't thought enough about what you're doing. So the default behavior sort of makes sense. It might make more sense if it detected merges it was going to skip, and warned or required a flag.)

Upvotes: 8

padawin
padawin

Reputation: 4476

From man git-rebase, it is said:

The interactive rebase command was originally designed to handle individual patch series. As such, it makes sense to exclude merge commits from the todo list, as the developer may have merged the then-current master while working on the branch, only to rebase all the commits onto master eventually (skipping the merge commits).

I would like to add that if you use git rebase --interactive with merges, you should (also, as per man git-rebase) use --rebase-merges instead of --preserve-merges. It will prevent you a lot of other headaches.

Upvotes: 4

Related Questions