I'd like to move the last several commits I've committed to master to a new branch and take master back to before those commits were made. Unfortunately, my Git-fu isn't strong enough yet, any help?

I.e. How can I go from this

master A - B - C - D - E

to this?

newbranch     C - D - E
             /
master A - B 
share|improve this question
61  
Note: I asked the opposite question here – Benjol Dec 16 '10 at 8:56
2  
possible duplicate of Move commits from master onto a branch using git – Chris Moschini Sep 24 '13 at 20:09
4  
@ChrisMoschini, that question is newer (2010) than this one (2009). – lobner Aug 20 '14 at 10:15
2  
@lobner Yes - I pick the oldest question in most circumstances, but in some cases like this one I pick the one that is best asked, answered, or both. I found the top answer in the linked question to be the most concise, and the question a bit clearer. – Chris Moschini Aug 22 '14 at 16:08
58  
Time for my bimonthly visit to this question. – Éric Araujo Nov 30 '15 at 17:52
up vote 3144 down vote accepted
+50

Moving to a new branch

Unless there are other circumstances involved, this can be easily done by branching and rolling back.

git branch newbranch
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.*1
git checkout newbranch

But do make sure how many commits to go back. Alternatively, you can instead of HEAD~3, simply provide the hash of the commit (or the reference like origin/master) you want to "revert back to" on the master (/current) branch, e.g:

git reset --hard a1b2c3d4

*1 You will only be "losing" commits from the master branch, but don't worry, you'll have those commits in newbranch!

Moving to an existing branch

WARNING The method above works because you are creating a new branch with the first command: git branch newbranch. If you want to use an existing branch you need to merge your changes into the existing branch before executing git reset --hard HEAD~3. If you don't merge your changes first, they will be lost. So, if you are working with an existing branch it will look like this:

git checkout existingbranch
git merge master
git checkout master
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch
share|improve this answer
161  
And in particular, don't try to go back further than the point where you last pushed commits to another repository from which somebody else might have pulled. – Greg Hewgill Oct 27 '09 at 3:23
74  
Wondering if you can explain WHY this works. To me you're creating a new branch, removing 3 commits from the old branch you are still on, and then checking out the branch you made. So how do the commits you removed magically show up in the new branch? – Jonathan Dumaine Aug 3 '10 at 18:28
96  
@Jonathan Dumaine: Because I created the new branch before removing the commits from the old branch. They're still there in the new branch. – sykora Aug 4 '10 at 8:28
59  
branches in git are just markers which point to commits in history, there is nothing being cloned, created or deleted (except the markers) – knittl Aug 16 '10 at 11:32
125  
Also note: Don't do this with uncommitted changes in your working copy! This just bit me! :( – Adam Tuttle Oct 25 '11 at 3:59

For those wondering why it works (as I was at first):

You want to go back to C, and move D and E to the new branch. Here's what it looks like at first:

A-B-C-D-E (HEAD)
        ↑
      master

After git branch newBranch:

    newBranch
        ↓
A-B-C-D-E (HEAD)
        ↑
      master

After git reset --hard HEAD~2:

    newBranch
        ↓
A-B-C-D-E (HEAD)
    ↑
  master

Since a branch is just a pointer, master pointed to the last commit. When you made newBranch, you simply made a new pointer to the last commit. Then using git reset you moved the master pointer back two commits. But since you didn't move newBranch, it still points to the commit it originally did.

share|improve this answer
24  
You do need --hard, because otherwise git leaves the changes from the reset commits in the working directory (or at least did for me). – Andrew Aug 22 '11 at 22:05
11  
I also needed to do a git push origin master --force for the change to show up in main repository. – Dženan Nov 26 '14 at 19:20
2  
This answer causes commits to be lost: next time you git rebase, the 3 commits will be silently discarded from newbranch. See my answer for details and safer alternatives. – John Mellor Apr 6 at 22:47
2  
@John, that's nonsense. Rebasing without knowing what you're doing causes commits to be lost. If you lost commits, I'm sorry for you, but this answer didn't lose your commits. Note that origin/master doesn't appear in the above diagram. If you pushed to origin/master and then made the changes above, sure, things would go funny. But that's a "Doctor, it hurts when I do this" kind of problem. And it's out of scope for what the original question asked. I suggest you write your own question to explore your scenario instead of hijacking this one. – Kyralessa Apr 7 at 3:20
1  
@John, in your answer, you said "Don't do this! git branch -t newbranch". Go back and read the answers again. Nobody suggested doing that. – Kyralessa Apr 7 at 15:02

