How to rebase a branch after its base branch was rebased on master [duplicate]
Asked Answered
G

4

8

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.
Giovannagiovanni answered 4/9, 2020 at 7:31 Comment(0)
I
5
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.

Ibiza answered 4/9, 2020 at 8:0 Comment(3)
What should I set as a in git rebase --onto A a B? I tried the start of the commit hash, but it didn't work very well. I unfortunately can't remember what the problem was (tried 5+ things, each failed in a different way).Giovannagiovanni
@Giovannagiovanni The SHA1 of a in your graph - a - b (B).Ibiza
Thanks, after lots of trying and reading up, I found that git rebase --onto A a is what I need (which seems to be the same as git rebase --onto A a B when checked out on B). I still don't remember why it failed the first time (maybe I pasted a wrong commit hash).Giovannagiovanni
P
1

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.

Partlow answered 4/9, 2020 at 8:5 Comment(4)
git rebase B A doesn't seem to work, not only it then switches my current branch to A, but I don't even see the correct changes.Giovannagiovanni
Sorry, I accidentally inverted the arguments :$. I updated my answer.Partlow
Thanks, I tried switching the arguments, but that didn't seem to anything it all, it was probably a bug in VSCode or something. In the end I bit the bullet and rebased to master, resolving the conflict between a and c again.Giovannagiovanni
Ok, I understand how it works now. Git won't re-apply commit a because it can determine that a' already introduces the same changes. This will not work because there was a conflict and I would not only have to resolve the conflict again but I would have to resolve it exactly the same as before or else the commits would not be the same.Giovannagiovanni
S
1

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
Surround answered 7/9, 2020 at 15:14 Comment(0)
G
0

After the fact, I found this article: RECOVERING FROM UPSTREAM REBASE, but I haven't tried it yet.

EDIT: After doing a lot of research, the correct answer is:

git checkout B
git rebase --onto A a

See I can't understand the behaviour of git rebase --onto for explanation.

Giovannagiovanni answered 7/9, 2020 at 11:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.