Best way to rebase against a parent branch that performed a rebase?

Viewed 115

I have branch_A that is branched off master and branch_B that is branched off branch_A.

 Master----+                       
           |                       
           |                       
           |Branch_A------+        
                          |        
                          |        
                          |Branch_B

In my case, master updated and I rebased Branch_A. Now Branch_B has diverged. The only solution I've done is make a new branch from A and cherry-pick commits. It's very convoluted and gets confusing. Is there an easier way in this current state to get B up to date with A even with the divergent history?

3 Answers
git rebase --fork-point Branch_A

is your ticket, or if this is purely local work and you did git checkout -t -b Branch_B while on Branch-A or otherwise told Git about the relationship, just plain git rebase will do it.

Let's do --fork-point by easy stages. Start where everything happens in your repo, going on the scenario in your question you did

git checkout -b Branch_A master
git commit however many times
git checkout -b Branch_B Branch_A
git commit however many times
git rebase master Branch_A
git commit possibly some more
git checkout Branch_B
git commit possibly even more

producing

X---*---*---*---Y    Branch_A
 \
  *---A---*---B      Branch_B

where A is the commit that was Branch_A's tip when you started Branch_B. Now you want to rebase Branch_B. That's the situation git rebase --fork-point Branch_A is for.

What the --fork-point does is, it looks through your reflogs to see whether any of your current tip's history is really abandoned commits from the new base's past. X---*---A was once Branch_A's tip, Git concludes that's where you "fork"ed branch B, so that's the rebase cut point. git rebase --fork-point Branch_A will rebase the A..B range on to Branch_A's current tip.

This gets much less tedious if you tell Git what you're doing. If Branch_A is somebody else's work you've almost certainly been fetching it, and your real base was almost certainly origin/Branch_A. So git branch --set-upstream-to origin/Branch-A Branch_B and Git will figure it all out from just plain git rebase.

You can use the optional --onto parameter to rebase:

git rebase <Branch_A-old> Branch_B --onto Branch_A

For <Branch_A-old> you need either a branch or a commit ID. So, before you rebase Branch_A you could do something like:

git branch Branch_A-old Branch_A
git rebase Branch_A master

Or, you could simply look at the current history of Branch_B and use the parent commit ID of the first commit you would have cherry-picked, which will represent the previous version of the commit on Branch_A. Note this "fancy" rebase is doing what you did with cherry-picks, but all in one shot and without necessitating creating a new branch.

Side Note: you can also cherry-pick a range of commits in a single command, starting with the parent ID of the first commit you want to cherry-pick, through the last commit, like this:

git switch -c Branch_B-new Branch_A
git cherry-pick <first-commit-id>^..<Branch_B>
# note the ^ symbol refers to the parent

This still isn't as efficient as the single rebase command because it requires a new branch first, but it's nice to know this command exists.

Way off to the side note: In general, I recommend trying to avoid working on something that depends on something else that isn't merged yet, especially when you use a rebase (or squash) strategy, for the exact reasons you have witnessed. Obviously you can't avoid it all the time, and you can just rebase --onto when it happens. Oftentimes though, you may find you can start working on something else while you're waiting.

Yes, there is. If instead of rebasing, you

git checkout Branch_A
git merge master

Fix the merge conflicts that may arise, then you can update Branch_B via

git checkout Branch_B
git merge Branch_A

You may still have merge conflicts and similar difficulties, but at least up to the point when Branch_B merged out from Branch_A, things are perfectly in sync between the two. So, whatever was pushed into master and Branch_B afterwards may have diverged, which means that you may need to fix this, but, if the changes are not very close to each-other in the code, then you may end up merging without conflicts.

Related