撤消git reset --hard在暂存区中使用未提交的文件


109

我正在努力恢复工作。我愚蠢地做过git reset --hard,但是在那之前我只做过get 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脚本来创建所有输出的文件!我将脚本添加为答案
男孩

4
不是“愚蠢” ..而是“天真的” ...因为我只是做同样的事情!
Rosdi Kasim 2014年

仍然可能是愚蠢的;-)
邓肯·麦格雷戈

这是一篇很棒的文章,介绍了如何扭转这种状况。这将需要一些手动工作。
Jan Swart

@MarkLongair```找到.git / objects / -type f -printf'%TY-%Tm-%Td%TT%p \ n'| 排序```对我有用。日期也会出现,从头开始检查blob。
ZAky

Answers:


175

git add .尽管可能需要一些工作,但是您应该能够恢复添加到索引中的所有文件(例如,根据您的情况,使用)。为了将文件添加到索引,git将其添加到对象数据库,这意味着只要还没有进行垃圾收集,就可以对其进行恢复。JakubNarębski的答案中提供了一个有关如何执行此操作的示例:

但是,我在测试存储库上进行了尝试,发现了两个问题- --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

(请注意,必须删除/路径末尾的以获得对象名称。)


在发布答案之前,我考虑过要包括这些步骤。但是,在使用我的一个本地存储库尝试此操作时,我遇到了数百个无法访问的对象,并且我无法提出一种适当的方法来对它们(按提交日期进行排序)进行排序。现在那太棒了
sehe 2011年

3
是否有可能查看晚于最后一次提交的最新对象的对象时间戳?还是我错过了一些东西。
菲利普·奥克利

1
@Philip Oakley:感谢您的建议,我添加了建议,以便在对象数据库中找到最近修改的对象。
Mark Longair 2011年

2
杜德,太棒了!刚才保存了我的@ $$。我学了一些git科学。更好地观察您添加到索引中的内容...
bowsersenior 2012年

1
不要在这里偷别人的光。但是,由于我遇到了同样的问题并以为我输了所有工作,因此只是一个说明。对于使用IDE的用户,IDE通常具有一键恢复解决方案。如果是PHPstorm,我只需要右键单击该目录并选择“显示本地历史记录”,然后恢复到最后一个有效状态。
Shalom Sam

58

我刚做git reset --hard了一次却丢了一次提交。但是我知道提交哈希,因此我能够git cherry-pick COMMIT_HASH还原它。

我在丢失提交后的几分钟内完成了此操作,因此它可能对某些人有用。


5
谢谢谢谢谢谢感谢这个答案,我们得以恢复了一天的工作
Dace 2013年

21
也许值得一提的,你可以用看那些哈希值git reflog,例如git reset --hard- > git reflog(看HEAD @ {1}散列),最后git cherry-pick COMMIT_HASH
哈维尔·洛佩斯

2
读过这些的人请注意,这仅适用于单个提交哈希,如果有多个提交,那么它将无法立即解决问题!
Ain Tohvri 2014年

