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.