Does anybody know how to easily undo a git rebase?
The only way that comes to mind is to go at it manually:
If you are on a branch you can use:
git reset --hard @{1}
There is not only a reference log for HEAD (obtained by git reflog
), there are also reflogs for each branch (obtained by git reflog <branch>
). So, if you are on master
then git reflog master
will list all changes to that branch. You can refer to that changes by master@{1}
, master@{2}
, etc.
git rebase
will usually change HEAD multiple times but the current branch will be updated only once.
@{1}
is simply a shortcut for the current branch, so it's equal to master@{1}
if you are on master
.
git reset --hard ORIG_HEAD
will not work if you used git reset
during an interactive rebase
.
If you don't want to do a hard reset...
You can checkout the commit from the reflog, and then save it as a new branch:
git reflog
Find the commit just before you started rebasing. You may need to scroll further down to find it (press Enter or PageDown). Take note of the HEAD number and replace 57:
git checkout HEAD@{57}
Review the branch/commits, and if it's correct then create a new branch using this HEAD:
git checkout -b new_branch_name
If you successfully rebased against remote branch and can not git rebase --abort
you still can do some tricks to save your work and don't have forced pushes.
Suppose your current branch that was rebased by mistake is called your-branch
and is tracking origin/your-branch
git branch -m your-branch-rebased
# rename current branchgit checkout origin/your-branch
# checkout to latest state that is known to origingit checkout -b your-branch
git log your-branch-rebased
, compare to git log your-branch
and define commits that are missing from your-branch
git cherry-pick COMMIT_HASH
for every commit in your-branch-rebased
remote/your-branch
and you should push only your-branch
In case you had pushed your branch to remote repository (usually it's origin) and then you've done a succesfull rebase (without merge) (git rebase --abort
gives "No rebase in progress") you can easily reset branch using
command:
git reset --hard origin/{branchName}
Example:
$ ~/work/projects/{ProjectName} $ git status
On branch {branchName}
Your branch is ahead of 'origin/{branchName}' by 135 commits.
(use "git push" to publish your local commits)
nothing to commit, working directory clean
$ ~/work/projects/{ProjectName} $ git reset --hard origin/{branchName}
HEAD is now at 6df5719 "Commit message".
$ ~/work/projects/{ProjectName} $ git status
On branch {branchName}
Your branch is up-to-date with 'origin/{branchName}.
nothing to commit, working directory clean
Following the solution of @Allan and @Zearin, I wish I could simply do a comment though but I don't enough reputation, so I have used the following command:
Instead of doing git rebase -i --abort
(note the -i) I had to simply do git rebase --abort
(without the -i).
Using both -i
and --abort
at the same time causes Git to show me a list of usage/options.
So my previous and current branch status with this solution is:
matbhz@myPc /my/project/environment (branch-123|REBASE-i)
$ git rebase --abort
matbhz@myPc /my/project/environment (branch-123)
$
It annoys me to no end that none of these answers is fully automatic, despite the fact that it should be automatable (at least mostly). I created a set of aliases to try to remedy this:
# Useful commands
#################
# Undo the last rebase
undo-rebase = "! f() { : git reset ; PREV_COMMIT=`git x-rev-before-rebase` && git reset --merge \"$PREV_COMMIT\" \"$@\";}; f"
# See what changed since the last rebase
rdiff = "!f() { : git diff ; git diff `git x-rev-before-rebase` "$@";}; f"
# Helpers
########
# Get the revision before the last rebase started
x-rev-before-rebase = !git reflog --skip=1 -1 \"`git x-start-of-rebase`\" --format=\"%gD\"
# Get the revision that started the rebase
x-start-of-rebase = reflog --grep-reflog '^rebase (start)' -1 --format="%gD"
You should be able to tweak this to allow going back an arbitrary number of rebases pretty easily (juggling the args is the trickiest part), which can be useful if you do a number of rebases in quick succession and mess something up along the way.
It will get confused if any commit messages begin with "rebase (start)" (please don't do this). You could make the regex more resilient to improve the situation by matching something like this for your regex:
--grep-reflog "^rebase (start): checkout "
WARNING: not tested (regex may need adjustments)
The reason I haven't done this is because I'm not 100% that a rebase always begins with a checkout. Can anyone confirm this?
[If you're curious about the null (:
) commands at the beginning of the function, that's a way of setting up bash completions for the aliases]