90

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
                                       - - - - - - - - - 
Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 4
    An ASCII graph representing the state of your repo would help. At first glance, I'd say you're looking for [`git rebase --onto`](https://git-scm.com/docs/git-rebase). See if this helps: http://stackoverflow.com/questions/28715619/how-can-i-rebase-part-of-a-branch-to-the-master-branch/28715930#28715930 – jub0bs Jan 21 '16 at 14:23
  • Better! See also the top of http://stackoverflow.com/questions/25488138/move-initial-commits-off-master-to-another-branch-in-git/25490288#25490288 – jub0bs Jan 21 '16 at 14:31
  • git rebase --onto nearly worked for me. I did `git rebase --onto master --root HEAD~1`, but for some reason it picked three commits to bring with me instead of only `HEAD~1` and upwards. Furthermore, instead of rebasing my branch, I'm now in detached HEAD state. – Puppy Jan 21 '16 at 14:34

5 Answers5

115

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
Édouard Lopez
  • 40,270
  • 28
  • 126
  • 178
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    It sure does (was about to post the same myself). Incidentally, `git help rebase` covers this case. – Tom Fenech Jan 21 '16 at 15:20
  • @TomFenech even better than `git help rebase`: http://stackoverflow.com/a/2369516/6309 (my old 2010 answer) ;) – VonC Jan 21 '16 at 15:24
  • 7
    The tricky thing for me was understanding that "upstream" in the help means the change to hold still. `git rebase —onto ` – Joshua Goldberg Jan 22 '19 at 22:41
  • @JoshuaGoldberg Yes: that is what I meant by "all commits *after* branch1". I have included your command in the answer for more visibility. – VonC Jan 22 '19 at 22:45
  • 1
    If you need only replay the last commit only from branch, in this case work git rebase --onto newbranch1 HEAD~1 – Denis Sivtsov Nov 26 '20 at 00:06
  • @DenisSivtsov True. I have included your comment in the answer for more visibility. – VonC Nov 26 '20 at 06:46
38

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.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 5
    To clarify what @Puppy did was do an interactive rebase from branch 2 onto new branch 1 (`git rebase -i new_branch_1` while on branch 2) and removed all the branch 1 commits using the interactive console so only branch 2's commits were played. – theannouncer Jul 26 '17 at 21:19
  • 1
    Yes, doing `git checkout branch-to-move && git rebase -i new-parent-branch` and keeping only lines that match the patches you want to keep works fine. Just be really careful not to drop any patch you don't want to lose. – Mikko Rantalainen Mar 18 '20 at 11:19
17
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

Nelu
  • 16,644
  • 10
  • 80
  • 88
2

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
binaryfunt
  • 6,401
  • 5
  • 37
  • 59
1

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"
Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277