如何在Git存储库中查找和恢复已删除的文件?

3192

假设我在一个Git仓库中。我删除了一个文件并提交了这次更改。然后,我继续工作并做出了一些更多的提交。然后,我发现需要恢复之前删除的那个文件。

我知道可以使用git checkout <commit> -- filename.txt命令来检出一个文件,但我不知道何时删除了该文件。

  1. 如何找到删除指定文件的提交?
  2. 如何将该文件恢复回我的工作副本?

43
请注意,前一条评论回答了标题中的问题,而不是正文中的问题——这包括查找文件何时被删除。 - avdgaag
14
查找文件被删除的提交记录:git log --diff-filter=D -- path/to/file - titaniumdecoy
@hhh不起作用,我收到错误消息:pathspec './src/main/resources/file'未匹配任何已知于git的文件。 - kap
64
如果文件已被删除但该删除尚未暂存或提交,使用git checkout deletedFile命令可以恢复该文件。但这并不是本问题所要求的;本问题是关于如何恢复一个在很多次提交之前被删除的文件。 - Mark Amery
显示剩余2条评论
30个回答

3509

查找最后一次影响给定路径的提交记录。由于该文件不在HEAD提交中,因此之前的提交记录必须已经删除了它。

git rev-list -n 1 HEAD -- <file_path>

然后使用脱字符(^)符号检出前一个提交的版本:

git checkout <deleting_commit>^ -- <file_path>

如果 $file 是指定的文件,则可以在一个命令中完成。

git checkout $(git rev-list -n 1 HEAD -- "$file")^ -- "$file"

如果你正在使用zsh并启用了EXTENDED_GLOB选项,插入符号(^)将无法正常工作。你可以使用~1代替。

git checkout $(git rev-list -n 1 HEAD -- "$file")~1 -- "$file"

111
棘手的部分是要使用 ^ 后缀来检出之前的提交。谢谢。 - Christian Oudard
5
因某些原因,这个命令在zsh中无法正常工作。± git checkout $(git rev-list -n 1 HEAD "spec/Sporkfile_example.rb")^ -- "spec/Sporkfile_example.rb" zsh: no matches found: b71c152d8f38dcd23ad7600a93f261a7252c59e9^我改用bash,它就可以正常运行了。 - zoras
23
从Windows命令行中,我遇到了一个错误:error: pathspec <filename> did not match any file(s) known to git. 解决方法是使用Git Bash。 - donturner
62
@zoras 我相信zsh在'^'上有自己的扩展,但你可以使用替代语法'~1':git checkout <deleting-commit>~1 -- <file-path>X允许您指定指定提交之前的X个提交,所以1是指定提交之前的一个提交, ~2是两个提交之前,以此类推。 - Nils Luxton
33
在Windows命令提示符中,“^”字符是转义字符!因此,在cmd中,您必须键入“^^”来告诉cmd您需要一个单独的文字“^”,并且您没有在其后转义其他内容。许多人遇到的问题是“^”后面跟着一个空格。因此,cmd认为您正在转义空格--这只会产生一个空格字符。因此,当git获取cli参数时,它看到的是“SHA1”,而不是“SHA1 ^”。这真的很恼人。“~”不是转义字符,所以它仍然有效。(另外,如果您认为谷歌员工会想要这些信息,请点赞此评论) - Alexander Bird
显示剩余16条评论

1036
获取所有已删除文件的提交,以及被删除的文件:
git log --diff-filter=D --summary
记录所需的提交哈希值,例如e4e6d4d5e5c59c69f3bd7be2。
从上述确定的提交(e4e6d4d5e5c59c69f3bd7be2)的前一个提交(~1)中恢复已删除的文件:
git checkout e4e6d4d5e5c59c69f3bd7be2~1 path/to/file.ext
注意到~1。波浪号规范将给出指定提交的第n个祖先。

13
好的,"curious, what does the 1 refer to?" 可以翻译为 "好奇,1 是指什么?" - tommy chheng
8
@tommy - 波浪号规范将为您提供所命名提交的第n个子代。有关更多详细信息,请参阅http://book.git-scm.com/4_git_treeishes.html。 - Robert Munteanu
10
这绝对是最简单直观的方法。git log -- *文件名的一部分*。感谢 $commit~1 - bgs
4
git checkout $commit1 filename 语法适用于单个文件,也适用于整个目录。例如:要从 sha 12345 恢复 ./images 中所有已删除的图像,请运行命令 `git checkout 123451 images`。感谢您提供这个答案! - noinput
38
@Alexar $commit~1 的意思是你需要添加提交的名称,类似于 $commit 所代表的 1d0c9ef6eb4e39488490543570c31c2ff594426c。请注意不要改变原有的意思,并使其更加通俗易懂。 - Eugene
显示剩余11条评论

