Git: Rebase with dependent branches¶
Rebasing a branch on another is very common...
Yet by default, the dependent branches of the rebased branch will remain where they are and not follow the rebase.
This notes shows how to deal with those dependent branches.
The problem¶
At some point in time, you will end up with a situation as described below where you have a branch (main
), with a child branch (feature/foo
) with a child-child branch (feature/bar
).
graph LR
A[A] --> B[B];
B[B] --> C[C];
C[C] --> D[D];
D[D] --> E[E];
E[E] -.- F{{main}};
C[C] --> G[G];
G[G] --> H[H];
H[H] --> I[I];
I[I] -.- J{{feature/foo}};
H[H] --> K[K];
K[K] --> L[L];
L[L] -.- M{{feature/bar}};
style F fill:#000,stroke:#000,color:#fff;
style J fill:#000,stroke:#000,color:#fff;
style M fill:#000,stroke:#000,color:#fff;
Using rebase here is complicated.
Start by rebasing the first child (in this case feature/foo
):
git checkout feature/foo
git rebase main
You'll end up with this:
graph LR
A[A] --> B[B];
B[B] --> C[C];
C[C] --> D[D];
D[D] --> E[E];
E[E] -.- F{{main}};
E[E] --> GG[G'];
GG[G'] --> HH[H'];
HH[H'] --> II[I'];
II[I'] -.- JJ{{feature/foo}};
C[C] --> G[G];
G[G] --> H[H];
H[H] --> K[K];
K[K] --> L[L];
L[L] -.- M{{feature/bar}};
style F fill:#000,stroke:#000,color:#fff;
style JJ fill:#000,stroke:#000,color:#fff;
style M fill:#000,stroke:#000,color:#fff;
As you can see, feature/foo
has been rebased on main
... BUT feature/bar
has been left where it was.
WRONG¶
If you were to run a rebase with feature/bar
like this:
git checkout feature/bar
git rebase main
You end up with something like this:
graph LR
A[A] --> B[B];
B[B] --> C[C];
C[C] --> D[D];
D[D] --> E[E];
E[E] -.- F{{main}};
E[E] --> GG[G'];
GG[G'] --> HH[H'];
HH[H'] --> II[I'];
II[I'] -.- JJ{{feature/foo}};
E[E] --> G[G''];
G[G''] --> H[H''];
H[H''] --> K[K''];
K[K''] --> L[L''];
L[L''] -.- M{{feature/bar}};
style F fill:#000,stroke:#000,color:#fff;
style JJ fill:#000,stroke:#000,color:#fff;
style M fill:#000,stroke:#000,color:#fff;
NOT WHAT YOU'RE LOOKING FOR
Could get worse...
Trying to rebase on feature/foo
will result in this (after dealing with a lot of conflicts):
git checkout feature/bar
git rebase feature/foo
graph LR
A[A] --> B[B];
B[B] --> C[C];
C[C] --> D[D];
D[D] --> E[E];
E[E] -.- F{{main}};
E[E] --> GG[G'];
GG[G'] --> HH[H'];
HH[H'] --> II[I'];
II[I'] -.- JJ{{feature/foo}};
II[I'] --> G[G''];
G[G''] --> H[H''];
H[H''] --> K[K''];
K[K''] --> L[L''];
L[L''] -.- M{{feature/bar}};
style F fill:#000,stroke:#000,color:#fff;
style JJ fill:#000,stroke:#000,color:#fff;
style M fill:#000,stroke:#000,color:#fff;
As you can see, commits G
& H
have been redone even though they ought to be skipped.
STILL NOT WHAT YOU'RE LOOKING FOR
THE SOLUTION¶
The solution is to use the --onto
feature of git rebase
git checkout feature/bar
git rebase --onto {hash_of_H'} feature/foo@{1}
The @{1}
in the above code means: the latest known state of feature/foo
before the rebase.
graph LR
A[A] --> B[B];
B[B] --> C[C];
C[C] --> D[D];
D[D] --> E[E];
E[E] -.- F{{main}};
E[E] --> GG[G'];
GG[G'] --> HH[H'];
HH[H'] --> II[I'];
II[I'] -.- JJ{{feature/foo}};
HH[H'] --> K[K'];
K[K'] --> L[L'];
L[L'] -.- M{{feature/bar}};
style F fill:#000,stroke:#000,color:#fff;
style JJ fill:#000,stroke:#000,color:#fff;
style M fill:#000,stroke:#000,color:#fff;
PERFECT !
Note that, if feature/bar
had been branched on the last commit of feature/foo
:
graph LR
A[A] --> B[B];
B[B] --> C[C];
C[C] --> D[D];
D[D] --> E[E];
E[E] -.- F{{main}};
C[C] --> G[G];
G[G] --> H[H];
H[H] -.- J{{feature/foo}};
H[H] --> K[K];
K[K] --> L[L];
L[L] -.- M{{feature/bar}};
style F fill:#000,stroke:#000,color:#fff;
style J fill:#000,stroke:#000,color:#fff;
style M fill:#000,stroke:#000,color:#fff;
The command would have been more simple:
git checkout feature/foo
git rebase main
git checkout feature/bar
git rebase --onto feature/foo feature/foo@{1}
graph LR
A[A] --> B[B];
B[B] --> C[C];
C[C] --> D[D];
D[D] --> E[E];
E[E] -.- F{{main}};
E[E] --> GG[G'];
GG[G'] --> HH[H'];
HH[H'] -.- JJ{{feature/foo}};
HH[H'] --> K[K'];
K[K'] --> L[L'];
L[L'] -.- M{{feature/bar}};
style F fill:#000,stroke:#000,color:#fff;
style JJ fill:#000,stroke:#000,color:#fff;
style M fill:#000,stroke:#000,color:#fff;