4
您实际上可以重置“转发”。因此,如果您已重置重置通过了多个提交,那么再执行一次'git reset --hard <commit>'应该可以恢复到那时的整个提交链。(假设它们尚未进行
GC处理

6
恢复丢失的提交git reset --hard(假设您在执行该命令后git reset --hard @{1}
未做

13

感谢Mark Longair,我把东西还回来了!

首先,我将所有哈希保存到文件中:

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

接下来,我将它们全部(删除“无法访问的blob”)放入列表中,并将数据全部放入新文件中……您必须选择文件并再次将其重命名为所需的文件……但我只需要几个文件..希望这可以帮助某人...

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 2014年

1
我根据此答案编写了此脚本。开箱即用的作品:github.com/pendashteh/git-recover-index
Alexar,2016年

1
我发现这样自动化它更容易: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

6

@ Ajedi32在评论中的解决方案正是在这种情况下为我工作的。

git reset --hard @{1}

请注意,所有这些解决方案都依赖于git gc,其中有些可能会导致这种情况,因此在尝试执行任何操作之前,我会先压缩.git目录的内容,以便在没有git gc的情况下返回快照为你工作。


我有许多未提交的文件。然后,我提交了1个文件,并执行了一个git reset --hard,以任何未知的方式弄乱了我的存储库。@Duncan,@ {1}是做什么的?您指的是什么评论?重置git reset吗?
alpha_989 '18

我认为@ {1}是最后一次提交的相对引用,但我不是专家,只是报告对我
Duncan McGregor

3

遇到相同的问题,但尚未将更改添加到索引中。因此,上面的所有命令都没有使我带回所需的更改。

经过以上所有详尽的回答,这只是一个幼稚的提示,但也许它可以像我一样挽救那些最初没有想到的人。

令人绝望的是,我试图在编辑器(LightTable)中按CTRL-Z,在每个打开的选项卡中都按一次-幸运的是,此选项卡中的文件已恢复到之前的最新状态git reset --hard。HTH。


1
完全相同的情况,错过了添加索引并进行了硬重置,所有解决方案都无法正常工作。这是一个明智的举动,谢谢我做了Ctrl + Z并保存了我的一天。我的编辑器:SublimeText。从我身边一起来!
Vivek

1

这对那里的git专业人员来说可能是显而易见的,但是我想提出来,因为在疯狂的搜索中我没有看到这一点。

我登台了一些文件,然后做了一个git reset --hard,使它有些奇怪,然后注意到我的状态显示我所有的文件仍在登台,而所有删除都在未登台。

此时,只要不进行删除,就可以提交这些已进行的更改。在此之后,您只需要鼓起勇气再做git reset --hard一次,这将带您回到您已经上演但现在刚下定决心的那些改变。

再说一次,这对大多数人来说可能并不是什么新鲜事物,但是我希望它能对我有所帮助,但我没有发现任何暗示,它可能会对其他人有所帮助。


1

天哪,我一直拉着头发,直到遇到这个问题及其答案。我相信,仅当您将上面的两个评论放到一起时,才能正确回答该问题,这是一个地方:

  1. 如chilicuil所述,运行git reflog以在其中标识要返回的提交哈希

  2. 如akimsko所述,除非您只丢失一次提交,否则您可能不希望摘樱桃,因此您应该先运行 git reset --hard <hash-commit-you-want>

给egit Eclipse用户的注释:我找不到使用egit在Eclipse中执行这些步骤的方法。关闭Eclipse,在终端窗口中运行上述命令,然后重新打开Eclipse对我来说很好。


0

上面的解决方案可能会起作用,但是,有更简单的方法可以从中恢复,而不必进行git-s复杂的撤消。我想大多数git-resets发生在少数文件上,如果您已经使用VIM,这可能是最省时的解决方案。需要注意的是,您已经必须使用ViM'spersistent-undo,而您应该使用这两种方式,因为它使您能够撤消无限数量的更改。

步骤如下:

  1. 在vim中,按:并键入命令set undodir。如果您已persistent undo开启.vimrc,它将显示类似于的结果 undodir=~/.vim_runtime/temp_dirs/undodir

  2. 在回购中使用git log以找出您上次提交的最后日期/时间

  3. 在您的外壳中,导航到您的undodirusing cd ~/.vim_runtime/temp_dirs/undodir

  4. 在此目录中,使用此命令查找自上次提交以来已更改的所有文件

    find . -newermt "2018-03-20 11:24:44" \! -newermt "2018-03-23" \( -type f -regextype posix-extended -regex '.*' \) \-not -path "*/env/*" -not -path "*/.git/*"

    这里的“ 2018-03-20 11:24:44”是最后一次提交的日期和时间。如果执行日期git reset --hard是“ 2018-03-22”,则使用“ 2018-03-22”,然后使用“ 2018-03-23”。这是因为有一个find的怪癖,其中下边界是包括在内的,而上边界是排斥的。 https://unix.stackexchange.com/a/70404/242983

  5. 接下来转到每个文件,在vim中打开它们,然后执行“更早的20m”操作。您可以通过使用“ h更早”来找到有关“更早”的更多详细信息。此处的 earlier 20m意思是假设您在git hard --reset20分钟后进行了,回到20分钟后回到文件状态。对从find命令中弹出的所有文件重复此操作。我确信有人可以编写将这些内容组合在一起的脚本。


0

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

Edit -> reload from disk

幸运的是,git status在清除工作更改之前,我刚刚做了一件正确的事,所以我确切地知道需要重新加载什么。


0

记住您的文件层次结构,并使用Mark Longair的技术对Phil Oakley进行修改,会产生令人瞩目的结果。

本质上,如果您至少将文件添加到了仓库中但未提交,则可以使用进行交互地还原git show,检查日志并使用Shell重定向来创建每个文件(记住您感兴趣的路径)。

HTH!


0

如果您最近查看过a,git diff那么还有另一种方法可以从类似的事件中恢复,即使您尚未上演更改git diff,也可以:如果的输出仍在控制台缓冲区中,则可以向上滚动,将其复制粘贴。将diff放入文件中,然后使用该patch工具将diff应用于您的树:patch -p0 < file。这种方法节省了我几次。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.