What is the difference between `git rebase master` and `git rebase –onto master`?

[Do not accept this answer. It’s just a sort of commentary on torek’s answer.]

The way to look at this, in my opinion, is to understand that the full form of git rebase is with onto and three parameters:

git rebase --onto x y z

To read that, in your mind clump y and z together, and swap --onto x to the end (because that’s the natural English order for direct object and prepositional phrase), so that the whole thing parses something like this (pseudo-code):

rebase [y z] onto x

In that pseudo-code, the expression [y z] means “starting right after y and continuing all the way to z.” Git calculates what “starting right after y” means by working backwards from z, not forward from y, but the effect is generally the same.

So git rebase --onto x y z means: “Grab all the commits starting right after y and continuing all the way to z, and append them to x.”


Very well. That’s the full form of git rebase. When you omit any of the parameters, Git fills them in for you. And the way it does that is surprising. That’s the reason for the results you’re seeing.

So let’s take a real example. Here’s our starting position:

* f8696e6 (HEAD -> dev) z
* 103333e (origin/dev) y
* 559ad1f x
| * 8032a5d (origin/main, main) c
| * 2caa1e9 b
|/  
* 06c7439 a

Look carefully at the graph. We are on dev. We have a remote origin, and we are one ahead of our remote-tracking branch origin/dev. dev split off from main at a, and after that it goes

x y z 

Meanwhile, main goes

a b c

Now let’s try

git rebase main

We are on dev, so that means

git rebase main main dev

Which means “grab the commits start from after main and continuing to dev — namely, x y z — and attach them to main.” Here we go… Here’s what we get:

* f6b903e (HEAD -> dev) z
* 4adb109 y
* e9cc7fd x
* 8032a5d (origin/main, main) c
* 2caa1e9 b
* 06c7439 a

Yup, just as I said. This, I think, is what most people expect when they use a one-parameter git rebase.

Okay, now start over. This time we’ll say

git rebase --onto main

That, as torek’s answer tells you, means

git rebase --onto main origin/dev dev

So that means “grab everything from origin/dev to dev — that’s just z — and attach it to main. That’s extremely surprising! We never said anything about origin/dev, but that’s where Git is going to snip our branch as we rebase. Here we go… Here’s what we get:

* 0dccc25 (HEAD -> dev) z
* 8032a5d (origin/main, main) c
* 2caa1e9 b
| * 103333e (origin/dev) y
| * 559ad1f x
|/  
* 06c7439 a

That’s probably the kind of thing happened to you (the OP). And it’s easy to see why you found it surprising!

So in my opinion the main takeaway is that if you leave out any of the three parameters, you may be surprised by what Git chooses for them. Therefore, also in my opinion, you should not leave out any of them! You just don’t know what will happen if you do.


Final note: Okay, I lied a little. Remember the first result?

* f6b903e (HEAD -> dev) z
* 4adb109 y
* e9cc7fd x
* 8032a5d (origin/main, main) c
* 2caa1e9 b
* 06c7439 a

I left out origin/dev from that diagram. In reality, this what we now have:

* f6b903e (HEAD -> dev) z
* 4adb109 y
* e9cc7fd x
* 8032a5d (origin/main, main) c
* 2caa1e9 b
| * 103333e (origin/dev) y
| * 559ad1f x
|/  
* 06c7439 a

Notice the duplication of the x and y commits. That’s what git rebase does: it copies commits. This is a tricky situation if we intend to push dev, because we will be asking the remote origin to forget about the y and x currently pointed to by origin/dev, and it isn’t going to be happy about that.

Leave a Comment