在暂存区有未提交的文件时如何撤销 Git reset --hard 命令

131

我试图找回我的工作。我愚蠢地使用了git reset --hard,但在那之前我只进行了git add .操作,没有进行git commit。请帮忙!下面是我的日志:

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

在这种情况下,是否有可能撤销git reset --hard的操作?


@MarkLongair 很棒啊!你刚刚帮我解决了问题!我写了一个Python脚本来创建所有输出的文件!我会将这个脚本作为答案添加上去。 - Boy
6
不是“愚蠢地”做了,而是“天真地”做了...因为我刚刚也做了同样的事情! - Rosdi Kasim
仍然可能很愚蠢;-) - Duncan McGregor
这是一篇关于如何撤销 Git 强制重置的优秀文章,需要进行一些手动操作。 - Jan Swart
1
@MarkLongair find .git/objects/ -type f -printf '%TY-%Tm-%Td %TT %p\n' | sort对我有用。日期也会显示,从结尾开始检查 blobs。 - ZAky
我如何在“git show 907b308…”之后恢复文本?这会给我一个差异,而不是文件。 - Danijel
13个回答

203

如果你将文件添加到索引中(例如像你这种情况使用git add .),你应该能够恢复任何已添加的文件,尽管可能需要一些工作。为了将文件添加到索引中,Git将其添加到对象数据库中,这意味着只要垃圾回收还没有发生,它就可以被恢复。在这里Jakub Narębski's answer给出了如何执行此操作的示例:

但是,我在一个测试仓库上尝试了这一方法,发现有几个问题 - --cached 应该是 --cache,并且我发现它实际上并没有创建 .git/lost-found 目录。不过,以下步骤对我有效:

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

这应该输出对象数据库中所有不被任何引用、索引或引用日志所引用的对象。 输出结果会类似于下面:

unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03

...对于这些“Blob”中的每一个,您都可以执行以下操作:

git show 907b308

输出文件内容。


输出过多?

回应下面 sehe 的评论:

如果在该命令的输出中有许多提交和树列表,您可能希望从输出中删除从未引用的提交所引用的任何对象。(通常可以通过 reflog 返回这些提交 - 我们只关心已添加到索引但永远无法通过提交找到的对象。)

首先,使用以下命令保存命令的输出:

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

现在可以通过以下方式找到那些无法访问的提交的对象名称:

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

你可以使用以下代码来找到已添加到索引但尚未提交的树和对象:

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

那会大大减少你需要考虑的对象数量。


更新:Philip Oakley 在下面提出了另一种减少需要考虑对象数量的方法,就是只考虑 .git/objects 目录下最近修改的文件。你可以使用以下命令查找它们:

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

(我发现在这里找到了find调用的这里。)列表的末尾可能如下所示:

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

在哪种情况下,您可以使用以下方式查看这些对象:

git show b21700b09c0bc0fc848f67dd751a9e4ea5b4133b
git show de629830603289ef159268f443da79968360913a

(请注意,您需要在路径结尾处移除/以获取对象名称。)


