[git] Git: How to rebase to a specific commit?

I'd like to rebase to a specific commit, not to a HEAD of the other branch:

A --- B --- C          master
 \
  \-- D                topic

to

A --- B --- C          master
       \
        \-- D          topic

instead of

A --- B --- C          master
             \
              \-- D    topic

How can I achieve that?

This question is related to git version-control rebase git-rebase

The answer is


You can avoid using the --onto parameter by making a temp branch on the commit you like and then use rebase in its simple form:

git branch temp master^
git checkout topic
git rebase temp
git branch -d temp

You can even take a direct approach:

git checkout topic
git rebase <commitB>

I've used a mixture of solutions described above:

$ git branch temp <specific sha1>
$ git rebase --onto temp master topic
$ git branch -d temp

I found it much easier to read and understand. The accepted solution lead me to a merge conflict (too lazy to fix by hand):

$ git rebase temp
First, rewinding head to replay your work on top of it...
Applying: <git comment>
Using index info to reconstruct a base tree...
M       pom.xml
.git/rebase-apply/patch:10: trailing whitespace.
    <some code>
.git/rebase-apply/patch:17: trailing whitespace.
        <some other code>
warning: 2 lines add whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging pom.xml
CONFLICT (content): Merge conflict in pom.xml
error: Failed to merge in the changes.
Patch failed at 0001 <git comment>
The copy of the patch that failed is found in: .git/rebase-apply/patch

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".

There is another way of doing it or if you wish to move back to more than just one commit.

Here is a an example to move back to n number of commits:

git branch topic master~n

For the sake of this question, this can also be done:

git branch topic master~1

The command works perfectly on git version 2.7.4. Haven't tested it on any other version.


Use the "onto" option:

git rebase --onto master^ D^ D

A simpler solution is git rebase <SHA1 of B> topic. This works irrespective of where your HEAD is.

We can confirm this behaviour from git rebase doc

<upstream> Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured upstream for the current branch.


You might be thinking what will happen if I mention SHA1 of topic too in the above command ?

git rebase <SHA1 of B> <SHA1 of topic>

This will also work but rebase then won't make Topic point to new branch so created and HEAD will be in detached state. So from here you have to manually delete old Topic and create a new branch reference on new branch created by rebase.


The comment by jsz above saved me tons of pain, so here's a step-by-step recipie based on it that I've been using to rebase/move any commit on top of any other commit:

  1. Find a previous branching point of the branch to be rebased (moved) - call it old parent. In the example above that's A
  2. Find commit on top of which you want to move the branch to - call it new parent. In the exampe that's B
  3. You need to be on your branch (the one you move):
  4. Apply your rebase: git rebase --onto <new parent> <old parent>

In the example above that's as simple as:

   git checkout topic
   git rebase --onto B A

Topic Solution

The correct command to answer the posted question could be any of the following (assuming branch topic is already checked out):

git rebase --onto B master
git rebase --onto master~1 master
git rebase --onto B A
git rebase --onto B C
git rebase --onto B

If topic is not checked out, you simply append topic to the command (except the last one) like so:

git rebase --onto B master topic

Alternatively, check out the branch first with:

git checkout topic

Rebase Any String of Commits to a Target Commit

The basic form of the command we need, cribbed from the documentation, is:

git rebase --onto <Target> [<Upstream> [<Branch>]]

<Branch> is optional and all it does is checks out the branch specified before executing the rest of the command. If you've already checked out the branch you want to rebase, then you don't need this. Note that you must have specified <Upstream> in order to specify <Branch> or git will think you are specifying <Upstream>.

<Target> is the commit we will attach our string of commits to. When providing a branch name, you are simply specifying the head commit of that branch. <Target> can be any commit that won't be contained in the string of commits being moved. For example:

A --- B --- C --- D         master
      \
       \-- X --- Y --- Z    feature

To move the entire feature branch, you can not select X, Y, Z, or feature as the <Target> since those all are commits inside the group being moved.

<Upstream> is special because it can mean two different things. If it is a commit that is an ancestor of the checked out branch, then it serves as the cut point. In the example I provided, this would be anything that isn't C, D, or master. All commits after <Upstream> until the head of the checked out branch are the ones that will be moved.