341

恢复文件夹中所有已删除的文件:

git ls-files -d | xargs git checkout --

1
文件被导向到哪里?我看不到任何变化。 - William Grand
33
这可能是最简单的方法。可悲的是,连最简单的任务都被 git 变得如此困难。 - jww
10
ls-files子命令很方便,但似乎无法处理使用git rm删除(即已暂存)甚至提交的文件,这正是OP所问的。 - MarkHu
3
@RomainValeri - 现在是2019年。这些工具适用于我,而我不是为这些工具服务的。如果需要学习,那么设计就有问题了。 - jww
4
@RomainValeri,@jww 你们都是正确的。git 在其实用性方面几乎是无与伦比的,但学习起来也因其复杂而声名狼藉。git 学习曲线中相当大一部分原因是由于其不一致/不直观的用户界面。就我个人而言,在学习困难的东西时,看到其他(有能力的)人也努力地尝试弄清楚它,会让我感到安慰。 - Matthew Strasiotto
显示剩余5条评论

153
如果你删除了最新的HEAD提交中存在的文件,你可以使用以下命令来恢复它:
git checkout HEAD -- path/to/file.ext

101

如果你很着急,可以使用git-bisect。以下是具体做法:

git bisect start
git bisect bad
git bisect good <some commit where you know the file existed>

现在是运行自动化测试的时候了。Shell命令'[ -e foo.bar ]'如果foo.bar存在则返回0,否则返回1。git-bisect命令的"run"将使用二分查找自动找到第一个测试失败的提交。它从给定范围(从好的到坏的)的中间开始,并根据指定测试的结果将其分成两半。

git bisect run '[ -e foo.bar ]'

现在你已经到达删除该文件的提交点。从这里,你可以回到未来并使用git-revert命令撤消更改。

git bisect reset
git revert <the offending commit>

或者你可以回到上一个提交,手动检查损坏的部分:

git checkout HEAD^
cp foo.bar /tmp
git bisect reset
cp /tmp/foo.bar .

