Reputation: 3418
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
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
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