I have noticed that the two blocks of following git commands have different behaviours and I don't understand why.
I have a A and a B branch that diverge with one commit
---BRANCH A-------COMMIT1-----
\--BRANCH B--
I want to rebase B branch on the lastest A (and have the commit1 on the B branch)
---BRANCH A-------COMMIT1-----
\--BRANCH B--
No problem if I do:
checkout B
rebase A
But if I do:
checkout B
rebase --onto B A
It doesn't work at all, nothing happens. I don't understand why the two behaviours are different.
Phpstorm git client use the second syntax, and so seems to me completely broken, that's why I ask for this syntax issue.
For
onto
you need two additional branches. With that command you can apply commits frombranchB
that are based onbranchA
onto another branch e.g.master
. In the sample belowbranchB
is based onbranchA
and you want to apply the changes ofbranchB
onmaster
without applying the changes ofbranchA
.by using the commands:
you will get following commit hierarchy.
tl;dr
The correct syntax to rebase
B
on top ofA
usinggit rebase --onto
in your case is:or rebase
B
on top ofA
starting from the commit that is the parent ofB
referenced withB^
orB~1
.If you're interested in the difference between
git rebase <branch>
andgit rebase --onto <branch>
read on.The Quick: git rebase
git rebase <branch>
is going to rebase the branch you currently have checked out, referenced byHEAD
, on top of the latest commit that is reachable from<branch>
but not fromHEAD
.This is the most common case of rebasing and arguably the one that requires less planning up front.
In this example,
F
andG
are commits that are reachable frombranch
but not fromHEAD
. Sayinggit rebase branch
will takeD
, that is the first commit after the branching point, and rebase it (i.e. change its parent) on top of the latest commit reachable frombranch
but not fromHEAD
, that isG
.The Precise: git rebase --onto with 2 arguments
git rebase --onto
allows you to rebase starting from a specific commit. It grants you exact control over what is being rebased and where. This is for scenarios where you need to be precise.For example, let's imagine that we need to rebase
HEAD
precisely on top ofF
starting fromE
. We're only interested in bringingF
into our working branch while, at the same time, we don't want to keepD
because it contains some incompatible changes.In this case, we would say
git rebase --onto F D
. This means:In other words, change the parent of
E
fromD
toF
. The syntax ofgit rebase --onto
is thengit rebase --onto <newparent> <oldparent>
.Another scenario where this comes in handy is when you want to quickly remove some commits from the current branch without having to do an interactive rebase:
In this example, in order to remove
C
andE
from the sequence you would saygit rebase --onto B E
, or rebaseHEAD
on top ofB
where the old parent wasE
.The Surgeon: git rebase --onto with 3 arguments
git rebase --onto
can go one step further in terms of precision. In fact, it allows you to rebase an arbitrary range of commits on top of another one.Here's an example:
In this case, we want to rebase the exact range
E---H
on top ofF
, ignoring whereHEAD
is currently pointing to. We can do that by sayinggit rebase --onto F D H
, which means:The syntax of
git rebase --onto
with a range of commits then becomesgit rebase --onto <newparent> <oldparent> <until>
. The trick here is remembering that the commit referenced by<until>
is included in the range and will become the newHEAD
after the rebase is complete.This is all you need to know to understand
--onto
:You're switching a parent on a commit, but you're not providing the sha of the commit, only the sha of it's current (old) parent.
Put shortly, given:
Which is the same as (because
--onto
takes one argument):Means rebase commits in range (D, H] on top of F. Notice the range is left-hand exclusive. It's exclusive because it's easier to specify 1st commit by typing e.g.
branch
to letgit
find the 1st diverged commit which leads toH
.OP case
Can be changed to single command:
What looks like error here is placement of
B
which means "move some commits which lead to branchB
on top ofB
. The questions is what "some commits" are. If you add-i
flag you will see it is single commit pointed byHEAD
. The commit is skipped because it is already applied to--onto
targetB
and so nothing happens.The command is nonsense in any case where branch name is repeated like that. This is because the range of commits will be some commits which are already in that branch and during rebase all of them will be skipped.
Further explanation and applicable usage of
git rebase <upstream> <branch> --onto <newbase>
.git rebase
defaults.Expands to either :
Automatic checkout after rebase.
When used in standard way, like:
You won't notice that after rebase
git
movesbranch
to most recently rebased commit and doesgit checkout branch
. What is interesting when 2nd argument is commit hash instead branch name rebase still works but there is no branch to move so you end up in "detached HEAD" instead being checked out to moved branch.Omit primary diverged commits.
The
master
in--onto
is taken from 1stgit rebase
argument but it can be any commit or branch. This way you can limit number of rebase commits by taking the latest ones and leaving primary diverged commits.Will rebase single commit pointed by
HEAD
.Avoid explicit checkouts.
The default
HEAD
orcurrent_branch
are contextually taken from palce you're in. This is why most people checkout to branch which they want to rebase. But when argument is given explicitly you don't have to checkout before rebase to pass it in implicit way.This means you can rebase commits and branches from any place. So together with Automatic checkout after rebase. you don't have to separately checkout rebased branch before or after rebase.
Simply put,
git rebase --onto
selects a range of commits and rebases them on the commit given as parameter.Read the man pages for
git rebase
, search for "onto". The examples are very good:In this case you tell git to rebase the commits from
topicA
totopicB
on top ofmaster
.