@PhilipOakley 我的时间戳是用%tT而不是%TT - Steven Pribilinskiy
6
不要在这里窃取任何人的风头。但是请注意,因为我刚遇到同样的问题,以为我失去了所有的工作。对于使用 IDE 的人来说,IDE 通常有一键恢复解决方案。例如,在 PHPstorm 中,我只需要右键单击目录,选择显示本地历史记录,然后还原到最后一个有效状态。 - Shalom Sam
如果您在同一天进行了更改并且有某种关键字可搜索所有 blob:在 ./git/objects 文件夹中,我执行了 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 {}' - dojuba
很棒的方法,@MarkLongair。我将我的备份转换成了blob并转换为.txt文件。 - Sunil kumar
我的版本的find不支持那种语法(而且我懒得想办法修改它),但是ls -ltr ~/.git/objects/*/* 对我很有用。 - user1071847
正如@PhilipOakley所建议的那样,我发现查看.git/objects并搜索最近修改的文件是恢复您的工作的最佳方法。这意味着git fsck对我没有用,不过,find .git/objects/ ...非常好用。 - Danijel

64

我刚刚执行了 git reset --hard 命令,导致丢失了一个提交。但是我知道该提交的哈希值,所以我使用了 git cherry-pick COMMIT_HASH 命令来还原它。

在我失去该提交的几分钟内执行此操作,可能对你们中的一些人有用。


5
谢谢,谢谢,谢谢。由于这个答案,我成功地找回了一天的工作。 - Dace
24
可能值得一提的是,您可以使用 git reflog 命令查看这些哈希值,例如 git reset --hard -> git reflog(查看 HEAD@{1} 的哈希值),最后执行 git cherry-pick COMMIT_HASH - Javier López
2
读者们请注意,这仅适用于单个提交哈希值,如果有多个提交,则无法一次性解决问题! - Ain Tohvri
4
你实际上可以重置 "forward"。所以,如果你已经重置了多个提交,执行另一个 'git reset --hard <commit>' 应该会将整个提交链恢复到该点(前提是它们还没有被垃圾回收)。 - akimsko
7
假设您在执行 git reset --hard 命令后没有进行其他任何操作,恢复因此丢失的提交可以使用以下命令:git reset --hard @{1} - Ajedi32
显示剩余4条评论

18

感谢Mark Longair帮我找回了我的东西!

首先,我把所有哈希值保存到一个文件中:

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

接下来我把它们全部放在一个列表中(去掉了“不可达的文件块”),并把数据都放在新文件中...你需要挑选你需要的文件并将它们重命名...但我只需要几个文件..希望这能帮助到有需要的人...

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

1
是的!这正是我所需要的。我喜欢使用Python脚本重新创建所有文件。其他答案让我担心垃圾回收会丢失我的数据,所以转储文件对我来说是一个胜利 :) - Eric Olson
1
我根据这个答案编写了这个脚本。开箱即用:https://github.com/pendashteh/git-recover-index - Alexar
2
我发现这样自动化更容易: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。文件最终会出现在 lost/*.blob 中。 - Matija Nalis

7

@Ajedi32在评论中提供的解决方案在我这个情况下完全奏效。

git reset --hard @{1}

请注意,所有这些解决方案都依赖于没有进行git gc,其中一些可能会导致gc的发生,因此在尝试任何操作之前,请将.git目录的内容压缩成zip文件,以便在无法解决问题时可以回滚到之前的快照。

我有一些未提交的文件。然后我提交了一个文件,并执行了git reset --hard,这以某种未知的方式破坏了我的仓库。@Duncan,@{1}是什么意思?你指的是哪个评论?它是重置git reset吗? - alpha_989
我认为@{1}是相对于倒数第二个提交的引用,但我不是专家,只是报告了对我有效的方法。 - Duncan McGregor

5
遇到了相同的问题,但是没有将更改添加到索引中。因此,上面的所有命令都没有将所需的更改带回来。
在所有以上详细的答案之后,这是一个天真的提示,但是它可能会为没有首先考虑它的人节省时间,就像我一样。
绝望之中,我尝试在我的编辑器(LightTable)中按下CTRL-Z,每个打开的标签页都按一次——幸运的是,这恢复了该标签页中文件的状态,使其回到 git reset --hard 之前的最新状态。 HTH。

2
完全相同的情况,忘记添加索引并进行了硬重置,所有解决方案都无效。这是一个明智的举动,谢谢我使用Ctrl+Z并挽救了我的一天。我的编辑器:SublimeText。我为你点赞! - Vivek

3

哎呀,我一直在苦恼这个问题,直到看到这个问题和答案。我认为对于这个问题的正确而简洁的答案只有当你将上面两条评论结合起来时才能得到,所以我把它们放在了同一个地方:

  1. 如chilicuil所提到的,运行 git reflog 来识别出你想要回退到的提交哈希值

  2. 如akimsko所提到的,除非你只丢失了一个提交,否则你很可能不想使用 cherry pick,所以你应该运行 git reset --hard <hash-commit-you-want>

注意:如果你是egit Eclipse用户,我找不到在Eclipse中使用egit执行这些步骤的方法。关闭Eclipse,在终端窗口中运行上述命令,然后重新打开Eclipse就可以了。


2
如果您正在使用IDE编辑文件,则可能会保留其自己的历史记录,与Git独立。在某些情况下,完全绕过Git并使用IDE要简单得多。例如,IntelliJ IDEA和Eclipse都具有这种自动本地版本控制,它们称之为“本地历史记录”。在IntelliJ中,可以轻松检索跨多个文件丢失的整批更改:在项目窗格中,您可以右键单击整个项目或目录树,然后选择“本地历史记录”子菜单中的“显示历史记录”。应出现git reset --hard在列表中作为“外部更改”(即从IDE外部触发),您可以使用还原按钮或上下文菜单项将所有内容恢复到外部更改发生之前的状态。

1
这可能对于git专业人士来说是显而易见的,但我想提出来,因为在我疯狂搜索时没有看到这个问题被提及。
我暂存了一些文件,然后执行了git reset --hard,有点慌了,然后注意到我的状态显示所有的文件仍然是暂存状态,并且它们的删除操作也未暂存。
此时,只要你不将删除操作暂存,就可以提交那些已经暂存的更改。之后,你只需要鼓起勇气再次执行git reset --hard,这将把你带回到你已经暂存并刚刚提交的更改。
再次强调,这对大多数人来说可能并不新奇,但我希望既然它对我有所帮助,而且我没有找到任何建议这样做的内容,它可能会帮助其他人。

1
如果你最近查看了 git diff,那么在尚未暂存更改的情况下,有另一种从此类事件中恢复的方法:如果 git diff 的输出仍在控制台缓冲区中,你可以向上滚动,将差异复制粘贴到文件中,并使用 patch 工具将差异应用于你的树形结构:patch -p0 < file。这种方法曾经帮助过我几次。

0

我正在使用IntelliJ,并且能够简单地浏览每个文件并执行以下操作:

编辑 -> 从磁盘重新加载

幸运的是,在我清除了我的工作更改之前,我刚刚执行了git status,所以我知道需要重新加载的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接