Rebase only part of a branch

I've got two branches (and master). Branch 2 is based on Branch 1 is based on master. I've submitted Branch 1 for review, it had some changes, I rebased some of those changes into history and merged the result into master.

Now I need to rebase Branch 2 on top of master to prepare it for review/merge.

The problem is that Branch 2 still contains the original commits of Branch 1, which don't exist anymore, so git gets confused. I tried rebase -i to drop the original commits of Branch 1, but the commits of Branch 2 don't base on top of master-before-branch-1.

What I need to do is take branch 2, drop some commits, and rebase just the remaining commits on top of master in a single operation. But I only know how to do these two operations in two distinct steps.

How can I rebase part of my branch onto another branch, dropping all commits that are not in common ancestry, except the ones I specify (e.g. from HEAD~2 up)?

Here's the current state:

master                     new branch 1
- - - - - - - - - - - | - - - - - - - - -
    \
     \   branch 1
      \ _ _ _ _ _ _ _
                     \
                      \     branch 2
                       \ _ _ _ _ _ _ _

What I want to end up with:

master            new branch 1    
- - - - - - - | - - - - - - - - - -
                                   \
                                    \
                                     \
                                      \    branch 2
                                       - - - - - - - - - 

Solution 1:

The actual command would be:

git rebase --onto newbranch1 branch1 branch2

That will replay on top of new_branch1 all commits after branch1 up to branch2 HEAD.

As Joshua Goldberg puts it in the comments:

 git rebase --onto <place-to-put-it> <last-change-that-should-NOT-move> <change to move>

As Denis Sivtsov illustrates in the comments:

If you need only replay the last commit only from branch, in this case work:

git rebase --onto newbranch1 HEAD~1

Solution 2:

The solution is considerably simpler than I expected. It turns out that you can supply -i to a much larger variety of rebase commands (I thought it was only for rebasing a branch to itself for changing history). So I simply ran git rebase -i master and dropped those extra commits.

Solution 3:

git rebase --onto master HEAD~2
  • master - the branch you're rebasing onto
  • 2 - the last n commits from the current branch you need rebased

Source

Solution 4:

I find your ASCII graph a bit ambiguous, as branches don't really represent a range of commits - a branch points to a specific commit. I take it you mean something like

H (new-branch-1)
G
| F (HEAD -> branch-2)
| E
| D (branch-1)
| C
|/
B (master)
A

in which case the goal, and result of VonC's answer git rebase --onto new-branch-1 branch-1 branch-2, is

F' (HEAD -> branch-2)
E'
H (new-branch-1)
G
| D (branch-1)
| C
|/
B (master)
A

But your answer git rebase -i master doesn't make sense. Surely you would mean git rebase -i new-branch-1 and in the editor write

drop C
drop D
pick E
pick F

which would achieve the goal above.

(Your answer would result in):

F' (HEAD -> branch-2)
E'
| H (new-branch-1)
| G
|/ 
| D (branch-1)
| C
|/
B (master)
A

Solution 5:

As answered, you can do this using --onto.

I find the git rebase --onto syntax quite confusing. So I created this script for rebasing "nested" branches: Github Gist

In your example, you would call:

moveBranch newBranch2 from newBranch1 to master

#!/bin/bash

## Places a branch to a new base. 
## Useful when splitting a long branch to multiple pull requests.
##
##   ---+--------master 
##       \
##         --- A ---- B
## 
## git-moveBranch.sh B from A to master
##
##   ---+-------- master ---- B
##       \
##         --- A 


function printUsageAndExit() {
    echo "Usage: moveBranch <branch> from <previous-base> to <new-base>";
    exit 1;
}

if [ 5 != $# ] ; then printUsageAndExit; fi
if [ "$2" != "from" ] ; then printUsageAndExit; fi
if [ "$4" != "to" ] ; then printUsageAndExit; fi

WHAT="$1"
FROM="$3"
ONTO="$5"

echo "Running:   git rebase —-onto=\"$ONTO\" \"$FROM\" \"$WHAT\""
# git rebase —-onto <place-to-put-it> <last-change-that-should-NOT-move> <change to move>
git rebase --onto "$ONTO" "$FROM" "$WHAT"