1

I just by mistake used git reset --hard. Now I want to undo this command and bring my changes back. How can I do this?

I used git reflog command and see following.

6eea708 HEAD@{0}: commit (initial): Initial commit

How can I bring my changes back?

dimid
  • 7,285
  • 1
  • 46
  • 85
Om3ga
  • 30,465
  • 43
  • 141
  • 221
  • 1
    http://stackoverflow.com/questions/5473/how-can-i-undo-git-reset-hard-head1 – dimid May 07 '16 at 21:06
  • Possible duplicate of [How to move HEAD back to a previous location?](http://stackoverflow.com/questions/34519665/how-to-move-head-back-to-a-previous-location) – CodeWizard May 07 '16 at 22:25

3 Answers3

4

You are in luck because of this bit you put in a comment:

The thing is that I did not commit those changes. I first did git add . and then ...

First, let's note that git add . stages work-tree contents. That is, suppose that ., your current directory, contains files foo.txt and bar.xml that are both in the current (HEAD) commit,1 and you have modified one or both of these. The add step copies the new contents into the underlying repository, and then makes the index/staging-area refer to the new contents.

This answer is in the linked question, so this is technically a duplicate, but I think it's worth expanding on a bit, especially since that one answer is buried relatively far down, below the more common case of having used git reset to discard a commit.

... I used git reset --hard

Note that this form of reset does not discard any commits, but it does clear out work-tree files that have been staged. That is, it not only unstages files—i.e., undoes git add and/or git rm—just like git reset --mixed, it also overwrites the work-tree version of the file with the committed version.2

This makes it difficult to get the staged version back, but not impossible.

The key here is that the file's contents are in the repository, stored under their "hash name"—one of those big ugly SHA-1 hashes like fa49b077972391ad58037050f2a75f74e3671e92.

If you run:

$ git fsck

Git will find any hash-names that have nothing referring to them:

Checking object directories: 100% (256/256), done.
dangling blob fa49b077972391ad58037050f2a75f74e3671e92

The index/staging-area used to refer to this file, but git reset --hard cleared that out. So now nothing refers to this fa49b... file.

You can extract the file with git plumbing commands, but if there are a bunch of these and you want to refer to them more easily, you can add the --lost-found option to git fsck, then look in the lost-and-found directory, where these "dangling blob" files have been extracted:

$ git fsck --lost-found
Checking object directories: 100% (256/256), done.
dangling blob fa49b077972391ad58037050f2a75f74e3671e92
$ cd .git/lost-found/other
$ ls
fa49b077972391ad58037050f2a75f74e3671e92
$ cat fa49b077972391ad58037050f2a75f74e3671e92 
new file
$ 

(I only added, then reset away, the one file; if there were many files you would have to sift through them all to find any good ones.)

When you are done rooting through the lost-found bin contents, it is probably a good idea to remove them (cd .git/lost-found && rm -rf *), although they are largely harmless to leave there—they just occupy space. You can run git fsck --lost-found again later to repopulate it (with any then-dangling commits and blobs).

Note that this method will fail if you wait too long—these dangling blob objects are not semi-protected (by reflog entries) the way discarded commits are. By default, though, leftover objects are not pruned for 14 days anyway. This means you have two weeks, instead of about a month as for commits.

There is one more trap here, not that it will affect people in practice. The file I reset away had one line reading new file (plus a newline). Suppose that, for whatever reason, some other file, in some existing (not discarded) commit, has the exact same contents. When I git added the file, Git would have computed this same "true name" hash—the true name of any file containing nothing but the one line new file is always fa49b077972391ad58037050f2a75f74e3671e92, in any repository anywhere on the planet3—and would have just referred to the existing copy of that file. Resetting it away and running git fsck, Git will not notice a dangling blob, because at this point it is not dangling: some other commit refers to file fa49b077972391ad58037050f2a75f74e3671e92, perhaps under the same path name, or perhaps under some other path.


1It also adds files that are not in the HEAD commit, but I did not want to get into all the side issues of worrying about .gitignore directives. See footnote 2. :-)

2If the file is newly added, git reset --hard still removes the file from the work-tree. This is where the .gitignore complication comes in: if the file was ignored, it wasn't added, but then git reset --hard did not remove it either. If it was added, it's in the repository and we are back to the "file existed in HEAD" case. So this all comes out in the wash anyway.

3This suggests a method of attack on Git repositories, consisting of using Google as a massive indexing engine: search blob hashes until you find some hash H that repeats, but refers to two different contents C1 and C2. Then, knowing that someone will want to use repository R, which has neither version of that blob, to hold content C1 in the future, make a commit in R holding content C2 instead. No new commit can now contain content C1 since Git will hash it to H and decide that it is already in the repository.

Practical, this attack is not. :-)

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
1

You can just cherry-pick the one commit you want

git cherry-pick <commit_id>
yelsayed
  • 5,236
  • 3
  • 27
  • 38
  • The thing is that I did not commit those changes. I first did 'git add .' and then I used `git reset --hard` – Om3ga May 07 '16 at 20:38
  • 1
    I'm afraid there's no way to recover the changes unless they were once part of a commit. I thought you said you got the commit id from `reflog` but weren't sure how to recover it. – yelsayed May 07 '16 at 20:42
1

Based on your git reflog output, you can just do

git reset HEAD@{0}

or

git reset 6eea708
Pawan
  • 1,480
  • 2
  • 18
  • 27