In General...

The method exposed by sykora is the best option in this case. But sometimes is not the easiest and it's not a general method. For a general method use git cherry-pick:

To achieve what OP wants, its a 2-step process:

Step 1 - Note which commits from master you want on a newbranch

Execute

git checkout master
git log

Note the hashes of (say 3) commits you want on newbranch. Here I shall use:
C commit: 9aa1233
D commit: 453ac3d
E commit: 612ecb3

Note: You can use the first seven characters or the whole commit hash

Step 2 - Put them on the newbranch

git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233

OR (on Git 1.7.2+, use ranges)

git checkout newbranch
git cherry-pick 612ecb3..9aa1233

git cherry-pick applies those three commits to newbranch.

share|improve this answer
8  
Wasn't the OP trying to move from master to newbranch? If you cherry-pick whilst on master, you would be adding to the master branch -- adding commits that it already had, in fact. And it doesn't move back either branch head to B. Or is there something subtle and cool I'm not getting? – RaveTheTadpole Jul 6 '12 at 2:48
5  
This works very well if you accidentally commit the wrong, non-master branch, when you should have created a new feature branch. – BigC Feb 27 '14 at 16:47
2  
+1 for a useful approach in some situations. This is good if you only want to pull your own commits (which are interspersed with others) into a new branch. – Tyler V. Oct 1 '14 at 17:11
3  
It's better answer. This way you can move commits to any branch. – skywinder Nov 5 '14 at 8:32
4  
Is the order of cherry picking important? – kon psych Apr 23 '15 at 22:04

Yet another way to do this, using just 2 commands. Also keeps your current working tree intact.

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 

Being able to push to . is a nice trick to know.

Late Edit: Now that I know about git branch -f the right way to do it is:

git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit 

Which is the same, but less "magic"

share|improve this answer
3  
That's... actually really clever. – Qix Mar 26 '14 at 16:24
1  
Current directory. I guess this would work only if you are in a top directory. – aragaer Mar 28 '14 at 5:35
1  
The local push is grin-inducing, but on reflection, how is it different to git branch -f here? – jthill Aug 5 '14 at 0:15
2  
@GerardSexton . is current director. git can push to REMOTES or GIT URLs. path to local directory is supported Git URLs syntax. See the GIT URLS section in git help clone. – weakish Nov 25 '14 at 10:24
14  
I don't know why this is not rated higher. Dead simple, and without the small but potential danger of git reset --hard. – Godsmith Feb 6 '15 at 14:56

Most previous answers are dangerously wrong!

Do NOT do this:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

As the next time you run git rebase (or git pull --rebase) those 3 commits would be silently discarded from newbranch! (see explanation below)

Instead do this:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • First it discards the 3 most recent commits (--keep is like --hard, but safer, as fails rather than throw away uncommitted changes).
  • Then it forks off newbranch.
  • Then it cherry-picks those 3 commits back onto newbranch. Since they're no longer referenced by a branch, it does that by using git's reflog: HEAD@{2} is the commit that HEAD used to refer to 2 operations ago, i.e. before we 1. checked out newbranch and 2. used git reset to discard the 3 commits.

Warning: the reflog is enabled by default, but if you've manually disabled it (e.g. by using a "bare" git repository), you won't be able to get the 3 commits back after running git reset --keep HEAD~3.

An alternative that doesn't rely on the reflog is:

# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3

(if you prefer you can write @{-1} - the previously checked out branch - instead of oldbranch).


Technical explanation

Why would git rebase discard the 3 commits after the first example? It's because git rebase with no arguments enables the --fork-point option by default, which uses the local reflog to try to be robust against the upstream branch being force-pushed.

Suppose you branched off origin/master when it contained commits M1, M2, M3, then made three commits yourself:

M1--M2--M3  <-- origin/master
         \
          T1--T2--T3  <-- topic

but then someone rewrites history by force-pushing origin/master to remove M2:

M1--M3'  <-- origin/master
 \
  M2--M3--T1--T2--T3  <-- topic

Using your local reflog, git rebase can see that you forked from an earlier incarnation of the origin/master branch, and hence that the M2 and M3 commits are not really part of your topic branch. Hence it reasonably assumes that since M2 was removed from the upstream branch, you no longer want it in your topic branch either once the topic branch is rebased:

M1--M3'  <-- origin/master
     \
      T1'--T2'--T3'  <-- topic (rebased)

This behavior makes sense, and is generally the right thing to do when rebasing.

So the reason that the following commands fail:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

