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

0

Issue

Like the title says, what is the difference between git rebase master and git rebase --onto master?

I ran both commands expecting to see the exact same results but got two wildly different commit histories afterwards.

Whats the big deal here? How are they different from each other?

Solution

Edit: I forgot a last—or first—point, which I’ll insert first here. The usage for git rebase is, greatly simplified:

git rebase [ --onto <newbase> ] [ <upstream> ]

The square brackets [...] indicate that each argument is optional. The angle brackets <...> mean you fill in something here. The --onto newbase option uses a flag; the newbase is given (by you, the user) if and only if it’s preceded by the keyword --onto, spelled with a double hyphen. Similarly, the upstream argument is give if and only if you give it. So:

git rebase master

gives one argument, an upstream, of master; git rebase --onto master gives one argument, a newbase, of master. If you don’t give an upstream argument, git rebase finds one on its own. If you don’t give a newbase argument, git rebase finds one on its own. If you give one, but not the other, git rebase still finds the other one on its own.

As a one-liner answer, then: git rebase master chooses master as both target and upstream, but git rebase --onto master chooses master as target, with the default upstream, whatever that is for the current branch. You can see the default for the current branch with:

git rev-parse --abbrev-ref @{upstream}

If the current branch is, say, dev, and its upstream is origin/dev, then git rebase master means git rebase --onto master master, but git rebase --onto master means git rebase --onto master origin/dev.

What the various arguments mean

To perform its duties—which is to say, copy some commits, then move one branch name—git rebase needs to know three things:

  • What commits should I copy?
  • Where should I put these copies?
  • What branch name should I move, in the end, after making copies?

The last of these defaults to the current branch.1 So you just run git checkout or git switch first, to select the correct branch.2

The Git authors cleverly crammed the remaining two of these three things into one argument to git rebase, which the documentation calls the upstream argument.

Sometimes, however, you really need these two things to be separate. The --onto flag allows you to separate them:

git rebase --onto <newbase> <upstream>

copies the commits to the supplied newbase, rather than copying them to the supplied upstream.

The curious thing is not so much the newbase, which is pretty straightforward, but rather how the upstream argument is used. The way it is used is complex, but to simplify it to essentials, Git runs:

git rev-list upstream..HEAD

(after doing the initial git checkout or git switch, if you provide a branch argument). So upstream specifies not what to copy, but rather what not to copy.

The rebase command is going to copy some set of commits. This is a given, because the goal of git rebase is to take some existing commits that are not quite good enough, in some way or form, and turn them into improved commits—but it is literally impossible to change any commit, once it is made. Since the existing commits can’t be changed, the best that git rebase can do is to copy them to new-and-improved copies, and then start using the copies in place of the originals.

The "use the copies instead of the originals" step is what makes it necessary to move one branch name. Git finds commits using branch names. Branch names move all the time, usually in a simple, one-step-at-a-time, easily followed manner. But because names can move, git reset and other Git commands can move them, perhaps even violently, many commits at a time, abducting them from their home village in China and dropping them into the Australian Outback or whatever. 😀 In the case of git rebase, the rebase code first copies the selected, old-and-lousy commits to their new home, making changes that—we hope—improve them, or at least make them fit into their new home. Then it moves the branch name so that we find the copies instead of the originals—and then rebase is done.

The upstream argument specifies what not to copy, and sometimes—actually, remarkably often!—this same specifier can be used as the place that the to-be-copied commits should go. But when it can’t be used that way, the --onto argument lets you specify both where to copy and what not to copy, as two separate things.

git | move old commit to the past of another branch shows a case where it’s convenient to specify what not to copy and where to copy to as two separate things.


1In fact, git rebase can only move the current branch (as of the latest Git versions, 2.32-ish, but probably for quite a while yet to come too)—so if you supply a branch name, git rebase starts by using git checkout or git switch. See footnote 2.

2You can supply a branch name to the command line command. If you do, the command currently literally runs or has built into it the necessary checkout/switch operation. When the rebase is complete, you’re on the branch you selected, even if you weren’t before the rebase started. That is, in:

git checkout main          # puts us on `main`
git rebase origin/foo foo

we might as well have run:

git checkout foo
git rebase origin/foo

anyway, because we end up on foo, not main. But this does mean that if we would have to run git checkout foo, we can run:

git rebase origin/foo foo

or:

git rebase --onto target origin/foo foo

so as to get git rebase to do the git checkout for us.

Answered By – torek

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave A Reply

Your email address will not be published.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More