However, if <Upstream> is not an ancestor, then git backs up the chain from the specified commit until if finds a common ancestor with the checked out branch (and aborts if it can't find one). In our case, an <Upstream> of B, C, D, or master will all result in commit B serving as the cut point. <Upstream> is itself an optional command and if it is not specified, then git looks at the parent of the checked out branch which is the equivalent of entering master.

Now that git has selected the commits it will cut and move, it applies them in order to <Target>, skipping any that are already applied to target.

Interesting Examples and Results

Using this starting point:

A --- B --- C --- D --- E         master
            \
             \-- X --- Y --- Z    feature
  • git rebase --onto D A feature
    Will apply commits B, C, X, Y, Z to commit D and end up skipping B and C because they already have been applied.

  • git rebase --onto C X feature
    Will apply commits Y and Z to commit C, effectively deleting commit X


Since rebasing is so fundamental, here's an expansion of Nestor Milyaev's answer. Combining jsz's and Simon South's comments from Adam Dymitruk's answer yields this command which works on the topic branch regardless of whether it branches from the master branch's commit A or C:

git checkout topic
git rebase --onto <commit-B> <pre-rebase-A-or-post-rebase-C-or-base-branch-name>

Note that the last argument is required (otherwise it rewinds your branch to commit B).

Examples:

# if topic branches from master commit A:
git checkout topic
git rebase --onto <commit-B> <commit-A>
# if topic branches from master commit C:
git checkout topic
git rebase --onto <commit-B> <commit-C>
# regardless of whether topic branches from master commit A or C:
git checkout topic
git rebase --onto <commit-B> master

So the last command is the one that I typically use.


Adding to the answers using --onto:

I never learned it by heart, so I wrote this little helper script:
Git: Rebase a (sub)branch from one base to another, leaving the other base's commits.

Usage:

moveBranch <branch> from <previous-base> to <new-base>

In short:

git rebase --onto "$3" "$2" "$1"

Besides that, one more solution usable for similar purposes, is cherry-picking a streak of commits:

git co <new-base> 
git cherry-pick <previous-base>..<branch>
git branch -f branch

Which has more less the same effect. Note that this syntax SKIPS the commit at <previous-branch> itself, so it cherry-picks the next and the following up to, including, the commit at <branch>.


Examples related to git

Does the target directory for a git clone have to match the repo name? Git fatal: protocol 'https' is not supported Git is not working after macOS Update (xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools) git clone: Authentication failed for <URL> destination path already exists and is not an empty directory SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443 GitLab remote: HTTP Basic: Access denied and fatal Authentication How can I switch to another branch in git? VS 2017 Git Local Commit DB.lock error on every commit How to remove an unpushed outgoing commit in Visual Studio?

Examples related to version-control

How can I switch to another branch in git? Do I commit the package-lock.json file created by npm 5? Project vs Repository in GitHub Remove a modified file from pull request Git push: "fatal 'origin' does not appear to be a git repository - fatal Could not read from remote repository." Git: How to squash all commits on branch git: updates were rejected because the remote contains work that you do not have locally Sourcetree - undo unpushed commits Cannot checkout, file is unmerged Git diff between current branch and master but not including unmerged master commits

Examples related to rebase

Git refusing to merge unrelated histories on rebase Git push rejected "non-fast-forward" What's the difference between 'git merge' and 'git rebase'? How can I combine two commits into one commit? Remove folder and its contents from git/GitHub's history git cherry-pick says "...38c74d is a merge but no -m option was given" What does 'git remote add upstream' help achieve? How to get "their" changes in the middle of conflicting Git rebase? Git: How to rebase to a specific commit? How do you rebase the current branch's changes on top of changes being merged in?

Examples related to git-rebase

rebase in progress. Cannot commit. How to proceed or stop (abort)? git remove merge commit from history Choose Git merge strategy for specific files ("ours", "mine", "theirs") What's the difference between 'git merge' and 'git rebase'? Your branch is ahead of 'origin/master' by 3 commits Rebase feature branch onto another feature branch git rebase merge conflict Remove folder and its contents from git/GitHub's history How to get "their" changes in the middle of conflicting Git rebase? How to rebase local branch onto remote master