2
你能详细说明一下 git bisect run '[ -e foo.bar ]' 吗? - avdgaag
如果有些东西无法自动检查,您也可以手动使用好和坏。请参阅 bisect 手册页。 - Josh Lee
3
@avdgaag,“git bisect run”告诉Git通过运行跟在“run”单词后面的命令来自动执行二分查找,该命令必须返回“0”表示版本为“good”(有关详细信息,请参见“ git help bisect”)。 “[ -e foo.bar]”是用于测试文件“foo.bar”是否存在的标准表达式(实现通常在文件“/usr/bin/[”中,通常与“/usr/bin/test”硬链接),而单引号用于将所有内容作为单个命令行参数放置。 - Mikko Rantalainen
1
太棒了。我尝试了这种方法,它找到了删除之前的提交,但没有找到实际删除文件的提交。在另一个测试中,它找到了删除之前的2个提交。 - Michael Osofsky
疯狂?也许吧。但是二分法是帮助找到错误引入位置的好方法,因此学习这种技能仍然很有价值。因此,尽管这可能不是“正确”的或最“正确”的方式,在这里仍然是一个好主意,绝对值得一试! - Pryftan

82

根据bonyiii回答(已点赞)和我自己关于“将参数传递给Git别名命令”的回答,我的新最爱别名是:

git config alias.restore '!f() { git checkout $(git rev-list -n 1 HEAD -- $1)~1 -- $(git diff --name-status $(git rev-list -n 1 HEAD -- $1)~1 | grep '^D' | cut -f 2); }; f'

我丢失了一个文件,几次提交之前不小心删除了它?
快速解决:

git restore my_deleted_file

危机已经解除。
请注意,随着 Git 2.23 的到来 (Q3 2019),会有一个名为 git restore(!)的实验性命令
因此,请重命名此别名(如下所示)。

Robert Dailey在评论中提出了以下别名:

restore-file = !git checkout $(git rev-list -n 1 HEAD -- "$1")^ -- "$1"

并且{{link1:jegan}}在评论中添加了{{link2:in the comments}}:

为了从命令行设置别名,我使用了这个命令:

git config --global alias.restore "\!git checkout \$(git rev-list -n 1 HEAD -- \"\$1\")^ -- \"\$1\"" 

7
这将恢复整个提交记录,而不仅仅是请求的文件。 - Daniel Bang
6
这是我的别名,非常好用:restore-file = !git checkout $(git rev-list -n 1 HEAD -- "$1")^ -- "$1"。(说明:该命令可用于Git版本控制系统中,用于还原某个文件到指定的提交版本。) - void.pointer
1
@RobertDailey 看起来很不错!我已经在答案中包含了你的别名,以增加可见性。 - VonC
2
Expansion of alias 'restore' failed; '!git' is not a git command - basickarl
1
Mac(Bash)!如果您只是提供一些信息,以便这是一个Linux解决方案,那就太好了^^ - basickarl
显示剩余10条评论

75

如果你知道文件名,使用基本命令就可以轻松完成:

列出该文件的所有提交记录。

git log -- path/to/file

最后一次提交(顶部)是删除该文件的提交。所以你需要恢复倒数第二次提交。

git checkout {second to last commit} -- path/to/file

5
这是我看到的第一个解决方案,足够简单,以至于下次我不必再回到这里找它。也许。 - Eloff
1
@Suncat2000,“second to last”指的是“删除之前的上一个提交”,与“next to last”相同。https://en.wiktionary.org/wiki/penultimate#Synonyms - wisbucky

31

恢复已删除且已提交的文件:

git reset HEAD some/path
git checkout -- some/path

这是在Git版本1.7.5.4上进行测试的。


1
那对我没用。结账后,我得到了“错误:pathspec 'foo'未匹配任何已知于git的文件”。我确保文件名正确。Git版本为2.7.0。 - wisbucky
-1; 这是错误的。这些命令将撤消尚未提交的删除操作(如果已经暂存,则第一个命令将取消暂存,第二个命令将丢弃文件的未暂存更改),但您在此声称它们将恢复已提交的文件删除操作,这显然是不正确的,并且会导致类似于@wisbucky评论中的错误。 - Mark Amery
@MarkAmery确实,我认为这个命令对那些没有为使用git add -A提交已删除文件而进行显式分段的开发人员非常有效,但是这样恢复的文件仍处于未提交阶段。 - Fedir RYKHTIK

29

我有这个解决方案

  1. 使用以下任一方式获取文件被删除的提交ID。

    • git log --grep=*word*
    • git log -Sword
    • git log | grep --context=5 *word*
    • git log --stat | grep --context=5 *word* # 推荐如果您几乎记不起任何东西
  2. 你应该得到像下面这样的结果:

commit bfe68bd117e1091c96d2976c99b3bcc8310bebe7 Author: Alexander Orlov Date: Thu May 12 23:44:27 2011 +0200

replaced deprecated GWT class
- gwtI18nKeySync.sh, an outdated (?, replaced by a Maven goal) I18n generation script

提交记录 3ea4e3af253ac6fd1691ff6bb89c964f54802302 作者:Alexander Orlov 日期:2011年5月12日 星期四 22:10:22 +0200

3. 现在使用提交编号 bfe68bd117e1091c96d2976c99b3bcc8310bebe7 进行操作:

git checkout bfe68bd117e1091c96d2976c99b3bcc8310bebe7^1 yourDeletedFile.java

由于提交 ID 引用了已经删除文件的提交,所以您需要引用 bfe68b 之前的提交,可以通过添加 ^1 来实现。这意味着:给我 bfe68b 之前的提交。


这与被接受的答案相同,但有更多查找删除提交的方法。我仍然喜欢被接受的答案,但这些是很好的替代方案。谢谢! - avdgaag
我假设首先检查已删除的文件,然后(不更改它)提交它不会创建该文件的副本。对吗?(我需要这样做来处理图像,而复件会使存储库变得更大) - Stonecrusher

25

如果您只修改了文件并删除了一些内容,但没有提交(commit),现在您想放弃这些更改

git checkout -- .

但是您删除的文件不会自动恢复,您只需执行以下命令:

git checkout <file_path>

瞬间,您的文件就回来了。


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