TL;DR the command you want is:
git rebase --onto [the new HEAD base] [the old head base - check git log] [the-branch-to-rebase-from-one-base-to-another]
And my main motivation to putting it here is to easily find it again in the future as I always forget the syntax.
(This is a re-post from my old blog on drupalgardens, but it is still helpful.)
Mental model
To make all of this simpler think of:
You have:
- Two red dishes on top of two blue dishes
- One yellow dish
You want:
- Those two red dishes on top of the one yellow dish
You do:
- Carefully go with the finger down to the bottom of the two red dishes, which is the first blue dish
- Take the two red dishes
- Transfer them over to the one yellow dish
That is what rebase --onto does:
git rebase --onto [yellow dish] [from: first blue dish] [the two red dishes]
Note: The following is meant for an intermediate audience that is familiar with general rebasing in GIT
Longer explanation
It happened! A branch - you had based your work - on has diverged upstream, but you still have work in progress, which you want to preserve.
So it looks like this for you:
"Your Commit A" - "Your Commit B" - "Upstream Commit: This is the last upstream commit" - "Upstream Commit: This is another upstream commit"
But now the branch you based your work on squashed their work together or removed a file due to privacy concerns and now upstream has:
"Upstream Commit: This is the last upstream commit and this is another upstream commit together in one."
So all that you want is to move your two commits onto the new base.
The cherry-pick way
Lets assume your branch is called 'work' and you based your work upon 'upstream/develop'.
What you could do is to checkout the develop branch, reset it to the new version and cherry-pick your two commits:
# Reset the develop branch
git checkout develop
git reset --hard upstream/develop
# Checkout a new branch
git checkout -b work2
# Now find the two commit IDs
git log work
# Cherry-pick those
git cherry-pick 28d3fc5
git cherry-pick a67b2c
# And reset your work branch to the new work
git checkout work
git reset --hard work2
git branch -d work2
(Note: cherry-pick now also supports commit ranges, so this can be further simplified.)
The rebase way
Usually git knows - when you call rebase - how to rebase as it has a common history. It gets only problematic if the history diverges as happened above.
In that case you will need to tell git to rebase which on what.
So when you normally use:
git fetch upstream
git rebase upstream/develop
You would now use with the new base:
git fetch upstream
git rebase --onto upstream/develop [old base] work
and thats it.
To get the old base just use the commit ID of the first commit that does not contain changes you made.
A real drupal.org contribution example
This is super helpful when having to juggle several branches for e.g. the Drupal 8 core queue that depend on each other.
Lets say you worked on issue-X and issue-Y and issue-Y is built on top of issue-X.
Now you work mostly on issue-Y, but now you work more on issue-X and for your personal merit decide to make the history a little nicer, so now issue-X has diverted.
But a simple:
git checkout issue-Y
git rebase --onto issue-X [old base of X in issue-Y] issue-Y
can again save the day. The same is sometimes needed when the branches are based of of different core versions.