How to rebase a branch after its base branch was rebased on master

Viewed 2246

I have a branch A in which I have made some changes a, and then I created a new branch B from A to continue to make some changes b, so that the total changes in B are a, b. Finally, someone else merged their changes c into master, so the work tree now looks like this:

- c (master)
 \ 
  - a (A) - b (B)

Then I rebased A to master, altering changes from a to a' and force pushed them to branch A, so the work tree looks like this:

- c (master) - a' (A)
 \
  - a - b (B)

The question

How can I rebase B to A, so that only the new changes b are altered and the work tree then looks like this:

- c (master) - a' (A) - b' (B)

If I try git rebase origin/A when I'm on B, then it tries to resolve commit a against c, a', which I don't want, since I fear it will create a new commit a'' and then the work tree will look like this:

- c (master) - a' (A) - a'' - b' (B)

Is there a way to only rebase change b now? (I have OFC tried to google this, but I didn't find anything to cover this case specifically)

EDIT:

I tried squashing a' and b on B, but that was even worse, because now all the changes from b were registered as conflicts. To illustrate, let's say that I:

  1. Added a new line l1 in a.
  2. Changed that line to l2 in b.
  3. This line was untouched in the other commit c.
  4. Now it is a conflict, because l1 was added in a', but l2 was added in the quashed commit a'&b.
4 Answers
git rebase --onto A a B

--onto A uses A as the new base. a B gets the set of commits like git log a..B, which in your case includes only b. The set of commits are applied onto the new base.

You can always use interactive rebase to deal with simple or complicated cases.

git rebase -i A

On the editing page, modify the leading pick to drop in the line of a, or simply remove the whole line. Save and quit.

If git rebase does not work, you can also try git cherry-pick to apply only the commits you want, and then reset B to the new head.

git checkout -b tmp A
git cherry-pick b
git checkout B
git branch B_bak
git reset tmp --hard
git branch -D tmp

If anything goes wrong, you can run git checkout B && git reset B_bak --hard to restore B.

How can I rebase B to A, so that only the new changes b are altered?

You can simply do:

git rebase A B

which is the same as saying "switch to branch B and rebase it on top of A". Git won't re-apply commit a because it can determine that a' already introduces the same changes.

Patch equivalence

When two different commits (i.e. commits with different SHA-1 hashes) introduce the same set of changes, they're are said to be patch equivalent.

You can determine which commits are patch equivalent between two branches by using the --cherry-pick option of git log:

--cherry-pick
Omit any commit that introduces the same change as another commit on the “other side” when the set of commits are limited with symmetric difference.

In your case, if you do:

git log --oneline --cherry-pick --left-right A...B

You'll see that commits a' and a are excluded from the output because they are patch equivalent.1

When you rebase B on top of A, Git cherry-picks the commits from B and applies them on top of A one at a time. When it reaches a commit that introduces the same changes as an existing one, it will simply skip it and move to the next one.


1 The ... notation (also known as triple dot) selects the commits that are reachable from either one of two references, but not both. This is useful to select the commits that are "new" in two different branches starting from their common parent.

Tell rebase to hunt down where the new base's tip used to be and start from there:

git rebase --fork-point A

and if your current branch's upstream is set, both that and the --fork-point option are the defaults, so your command is

git rebase
Related