Git: How to rebase to a specific commit?

310,791

Solution 1

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

Solution 2

You can even take a direct approach:

git checkout topic
git rebase <commitB>

Solution 3

Use the "onto" option:

git rebase --onto master^ D^ D

OR

git rebase --onto <commitB> <commitA> <commitD>

The 3 last arguments mean:

  • destination (new-parent, here it's commitB),
  • start-after (current-parent, parent of first commit to be moved),
  • and end-inclusive (last commit to be moved).

Solution 4

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

Solution 5

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

Share:
310,791

Related videos on Youtube

febot
Author by

febot

I am. (See my LinkedIn profile. Open to job offers for USA, Germany, Austria, Switzerland). 2012 update: I'm a company-man, a team-player, a paradigm-shifter and a core-competancy-synergizer. :) PS: I worked for Red Hat / JBoss, so my answers may be biased. Currently work for Swiss Re.

Updated on July 08, 2022

Comments

  • febot
    febot almost 2 years

    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?

    • Peter-Paul van Gemerden
      Peter-Paul van Gemerden over 12 years
      Have you tried doing git checkout B before running git rebase?
    • febot
      febot over 12 years
      Nope, should that help? I guess only the references of the rebase command are what matters.
  • febot
    febot over 12 years
    D and D^ would be hash of the last and next-to-last commit of "topic"?
  • febot
    febot over 12 years
    I like this RISC-like approach more :) Will try. Thanks.
  • jsz
    jsz over 12 years
    The syntax is like git rebase --onto <new-parent> <old-parent>. See Setting git parent pointer to a different parent. In your case, <new-parent> is B, and <old-parent> is A.
  • Adam Dymitruk
    Adam Dymitruk over 11 years
    I always use the 3 arguments: desitnation, start and end of commits to rebase.
  • Alois Mahdal
    Alois Mahdal almost 10 years
    I wonder why it does not work for me, in a slightly different scenario. I want group skip the pep8 and be based on master. git rebase temp (when on group) gives up with "Current branch groups is up to date.".
  • Dan Lenski
    Dan Lenski over 9 years
    For me, this doesn't actually do what's intended. As far as I can tell, it tries to rebase onto the "last common ancestor" of topic and commitB.
  • r0hitsharma
    r0hitsharma over 9 years
    @DanLenski, that isn't how rebase works.Quoting the docs, It works by going to the common ancestor of the two branches (the one you’re on and the one you’re rebasing onto), getting the diff introduced by each commit of the branch you’re on, saving those diffs to temporary files, resetting the current branch to the same commit as the branch you are rebasing onto, and finally applying each change in turn. I tried it again now and seemed to work just fine.
  • Admin
    Admin over 8 years
    This worked for me: git rebase --onto <commit-ID> master
  • gaborous
    gaborous about 8 years
    @jsz's comment is correct, contrary to Simon South's comment, it's the other way around: git rebase --onto master <commit-ID-of-old-parent> and for OP git rebase --onto B A.
  • Martin Konicek
    Martin Konicek over 7 years
    Super easy and works! I now have: commitB_from_master->topicCommit1->topicCommit2.
  • Mihai
    Mihai almost 6 years
    What happens if D has 2 parents as if it were a merge commit?
  • mirzmaster
    mirzmaster almost 6 years
    This solution won't work for the scenario where the topic has already been rebased onto master, but you want to rebase it on an ancestor to master. In that case you must use git rebase --onto <target> <from> <to> so that you can specify the <from> commit.
  • ash
    ash almost 6 years
    This is by far the easiest option if you want to rebase onto the same commit the branch is based on.
  • Oliver
    Oliver over 5 years
    same here, several files had conflicts when I used the 2 most popular answers (ie by r0hitsharma and Dymitruk)
  • NetherGranite
    NetherGranite over 5 years
    Did you misinterpret this as a question about branching? This actually a question about rebasing.
  • Zack Morris
    Zack Morris over 5 years
    This should be the correct answer. Except that I use git rebase --onto B master, see my answer for a more thorough explanation.
  • ROMANIA_engineer
    ROMANIA_engineer about 5 years
    I seems to work, but the GitLab says something else. If I had 10 commits behind, 5 commits ahead, my expectation was to have 8 commits behind, 5 commits ahead after getting 2 commits. But instead of this it adds more commits to those 5.
  • ROMANIA_engineer
    ROMANIA_engineer about 5 years
    It didn't work for me. Before this, GitLab said "n commits ahead". And now, it says "m commits ahead" where m > n.
  • ROMANIA_engineer
    ROMANIA_engineer about 5 years
    It didn't work fine for me. I chose 2 consecutive commits (the last one from master that was in the current branch and the first one from master that was not in the current branch). I started with 100 behind - 10 ahead and instead of having 99 behind - 10 ahead, now I have 105 behind - 13 ahead.
  • Nestor Milyaev
    Nestor Milyaev about 5 years
    Sorry to hear it didn't work. Sounds like your branches diverged quite a bit - I'd suggest squashing first before trying to rebase branches with this many differences.
  • Ashish-BeJovial
    Ashish-BeJovial over 4 years
    But this approach is rebasing from the head, i mean D has been rebased with C commit of master branch.
  • Egor Okhterov
    Egor Okhterov over 4 years
    What to do whis 'Current branch branch-name is up to date.'?
  • bkis
    bkis over 3 years
    @ROMANIA_engineer Give it some time. Platforms like GitLab and GitHub don't process changes in a repo's history right away. On GH for example it may take like 2 days or so. I once had the same problem.
  • Serhat
    Serhat over 2 years
    --onto was definitely the key. I was trying git rebase 0320dd but it was not working. after inserting git rebase --onto 0320dd I got my changes too!
  • Top-Master
    Top-Master about 2 years
    Why would one want to avoid --into option?