Undo git reset --hard with uncommitted files in the staging area

66,869

Solution 1

You should be able to recover any files back that you added to the index (e.g, as in your situation, with git add .) although it might be a bit of work. In order to add a file to the index, git adds it to the object database, which means it can be recovered so long as garbage collection hasn't happened yet. There's an example of how to do this given in Jakub Narębski's answer here:

However, I tried that out on a test repository, and there were a couple of problems - --cached should be --cache, and I found that it didn't actually create the .git/lost-found directory. However, the following steps worked for me:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)")

That should output all objects in the object database that aren't reachable by any ref, in the index, or via the reflog. The output will look something like this:

unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03

... and for each of those blobs, you can do:

git show 907b308

To output the contents of the file.


Too much output?

Update in response to sehe's comment below:

If you find that you have many commits and trees listed in the output from that command, you may want to remove from the output any objects which are referenced from unreferenced commits. (Typically you can get back to these commits via the reflog anyway - we're just interested in objects that have been added to the index but can never be found via a commit.)

First, save the output of the command, with:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > all

Now the object names of those unreachable commits can be found with:

egrep commit all | cut -d ' ' -f 3

So you can find just the trees and objects that have been added to the index, but not committed at any point, with:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") \
  $(egrep commit all | cut -d ' ' -f 3)

That enormously cuts down the number of objects you'll have to consider.


Update: Philip Oakley below suggests another way of cutting down the number of objects to consider, which is to just consider the most recently modified files under .git/objects. You can find these with:

find .git/objects/ -type f -printf '%TY-%Tm-%Td %TT %p\n' | sort

(I found that find invocation here.) The end of that list might look like:

2011-08-22 11:43:43.0234896770 .git/objects/b2/1700b09c0bc0fc848f67dd751a9e4ea5b4133b
2011-09-13 07:36:37.5868133260 .git/objects/de/629830603289ef159268f443da79968360913a

In which case you can see those objects with:

git show b21700b09c0bc0fc848f67dd751a9e4ea5b4133b
git show de629830603289ef159268f443da79968360913a

(Note that you have to remove the / at the end of the path to get the object name.)

Solution 2

I just did a git reset --hard and lost one commit. But I knew the commit hash, so I was able to do git cherry-pick COMMIT_HASH to restore it.

I did this within a few minutes of losing the commit, so it may work for some of you.

Solution 3

Thanks to Mark Longair I got my stuff back!

First I saved all the hashes into a file:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > allhashes

next I put them all (removing the 'unreachable blob' thing) in a list and put the data all in new files...you have to pick your files and rename them again which you need...but I only needed a few files..hope this helps someone...

commits = ["c2520e04839c05505ef17f985a49ffd42809f",
    "41901be74651829d97f29934f190055ae4e93",
    "50f078c937f07b508a1a73d3566a822927a57",
    "51077d43a3ed6333c8a3616412c9b3b0fb6d4",
    "56e290dc0aaa20e64702357b340d397213cb",
    "5b731d988cfb24500842ec5df84d3e1950c87",
    "9c438e09cf759bf84e109a2f0c18520",
    ...
    ]

from subprocess import call
filename = "file"
i = 1
for c in commits:
    f = open(filename + str(i),"wb")
    call(["git", "show", c],stdout=f)
    i+=1

Solution 4

@Ajedi32's solution in the comments worked for me in exactly this situation.

git reset --hard @{1}

Note that all these solutions rely on there being no git gc, and some of them might cause one, so I'd zip up the contents of your .git directory before trying anything so that you have a snapshot to go back to if one doesn't work for you.

Solution 5

Ran into the same issue, but had not added the changes to the index. So all commands above didn't bring me back the desired changes.

After all the above elaborate answers, this is a naive hint, but may be it'll save someone who didn't thought about it first, as I did.

In despair, I tried to press CTRL-Z in my editor (LightTable), once in every open tab — this luckily recovered the file in that tab, to its latest state before the git reset --hard. HTH.

Share:
66,869
eistrati
Author by

eistrati

Proud Father. Lucky Husband. Co-Founder @AdTechMediaPro. AWS Tech Partner @MitocGroup. Former @AWScloud and @HearstCorp.

Updated on September 01, 2020

Comments

  • eistrati
    eistrati over 3 years

    I am trying to recover my work. I stupidly did git reset --hard, but before that I've done only get add . and didn't do git commit. Please help! Here is my log:

    MacBookPro:api user$ git status
    # On branch master
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    
    #   modified:   .gitignore
    ...
    
    
    MacBookPro:api user$ git reset --hard
    HEAD is now at ff546fa added new strucuture for api
    

    Is it possible to undo git reset --hard in this situation?

  • sehe
    sehe over 12 years
    before I posted my answer I considered including exactly these steps. However, on trying this with one of my local repos, I got hundreds of unreachables, and I couldn't come up an adequate approach to, say, sort them by date submitted. Now that would be awesome
  • Mark Longair
    Mark Longair over 12 years
    @sehe: I've updated my answer suggesting how to cut down the number of objects that are output. On an existing repository I tried this on, the output was then quite manageable.
  • sehe
    sehe over 12 years
    +1 for some potentially useful refinements to my own usual procedures there!
  • Philip Oakley
    Philip Oakley over 12 years
    Isn't it possible to have a look at the object time stamps for the most recent objects that are later than your last commit? or am I missing something.
  • Mark Longair
    Mark Longair over 12 years
    @Philip Oakley: thanks for that suggestion, I've added the suggestion to find the most recently modified objects in the object database.
  • bowsersenior
    bowsersenior over 11 years
    Dude, that is awesome! That saved my @$$ just now. And I learned some git science. Better watch what you add to the index...
  • Dace
    Dace over 10 years
    Thank you thank you thank you Managed to recover a days work thanks to this answer
  • Javier López
    Javier López over 10 years
    Probably is worth mentioning you can see those hashes using git reflog, e.g. git reset --hard -> git reflog (looking at the HEAD@{1} hash) and finally git cherry-pick COMMIT_HASH
  • Lotus
    Lotus almost 10 years
    This is absolutely the easiest and fastest way to fix this. Awesome!
  • Technotronic
    Technotronic almost 10 years
    great answer! short and simple!
  • Ain Tohvri
    Ain Tohvri almost 10 years
    Guys reading this, beware that this only works for a single commit hash, if there was more than a single commit, it won't solve the problem at once!
  • Senica Gonzalez
    Senica Gonzalez almost 10 years
    Almost lost 5 days of work. Using git reflog I was able to do a hard reset back to one of the hashes from there and get everything back!
  • Steven Pribilinskiy
    Steven Pribilinskiy over 9 years
    @PhilipOakley I've got timestamps with %tT instead of %TT
  • akimsko
    akimsko over 9 years
    You can actually reset "forward". So if you have reset passed multiple commits, doing another 'git reset --hard <commit>' should restore the whole commit chain up to that point. (given that they're not GC'ed yet)
  • Shalom Sam
    Shalom Sam over 9 years
    not to steal anyones lime light here. But just as a note since I just ran into the same problem and thought I lost all my work. For those using an IDE, the IDEs usually have a one click recovery solution. In case of PHPstorm I just had to right click the directory and select show local history and then revert to the last valid state.
  • Eric Olson
    Eric Olson over 9 years
    Yes! This is exactly what I needed. I like the Python script for recreating all the files too. The other answers made me nervous about losing my data with garbage collection, so dumping the files is a win for me :)
  • James Wilkins
    James Wilkins over 9 years
    Thanks, this really helped. ;)
  • Ajedi32
    Ajedi32 over 9 years
    The one-liner for recovering commits lost with git reset --hard (assuming you haven't done anything else after that command) is git reset --hard @{1}
  • dojuba
    dojuba almost 9 years
    If you did the changes on the same day and had some sort of keyword to search through all blobs: in the ./git/objects folder, I did find -maxdepth 2 -type f -mtime -1 | sed -r 's/[\.\/]//g' | xargs -n 1 -I {} -i bash -c 'git show {} | grep -q YOUR_KEYWORD && echo {}'
  • Alexar
    Alexar about 8 years
    I wrote this script based on this answer. Works out of the box: github.com/pendashteh/git-recover-index
  • Sunil kumar
    Sunil kumar almost 8 years
    Great approach @MarkLongair, i got my backup in blob and converted to .txt file.
  • user1071847
    user1071847 almost 8 years
    My version of find didn't support that syntax (and I was too lazy to figure out how to modify it), but ls -ltr ~/.git/objects/*/* worked fine for me.
  • Kondal Kolipaka
    Kondal Kolipaka almost 8 years
    Awesome! you saved my time.
  • Vivek
    Vivek over 6 years
    Exactly same situation, missed to add in index and did hard reset and none of the solution worked all above. This one was a SMART move , thanks I did Ctrl+Z and saved my day. My editor : SublimeText. One up from my side !
  • Matija Nalis
    Matija Nalis over 6 years
    I find it easier to automate it like this: mkdir lost; git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") | grep -Po '\s\S{40}$' | xargs -i echo "git show {} > lost/{}.blob" | sh. Files will end up in lost/*.blob
  • alpha_989
    alpha_989 about 6 years
    I had a number of uncommited files. Then I commited 1 file, and did a git reset --hard, which messed my repo in any unknown fashion. @Duncan, what does the @{1} do? and which comment are you referring to? Is it reseting a git reset?
  • Duncan McGregor
    Duncan McGregor about 6 years
    I think that the @{1} is a relative reference to the last but one commit, but I'm no expert, just reporting what worked for me
  • Danijel
    Danijel over 5 years
    As suggested by @PhilipOakley, I find that looking into .git/objects and searching for recently modified files is by far the best way to recover your work. This means that git fsck didn't work for me, however, find .git/objects/ ... worked very nicely.
  • Neil
    Neil about 4 years
    Still saving asses 19 years later. I really thought I had messed up big time now. Managed to recover everything.
  • halogenr
    halogenr about 3 years
    Great ! This saved my day ;)