3500

Does anybody know how to easily undo a git rebase?

The only way that comes to mind is to go at it manually:

  • git checkout the commit parent to both of the branches
  • then create a temp branch from there
  • cherry-pick all commits by hand
  • replace the branch in which I rebased by the manually-created branch

In my current situation this is gonna work because I can easily spot commits from both branches (one was my stuff, the other was my colleague's stuff).

However my approach strikes me as suboptimal and error-prone (let's say I had just rebased with 2 of my own branches).

Any ideas?

Clarification: I'm talking about a rebase during which a bunch of commits were replayed. Not only one.

webmat
  • 50,648
  • 12
  • 52
  • 59
  • 6
    ALso note that during a rebase you can exclude commits, or squash them; these changes are not revertable without either a pointer to the original nodes or sifting through the reflog, so the cherrypicking would not work. – ANeves thinks SE is evil Dec 21 '11 at 10:47

18 Answers18

4813

The easiest way would be to find the head commit of the branch as it was immediately before the rebase started in the reflog...

git reflog

and to reset the current branch to it (with the usual caveats about being absolutely sure before reseting with the --hard option).

Suppose the old commit was HEAD@{5} in the ref log:

git reset --hard HEAD@{5}

In Windows, you may need to quote the reference:

git reset --hard "HEAD@{5}"

You can check the history of the candidate old head by just doing a git log HEAD@{5} (Windows: git log "HEAD@{5}").

If you've not disabled per branch reflogs you should be able to simply do git reflog branchname@{1} as a rebase detaches the branch head before reattaching to the final head. I would double check this, though as I haven't verified this recently.

Per default, all reflogs are activated for non-bare repositories:

[core]
    logAllRefUpdates = true
Cristian Ciupitu
  • 18,164
  • 7
  • 46
  • 70
CB Bailey
  • 648,528
  • 94
  • 608
  • 638
  • 142
    Git reflog is awesome, just remember you can get better formatted output with `git log -g` (tip from Scott Chacon's http://progit.org/book). – karmi Jul 23 '10 at 10:14
  • Per Allan's answer below a `git rebase -i --abort` is needed as well. The above alone is not enough. – Hazok Jun 14 '11 at 22:36
  • 67
    @Zach: `git rebase --abort` (`-i` makes no sense with `--abort`) is for abandoning a rebase that hasn't been completed - either because there were conflicts or because it was interactive or both; it's not about undoing a successful rebase which is what the question is about. You would either use `rebase --abort` or `reset --hard` depending on which situation you were in. You shouldn't need to do both. – CB Bailey Jun 15 '11 at 20:40
  • Isn't http://stackoverflow.com/questions/134882/undoing-a-git-rebase/137363#137363 better (more flexible and structured, doesn't require you to go through old logs)? – ripper234 Aug 17 '11 at 13:00
  • 1
    @ripper234: It depends on what you want to do. If you want to simply go back to the exact state you were at before the rebase then `reset` is simplest. You can do another rebase to recreate your previous state but depending on the severity of the rebase you may have to re-resolve some conflicts so it will take some care to get back the exact tree as it was before the rebase. – CB Bailey Aug 17 '11 at 21:05
  • 359
    Just in case, make a backup first: `git tag BACKUP`. You can return to it if something goes wrong: `git reset --hard BACKUP` – kolypto Nov 05 '12 at 14:00
  • 15
    If you've made a lot of commits the HEAD@{#} you're looking for will be prefaced with `commit:` as opposed to `rebase:`. Sounds obvious but it confused me for a bit. – Warpling Aug 26 '13 at 20:43
  • 8
    Joining the party after an accidental rebase :D. Wouldn't a `git reset --hard ORIG_HEAD` do the trick as well immediately after the accidental rebase? – quaylar Sep 23 '13 at 08:37
  • `git reset --hard BACKUP` is the same version code-wise, but git-wise it's not exactly the same version as the original. e.g. the rebase details are still in the reflog. Probably doesn't matter though. – Snowcrash May 12 '15 at 19:47
  • 1
    It doesn't work me. I can restore the state, but if I run the same interactive rebase again, it only gives me `noop` instead of the list of commits since I diverged from upstream, that I initially got. How do I restore that? – CodeManX Sep 09 '15 at 02:25
  • Excellent tool, one more time, making `TAG BACKUP` before jumping between `HEADs` could eliminate amount of moves. – ar-g Nov 23 '15 at 14:16
  • @SidJ @JosiahSprague were you on Windows? I was, and Git kept throwing ``error: unknown switch `e'`` and spitting out the usage instructions. Until I quoted that damn thing. – ADTC Feb 11 '16 at 04:09
  • 2
    In order to do the rebase again, I needed to `git rerere forget [path_to_file(s)]` because git would keep replaying the same (wrong) merge resolutions. – Bojan Radivojevic Bomber Dec 22 '16 at 16:33
  • 2
    Reflog is great, but you can avoid the need to do a reflog by creating a local tag on HEAD prior to doing your rebase. If your rebase goes wrong, you can easily find your old HEAD without reflog just by using the tag. I consider locally tagging commits prior to rebasing to be a "best practice". Just make sure you don't push your tags. Of course using branchName{n} is also a great way to do this if you know what value of N to use. – DaBlick Oct 09 '17 at 13:23
  • 1
    Suggest you copy your entire git folder first (just to be safe), then at command line or powershell within the git folder do `git reflog` to find the HEAD@{n} commit you want to go back to, then do a `git reset --hard "HEAD@{5}"`. This saved me losing several days work. – Greg Trevellick Jan 16 '18 at 15:45
  • FWIW osx requires quotes too i.e. `git reset --hard "HEAD@{x}"`. – zyndor Mar 09 '18 at 22:34
  • How do you get rid of the HEAD detached from refs/heads/[BRANCH NAME] message? And does that message mean that it's not a "real" branch (if that makes any sense)? – slim May 08 '18 at 16:03
  • in my version of git, a `rebase --abort` after a successful `reset --hard` (to undo a successful rebase) undoes the reset, bringing me back to the state after the successful rebase. I had to manually remove the `.git/rebase-merge` file as suggested by `git rebase` – ealfonso Aug 29 '18 at 02:15
  • Thanks so much! I accidentally made a mistake because I had pushed my branch (since I thought I was done with it for a while) and then did a rebase. This helped me out a lot! – Joshua Bakker Nov 05 '19 at 14:03
  • 1
    have some more reputation :) – unclebob Dec 16 '19 at 09:14
  • May have just saved my life. Thanks so much! – Alec Feb 06 '20 at 19:25
  • 2
    Give this person a cookie! – dor May 13 '20 at 16:55
  • Thank you so much. as you said, in windows, you have to use a double quote. – Nithin K Joy Jul 10 '20 at 12:18
  • It helped me in recovering deleted files from rebase command for the new repository. Thanks. – wui Jul 11 '20 at 22:14
  • This saved my bacon. Thanks! – Ben Richards Sep 17 '20 at 19:20
  • I would donate you a Starbucks if I could – turtlepick Nov 06 '20 at 00:22
  • @karmi the progit link has an expired cert, it looks like this is hosted at git-scm.com now: https://git-scm.com/book/en/v2 – Xiao Dec 04 '20 at 02:48
  • my saver, many thanks – Ken Block May 12 '21 at 04:34
1635

Actually, rebase saves your starting point to ORIG_HEAD so this is usually as simple as:

git reset --hard ORIG_HEAD

However, the reset, rebase and merge all save your original HEAD pointer into ORIG_HEAD so, if you've done any of those commands since the rebase you're trying to undo then you'll have to use the reflog.

Pat Notz
  • 186,044
  • 29
  • 86
  • 92
  • This is exactly what I needed, I made the mistake of using rebase with git tfs. This got me back to a point where I could commit. – Amir Aug 18 '11 at 18:05
  • Didn't work for me. I borked a rebase. Did the `git reset` as you suggest, now `git log` shows some still-rebased commit messages and is missing others completely. – z5h Mar 28 '12 at 01:37
  • 40
    In case `ORIG_HEAD` is no longer useful, you can also use the `branchName@{n}` syntax, where `n` is the nth prior position of the branch pointer. So for example, if you rebase `featureA` branch onto your `master` branch, but you don't like the result of the rebase, then you can simply do `git reset --hard featureA@{1}` to reset the branch back to exactly where it was before you did the rebase. You can read more about the branch@{n} syntax at [the official Git docs for revisions](http://git-scm.com/docs/gitrevisions). –  May 24 '13 at 05:17
  • Also, here the the quote about `ORIG_HEAD` from [the official docs for rebase](http://git-scm.com/docs/git-rebase): "ORIG_HEAD is set to point at the tip of the branch before the reset." –  May 24 '13 at 05:19
  • 18
    This is the easiest. Follow it up with a `git rebase --abort` though. – Seph Sep 16 '15 at 19:24
  • As far as upvoting this, I think this may only work in the middle of a rebase. My interpretation of the question is that it wasn't necessarily restricted to that case. And even if that was the case, I would say `git rebase --abort` is better. – DaBlick Oct 09 '17 at 13:27
  • 1
    @DaBlick this worked fine for me after a completely successful rebase with no conflicts on `git 2.17.0`. – dvlsg Sep 27 '18 at 23:40
  • Unable to do via this method but yeah i got to know that this can also be used if you have not done any reset rebase or merge after the one rebase you want to undo. the HEAD@{n}method is working for me – Vipin Jain Dec 31 '18 at 18:02
  • 6
    And let me complement it: `git reset --hard ORIG_HEAD` can use repeatedly to roll back again and again. Say , If A---rebase to---B---rebase to---C, now I am at C, I can go back to A by using two times `git reset --hard ORIG_HEAD` – CalvinChe Jan 05 '19 at 07:52
  • 8
    @Seph Can you explain why you suggest following up with `git rebase --abort`? – UpTheCreek Jan 28 '19 at 08:50
  • Does this keep the latest changes? – alper Apr 26 '20 at 11:53
  • After said command I need to do git pull to get to original state – Gaurav Khare Jul 01 '20 at 13:53
434

Charles's answer works, but you may want to do this:

git rebase --abort

to clean up after the reset.

Otherwise, you may get the message “Interactive rebase already started”.

Borislav Ivanov
  • 3,299
  • 2
  • 26
  • 48
Allan
  • 4,558
  • 1
  • 13
  • 3
95

Resetting the branch to the dangling commit object of its old tip is of course the best solution, because it restores the previous state without expending any effort. But if you happen to have lost those commits (f.ex. because you garbage-collected your repository in the meantime, or this is a fresh clone), you can always rebase the branch again. The key to this is the --onto switch.

Let’s say you had a topic branch imaginatively called topic, that you branched off master when the tip of master was the 0deadbeef commit. At some point while on the topic branch, you did git rebase master. Now you want to undo this. Here’s how:

git rebase --onto 0deadbeef master topic

This will take all commits on topic that aren’t on master and replay them on top of 0deadbeef.

With --onto, you can rearrange your history into pretty much any shape whatsoever.

Have fun. :-)

Aristotle Pagaltzis
  • 101,052
  • 21
  • 94
  • 96
  • 3
    I think this is the best option because of its flexibility. I branched b1 off master, then rebased b1 into a new branch b2, then wanted to revert b1 to be based on master again. I just love git - thanks! – ripper234 Aug 17 '11 at 12:59
  • 2
    This is the best option here! It kept all changes I have on my current branch, and removed all the unwanted ones! – Alicia Tang Mar 15 '16 at 20:43
  • I'd say that with a combination of `--onto` and `-i` you can rearrange your history into pretty much any shape whatsoever. Use gitk (or gitx on mac) to see the shapes you make :-). – rjmunro Mar 27 '18 at 12:59
  • for some reason your answer made me realize I can do a git rebase -i commitish and then EDIT the commit I wasn't satisfied with :) – Devin Rhode Oct 18 '20 at 04:10
83

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
Nick Roz
  • 3,002
  • 2
  • 30
  • 49
Maksym
  • 3,604
  • 2
  • 23
  • 42
  • 1
    That is the correct answer for me. Rebase and commit prior to rebase had same commit ID, and going back to HEAD{1} just wouldn't revert rebase! – Bill Kotsias Aug 02 '17 at 05:34
73

I actually put a backup tag on the branch before I do any nontrivial operation (most rebases are trivial, but I'd do that if it looks anywhere complex).

Then, restoring is as easy as git reset --hard BACKUP.

Alex Gontmakher
  • 1,307
  • 11
  • 10
  • 2
    I do this too. It's also useful to git diff BACKUP..HEAD to make sure you changed what you meant to. – Paul Bone Apr 12 '11 at 07:43
  • 1
    This basic precaution saved my ass so many times... mistakes with rebasing are costly. – ANeves thinks SE is evil Dec 21 '11 at 10:49
  • 5
    I used to do that too, but since I got more comfortable with the reflog I no longer feel it's necessary. The reflog is essentially doing this on your behalf every time you change HEAD. – Pete Hodgson Aug 01 '12 at 19:44
  • 4
    Well, I prefer meaningful names, because searching for the right item in the reflog is sometimes not fun at all. – Alex Gontmakher Aug 02 '12 at 21:13
  • 12
    You actually don't even need to make a backup branch, you can simply use the `branchName@{n}` syntax, here `n` is the nth prior position of the branch pointer. So for example, if you rebase `featureA` branch onto your `master` branch, but you don't like the result of the rebase, then you can simply do `git reset --hard featureA@{1}` to reset the branch back to exactly where it was before you did the rebase. You can read more about the `branch@{n}` syntax at [the official Git docs for revisions](http://git-scm.com/docs/gitrevisions). –  May 24 '13 at 05:12
  • 2
    Actually, you don't even need to use the syntax above, according to [Pat Notz's answer](http://stackoverflow.com/a/692763/456814), the original `HEAD` of the branch is temporarily stored in `ORIG_HEAD`. But the technique of using a backup branch label works too, it's just more steps. –  May 24 '13 at 05:21
  • Using ORIG_HEAD works only as long as you don't manually invoke 'git reset' during an interactive rebase! Also, my current version of git (2.20.1) seems to modify ORIG_HEAD if you apply a fixup commit while rebasing. – devconsole Jan 07 '19 at 16:49
25

Using reflog didn't work for me.

What worked for me was similar to as described here. Open the file in .git/logs/refs named after the branch that was rebased and find the line that contains "rebase finsihed", something like:

5fce6b51 88552c8f Kris Leech <me@example.com> 1329744625 +0000  rebase finished: refs/heads/integrate onto 9e460878

Checkout the second commit listed on the line.

git checkout 88552c8f

Once confirmed this contained my lost changes I branched and let out a sigh of relief.

git log
git checkout -b lost_changes
Kris
  • 16,882
  • 6
  • 79
  • 99
  • or this: http://stackoverflow.com/questions/1108853/recovering-added-file-after-doing-git-reset-hard-head – cregox Jun 20 '12 at 18:52
  • 4
    Whoa -- from that link, "There is one caveat: I lost the history of the branch but in this case it didn’t really matter. I was just happy to have retrieved my changes." ? – ruffin Aug 30 '12 at 14:13
16

For multiple commits, remember that any commit references all the history leading up to that commit. So in Charles' answer, read "the old commit" as "the newest of the old commits". If you reset to that commit, then all the history leading up to that commit will reappear. This should do what you want.

Greg Hewgill
  • 828,234
  • 170
  • 1,097
  • 1,237
16
git reset --hard origin/{branchName}

is the correct solution to reset all your local changes done by rebase.

Damodar P
  • 325
  • 3
  • 3
11

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)
$
Matheus Felipe
  • 2,033
  • 18
  • 24
11

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 branch
  • git checkout origin/your-branch # checkout to latest state that is known to origin
  • git checkout -b your-branch
  • check 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
  • push your changes. Please aware that two local branches are associated with remote/your-branch and you should push only your-branch
Sergey P. aka azure
  • 2,857
  • 22
  • 21
8

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
Andrew
  • 13,934
  • 8
  • 78
  • 93
4

Let's say I rebase master to my feature branch and I get 30 new commits which break something. I've found that often it's easiest to just remove the bad commits.

git rebase -i HEAD~31

Interactive rebase for the last 31 commits (it doesn't hurt if you pick way too many).

Simply take the commits that you want to get rid of and mark them with "d" instead of "pick". Now the commits are deleted effectively undoing the rebase (if you remove only the commits you just got when rebasing).

Hardev
  • 7,841
  • 2
  • 13
  • 17
  • That's not going to work. You've effectively just deleted all your work. Rebase rewrites commits, so in most cases you're going to nuke both your original commits and rewritten commits by doing this. – DylanYoung Jul 19 '20 at 21:25
3

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.

devconsole
  • 7,216
  • 1
  • 31
  • 42
3

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.

Caveats

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]

DylanYoung
  • 1,835
  • 22
  • 27
2

What I usually do is git reset #commit_hash

to the last commit where I think rebase had no effect.

then git pull

Now your branch should match exactly like master and rebased commits should not be in it.

Now one can just cherry-pick the commits on this branch.

mrigendra
  • 1,135
  • 2
  • 10
  • 32
1

I tried all suggestions with reset and reflog without any success. Restoring local history of IntelliJ resolved the problem of lost files

  • Thanks! Never used the local history before, but this turned out to be the easiest option for me to recover some accidentally rebased code. Very nice but somewhat hidden feature. – Magnus W May 15 '20 at 12:04
-4

If you mess something up within a git rebase, e.g. git rebase --abort, while you have uncommitted files, they will be lost and git reflog will not help. This happened to me and you will need to think outside the box here. If you are lucky like me and use IntelliJ Webstorm then you can right-click->local history and can revert to a previous state of your file/folders no matter what mistakes you have done with versioning software. It is always good to have another failsafe running.

Ed Avis
  • 1,220
  • 14
  • 35
Steve K
  • 12,452
  • 10
  • 74
  • 126
  • 6
    `git rebase --abort` aborts an active rebase, it doesn't _undo_ a rebase. Also, using two VCS's at the same time is a bad idea. Its a nice feature in Jetbrains software but you shouldn't use both. It's better to just learn Git, particularly when answering questions on Stack Overflow that are about Git. – dudewad May 15 '17 at 17:56