How do I move my recent commits on master
to a new branch, and reset master to before those commits were made? e.g. From this:
master A - B - C - D - E
To this:
newbranch C - D - E
/
master A - B
How do I move my recent commits on master
to a new branch, and reset master to before those commits were made? e.g. From this:
master A - B - C - D - E
To this:
newbranch C - D - E
/
master A - B
WARNING: You need to store uncommitted edits to your stash before doing this, using git stash
. Once complete, you can retrieve the stashed uncommitted edits with git stash pop
. git reset hard command will remove all changes!
If you want to move your commits to an existing branch, it will look like this:
git checkout existingbranch
git merge branchToMoveCommitFrom
git checkout branchToMoveCommitFrom
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch
WARNING: This method works because you are creating a new branch with the first command: git branch newbranch
. If you want to move commits to an existing branch you need to merge your changes into the existing branch before executing git reset --hard HEAD~3
(see Moving to an existing branch above). If you don't merge your changes first, they will be lost.
Unless there are other circumstances involved, this can be easily done by branching and rolling back.
# Note: Any changes not committed will be lost.
git branch newbranch # Create a new branch, saving the desired commits
git checkout master # checkout master, this is the place you want to go back
git reset --hard HEAD~3 # Move master back by 3 commits (Make sure you know how many commits you need to go back)
git checkout newbranch # Go to the new branch that still has the desired commits
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
Note: You will only be "losing" commits from the master branch, but don't worry, you'll have those commits in newbranch! An easy way to check that, after completing the 4 step sequence of commands above, is by looking at git log -n4
which will show the history of newbranch actually retained the 3 commits (and the reason is that newbranch was created at the time those changes were already commited on master!). They have only been removed from master, as git reset
only affected the branch that was checked out at the time of its execution, i.e. master (see git reset description: Reset current HEAD to the specified state). git status
however will not show any checkouts on the newbranch, which might be surprising at first but that is actually expected.
Lastly, you may need to force push your latest changes to main repo:
git push origin master --force
WARNING: With Git version 2.0 and later, if you later git rebase
the new branch upon the original (master
) branch, you may need an explicit --no-fork-point
option during the rebase to avoid losing the carried-over commits. Having branch.autosetuprebase always
set makes this more likely. See John Mellor's answer for details.
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.
git push origin master --force
for the change to show up in main repository.
git rebase
, the 3 commits will be silently discarded from newbranch
. See my answer for details and safer alternatives.
Commented
Apr 6, 2016 at 22:47
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.
Commented
Apr 7, 2016 at 3:20
git branch -t newbranch
". Go back and read the answers again. Nobody suggested doing that.
Commented
Apr 7, 2016 at 15:02
git rebase
with no arguments enables the --fork-point
option, which can cause commits to be lost. In my opinion this is a defect in git rebase
. It has nothing to do with my answer above, though. There are many circumstances in which git rebase
with no arguments can cause trouble. The moral of the story (unfortunately) is not to call git rebase
without passing in a branch, even if that branch is already the upstream. Git's usability has improved quite a lot in the past eight years or so, but this is one area wherre it could use more work.
Commented
Jun 1, 2018 at 11:54
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:
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
newbranch
git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233
git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233
git cherry-pick applies those three commits to newbranch.
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}
--keep
is like --hard
, but safer, as fails rather than throw away uncommitted changes).newbranch
.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
).
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.
master
. So no, they are not dangerously wrong.
-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.
Commented
Sep 16, 2016 at 2:36
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 branch -f master HEAD~3 # make master point to some older commit
Old version - before I learned about git branch -f
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.
git branch -f
here?
.
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
.
Here's a far simpler solution for commits to the wrong branch. Starting on branch master
that has three mistaken commits:
git reset HEAD~3
git stash
git checkout newbranch
git stash pop
master
master
, yet leaves all working files intactmaster
working tree exactly equal to the HEAD~3 statenewbranch
You can now use git add
and git commit
as you normally would. All new commits will be added to newbranch
.
The OP stated the goal was to "take master back to before those commits were made" without losing changes and this solution does that.
I do this at least once a week when I accidentally make new commits to master
instead of develop
. Usually I have only one commit to rollback in which case using git reset HEAD^
on line 1 is a simpler way to rollback just one commit.
Don't do this if you pushed master's changes upstream
Someone else may have pulled those changes. If you are only rewriting your local master there's no impact when it's pushed upstream, but pushing a rewritten history to collaborators can cause headaches.
git add
and git commit
commands that I used so all I had to do was hit up arrow and enter a few times and boom! Everything was back, but on the right branch now.
Commented
Sep 19, 2019 at 14:57
git checkout -b newbranch
Commented
Aug 19, 2021 at 17:43
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)
rebase
on the detached branch in the scenario above.
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!
How can I go from this
A - B - C - D - E
|
master
to this?
A - B - C - D - E
| |
master newbranch
With two commands
giving
A - B - C - D - E
|
newbranch
and
giving
A - B - C - D - E
| |
master newbranch
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
If you just need to move all your unpushed commits to a new branch, then you just need to,
create a new branch from the current one :git branch new-branch-name
push your new branch: git push origin new-branch-name
revert your old(current) branch to the last pushed/stable state: git reset --hard origin/old-branch-name
Some people also have other upstreams
rather than origin
,
they should use appropriate upstream
TLDR
git checkout branch_to_remove_commits
git reset --hard ${hash_of_new_tip}
git checkout -b branch_to_store_commits
# Move commits (single hash, list of hashes or range ffaa..ffoo)
git cherry-pick ${commit_hash}
git push --set-upstream origin branch_to_store_commits
# Switch back to last branch
git checkout -
git push -f
For me
git log --pretty=oneline -n ${NUMBER}
works best to identify the commit hashes in question.
You can do this is just 3 simple step that i used.
1) make new branch where you want to commit you recent update.
git branch <branch name>
2) Find Recent Commit Id for commit on new branch.
git log
3) Copy that commit id note that Most Recent commit list take place on top. so you can find your commit. you also find this via message.
git cherry-pick d34bcef232f6c...
you can also provide some rang of commit id.
git cherry-pick d34bcef...86d2aec
Now your job done. If you picked correct id and correct branch then you will success. So before do this be careful. else another problem can occur.
Now you can push your code
git push
I was surprised that nobody recommended this way:
git checkout master
git checkout <commit hash from which you want to split>
git checkout -b new_branch
git rebase master
git checkout master
git reset --hard <commit hash you splitted>
to explain:
1) Create a new branch, which moves all your changes to new_branch.
git checkout -b new_branch
2) Then go back to old branch.
git checkout master
3) Do git rebase
git rebase -i <short-hash-of-B-commit>
4) Then the opened editor contains last 3 commit information.
...
pick <C's hash> C
pick <D's hash> D
pick <E's hash> E
...
5) Change pick
to drop
in all those 3 commits. Then save and close the editor.
...
drop <C's hash> C
drop <D's hash> D
drop <E's hash> E
...
6) Now last 3 commits are removed from current branch (master
). Now push the branch forcefully, with +
sign before branch name.
git push origin +master
Using Emacs' git porcelain Magit, you can do this simply by hitting b s
(magit-branch-spinoff). You'll be asked to enter a name for your new branch and once you hit enter, voila.
From the Magit documentation:
This command creates and checks out a new branch starting at and tracking the current branch. That branch in turn is reset to the last commit it shares with its upstream. If the current branch has no upstream or no unpushed commits, then the new branch is created anyway and the previously current branch is not touched.
This is useful to create a feature branch after work has already began on the old branch (likely but not necessarily "master").
Most of the solutions here count the amount of commits you'd like to go back. I think this is an error prone methodology. Counting would require recounting.
You can simply pass the commit hash of the commit you want to be at HEAD or in other words, the commit you'd like to be the last commit via:
(Notice see commit hash)
To avoid this:
1) git checkout master
2) git branch <feature branch> master
3) git reset --hard <commit hash>
4) git push -f origin master
You can put the files affected by the 3 commits back to Staged and then re-commit the code to a new branch. Assuming you're on master
and want to move exactly 3 commits to new-branch
:
git reset --soft HEAD~3
git checkout -b new-branch
git commit -m "message"
The code is now committed with a new hash into new-branch
, avoiding any future rebase-issues highlighted by some other answers. To avoid any merge conflicts, push master
:
git checkout master
git push -ff
This solution also provides the flexibility to re-create your commits completely. In the example above, I replace 3 commits with 1 commit. However, you can of course unstage your staged files by replacing the commit
-step above with:
git reset # unstage all files
git add path/file_1
git commit -m "first-commit"
git add path/file_2
git commit -m "second-commit"
...
I got to move 7 commits from one old-branch to a new-branch.
git checkout old-branch # in the example, master
git reset --hard h4sh # h4sh is the hash for the commit
git checkout -b new-branch
git push origin new-branch
After that, both branches were related to the 7 commits I have done. After git checkout new-branch
, I was getting fine git log
and git status
, but, when accessing the old-branch (git checkout old-branch
), I'd got the message "git is behind by 7 commits and can be fast-forwarded". What worked for me to erase this message was the followind:
git checkout old-branch
git status
> git is behind by 7 commits and can be fast-forwarded
git push origin old-branch -f
After that step, the last 7 commits was referenced only for the new-branch and the previous ones were referenced as old-branch and new-branch in the Bitbucket tree.
If you are a UI person like me and you are using Visual Studio. Then you can do the following: In my case, I want to take the latest commit to another branch.
So all commit changes will appear in the Git Changes pane.
Now, stash your changes
From "Git Changes" double click on your latest Stash.
"Stash details" pane will be opened. Click on "Pop", then resolve conflicts (if exists).
I had a similar problem and solved like this, considering "E" as the hash of the last commit and "B" the hash of the commit that I want the master to point to:
git branch new_branch E
git branch --force master B
What worked for me was this:
git checkout -b newbranch
git push -u origin newbranch
git checkout oldbranch
git revert --no-commit <hash-to-keep>..HEAD
git commit -m Reverting the previous three commits
git push
The "-u origin newbranch" was needed on "git push" because there was no new commit. My changes were on a new branch, where they belonged. Everyone who had unpushed commits just needed to pull, and they'd see no conflicts. I'd tried to use a couple of the other solutions here, but my remote would not allow me to rewrite history.
Taking some ideas from other posts, avoiding anything to do with reset, and being ultra paranoid, my solution is:
I'm not proud, but I kept my data ;)