is because they leave the reflog in the wrong state. Git sees newbranch as having forked off the upstream branch at a revision that includes the 3 commits, then the reset --hard rewrites the upstream's history to remove the commits, and so next time you run git rebase it discards them like any other commit that has been removed from the upstream.

But in this particular case we want those 3 commits to be considered as part of the topic branch. To achieve that, we need to fork off the upstream at the earlier revision that doesn't include the 3 commits. That's what my suggested solutions do, hence they both leave the reflog in the correct state.

For more details, see the definition of --fork-point in the git rebase and git merge-base docs.

share|improve this answer
    
I think I'd be more satisfied with this answer if you could demonstrate what the "wrong" state of reflog would be as opposed to the right state in this circumstance. – Makoto Apr 6 at 22:49
    
In your scenario, if I keep M2 and M3 in my topic branch and then try to merge my topic branch back into master, then don't I reintroduce the edits someone else tried to delete (i.e. M2)? It would seem that this is undesired behavior, no? To me it makes sense that if someone is going to re-write history, you will want your topic branch to track the new history so you don't un-re-write history when you merge. – Gordon Bean Apr 6 at 23:27
2  
This answer says "Do NOT do this!" above something that no one suggested doing. – Kyralessa Apr 16 at 21:41
1  
@Kyralessa, the -t you are referring to in git branch happens implicitly if you have git config --global branch.autosetuprebase always set. Even if you don't, I already explained to you that the same problem occurs if you setup tracking after performing these commands, as the OP likely intends to do given their question. – John Mellor Sep 16 at 2:36
1  
@RockLee, yes, the general the way to fix such situations is to create a fresh branch (newbranch2) from a safe starting point then cherry-pick all the commits you want to keep (from badnewbranch to newbranch2). Cherry-picking will give the commits new hashes, so you'll be able to safely rebase newbranch2 (and can now delete badnewbranch). – John Mellor Sep 16 at 2:36

This doesn't "move" them in the technical sense but it has the same effect:

A--B--C  (branch-foo)
 \    ^-- I wanted them here!
  \
   D--E--F--G  (branch-bar)
      ^--^--^-- Opps wrong branch!

While on branch-bar:
$ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range)

A--B--C  (branch-foo)
 \
  \
   D-(E--F--G) detached
   ^-- (branch-bar)

Switch to branch-foo
$ git cherry-pick E..G

A--B--C--E'--F'--G' (branch-foo)
 \   E--F--G detached (This can be ignored)
  \ /
   D--H--I (branch-bar)

Now you won't need to worry about the detached branch because it is basically
like they are in the trash can waiting for the day it gets garbage collected.
Eventually some time in the far future it will look like:

A--B--C--E'--F'--G'--L--M--N--... (branch-foo)
 \
  \
   D--H--I--J--K--.... (branch-bar)
share|improve this answer
1  
Can't you use rebase for the same thing? – Bergi Jun 29 '15 at 17:37
    
Yes you could alternatively use rebase on the detached branch in the scenario above. – Sukima Jun 30 '15 at 1:32

Had just this situation:

Branch one: A B C D E F     J   L M  
                       \ (Merge)
Branch two:             G I   K     N

I performed:

git branch newbranch 
git reset --hard HEAD~8 
git checkout newbranch

I expected that commit I would be the HEAD, but commit L is it now...

To be sure to land on the right spot in the history its easier to work with the hash of the commit

git branch newbranch 
git reset --hard #########
git checkout newbranch
share|improve this answer

To do this without rewriting history (i.e. if you've already pushed the commits):

git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>

Both branches can then be pushed without force!

share|improve this answer
    
But then you have to deal with the revert scenario, which, depending on your circumstance, can be a lot trickier. – Makoto Apr 6 at 22:43
    
Could you elaborate on that @Makoto? – teh_senaus Apr 7 at 16:02
    
If you revert a commit on the branch, Git will still see those commits as have taken place, so in order to undo that, you have to revert the revert. This burns quite a few people, especially when they revert a merge and try to merge the branch back, only to find that Git believes that it's already merged that branch in (which is entirely true). – Makoto Apr 7 at 16:15
    
That's why I cherry-pick the commits at the end, onto a new branch. That way git sees them as new commits, which solves your issue. – teh_senaus Apr 7 at 17:18
2  
I don't follow your argument - the point of this answer is that you're not changing history, simply adding new commits (which effectively undo the redo the changes). These new commits can be pushed and merged as normal. – teh_senaus Apr 8 at 10:33

protected by Elenasys Jan 14 '14 at 0:24

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.