From my main feature branch, I started a new feature branch. When I completed this feature, I started another new feature branch. Is it possible to change the branch point of the second sub-feature branch so that it branches from the main feature branch?
i.e., I have this:
A --> B --> C --> D --> E --> F
^ ^ ^
| | |
Head of Head of Head of
feat0 feat1 feat2
I want to turn it into this:
.--> E --> F
/
A --> B
\
`--> C --> D
The changes in C
and D
are (almost) mutually exclusive to those in E
and F
.
I thought that maybe rebase --onto
would do the trick. At F
I did git rebase --onto B
. That moved the head of feat2
to B, losing E
and F
; definitely not what I want!
My second thought is that I could do an interactive rebase at F
from B
, dropping C
and D
, tweaking any commits that cause conflicts. That seems to have worked, but it was unnecessarily messy and involved.
Is there a better way?
CodePudding user response:
You can do this via:
git rebase D F --onto B
The full form of any rebase has 3 arguments:
- the upstream (old base) (D)
- the branch (F)
- the new base (B)
The "graft", identified by the base & branch will be "treated as diffs" and replayed atop the new base, i.e. a "re-base".
Any time you use rebase with fewer arguments, one or more of these arguments are being inferred based on the location of current HEAD, etc.
This operation can also be done via cherry-pick, as rebasing and cherry-picking are conceptually identical.
To explain what was happening with git rebase --onto B
not doing what you wanted, let's first look at the most common rebase
use-case, starting with:
* feat-a (HEAD)
|
*
/
* - main
Suppose other people contribute, so main
gets advanced, e.g. after a fetch
:
* main
| * feat-a (HEAD)
| |
| *
|/
* main-old
A git rebase main
is inferred to mean git rebase main-old feat-a --onto main
because:
--onto <newbase>
Starting point at which to create the new commits. If the --onto option is not specified, the starting point is <upstream>. May be any valid commit, and not just an existing branch name.
<upstream>
Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured upstream for the current branch.
<branch>
Working branch; defaults to HEAD.
Resulting in:
* feat-a (HEAD)
|
*
|
* main
|
* main-old
i.e. "Replay feat-a branch in relation to main-old on (newer) main"
When you ran git rebase --onto B
, the shared ancestor between F (HEAD) and B is... B, so that's where your branch ended up pointing.
CodePudding user response:
Since there is a very recent question on SO about lost commits because of a forced push, it's worth pointing out that you could achieve your goal without rebasing.
feat0
is easy - no workfeat1
is easy - no workfeat2b
(new branch):- Create
feat2b
fromfeat0
(B) - Cherry-pick E and F
- Create
This isn't sophisticated, but it's hard to get this wrong.