每个提交都有一个每个文件的完整副本——或者更准确地说,它拥有的每个文件,但是这样说,听起来是多余的。这意味着如果您签出一些提交 C 并开始工作,您要做的一件事就是删除一些文件 path/to/file 并最终运行 git commit 以进行新的提交 @ 987654325@,commit D 拥有它拥有的所有文件,这意味着它省略 path/to/file。但是 path/to/file 仍保留在提交 C 中,因为一旦提交,任何地方的任何权力都无法更改提交。
这反过来意味着,如果文件 path/to/file 没有在提交 C 与一些较早的提交 B 和 A ,您可以从这三个提交中的任何一个中获取文件path/to/file。所有三个提交中的文件只是一个副本(事实上,Git de-duplicated 它,尽管文件出现在所有三个提交中,但它只存储一次)。这背后的机制非常聪明和优雅,与你的目的完全无关。 ? 对您而言,重要的是您可以从任何具有您想要的副本的提交中获取文件。
记住这一点,如果最容易找到专门提交C,而不是提交A 或B,我们可以这样做——找到提交C。如果最容易找到提交 B 或 A,并且您确定这两个提交中的副本同样好,请考虑使用提交 B 或 A 来取回文件。
Florian Endrich showed in his answergit log --diff-filter=D --summary 的第一个命令是一种查找我在上面调用D 的提交的方法——您关心的文件在其中被删除,从而找到文件的完整路径名。如果您已经知道文件的路径,则只需使用 next 命令会更简单:
git log -n 1 -- path/to/file
这是一个简化版本:我们只需要获取提交D 的原始哈希ID。一旦我们有了那个,将提交命名为C 就很简单了,因为提交C 是提交D 的父提交。这是一个示例,稍微修剪一下,将一个 @ 更改为一个空格,以(也许)减少一点垃圾邮件:
git log -n 1 -- compat/gmtime.c
commit 84b0115f0dc9483dbc7f064b46afaddc4d94db92
Author: Carlo Marcelo Arenas Belón <carenas gmail.com>
Date: Thu May 14 12:18:54 2020 -0700
compat: remove gmtime
ccd469450a (date.c: switch to reentrant {gm,local}time_r, 2019-11-28)
removes the only gmtime() call we had and moves to gmtime_r() which
doesn't have the same portability problems. ...
这个对 Git 存储库的特定提交会删除文件 compat/gmtime.c。因此,该文件存在于此提交的 parent 提交中,其形式为删除前的形式。提交84b0115f0dc9483dbc7f064b46afaddc4d94db92 的父提交是什么? Git 可以告诉我们:
$ git rev-parse 84b0115f0dc9483dbc7f064b46afaddc4d94db92^
7397ca33730626f682845f8691b39c305535611e
(注意rev-parse 参数末尾的插入符号^;如果你的命令行解释器喜欢啃胡萝卜,呃,插入符号,你可以使用~)。
因此84b0115f0d... 的父级是7397ca3373...,这意味着如果我们想要返回compat/gmtime.c,我们可以简单地让Git 查看或提取该文件的版本,因为它出现在提交7397ca3373... :
$ git show 7397ca3373:compat/gmtime.c
#include "../git-compat-util.h"
#undef gmtime
#undef gmtime_r
[snipped]
我们实际上也不需要找到 C 的 hash ID,因为我们可以将 ^ 后缀添加到 D 的 hash ID 以表示“提交 C” .所以git show 84b0115f0d^:compat/gmtime.c 也可以。
鉴于您想要文件返回,您想要使用的命令是 Git 2.23 中的新 git restore:
git restore --source=<commit> --staged --worktree -- path/to/file
其中<commit> 部分是C 的正确哈希ID,或D 的哈希ID 后跟插入符号^ 或波浪号~ 字符,或其他。
如果您有一个易于使用、易于回忆的名称,用于提交A 或B,先于 提交C,但是里面有正确的文件,你也可以在这里使用 that:
git restore --source=dev --staged --worktree -- path/to/file
如果分支dev 上最后一次提交中的文件副本是一个好的副本。
如果你没有 Git 2.23 并且无法升级,你可以使用:
git checkout <commit> -- <path>
用--source=... --staged --worktree 代替git restore。事实上,如果你确实有 Git 2.23 或更高版本,你可以仍然使用这种git checkout,它仍然有效。
总结
-
找到具有良好文件副本的提交。 任何 提交都可以:提交只保存文件的完整快照,因此 任何 包含正确快照的提交都是该文件副本的良好来源。
-
使用git restore 或git checkout 从该提交中提取文件。使用--staged --worktree 告诉git restore 将文件解压缩到暂存区和您的工作树,以便文件准备就绪。使用 git checkout 也总是这样做:例如,使用 git restore 您可以选择 not 以使文件已暂存,因此 git restore 更灵活,但代价是需要更多输入.