Git 不会在签出之前的快照时删除新文件

Git doesn't remove new files on checkout of prior snapshot

我在快照 a0c53b9 开始使用特定的存储库。我更改了一些文件并在目录 kernel/u-boot/ 中添加了其他文件,并提交了快照 9c06fb7.

事实证明,我不会使用对 u-boot/ 所做的更改,但仍希望能够参考它们。因此我做了以下事情:

git checkout a0c53b9 u-boot/
git commit -m "profound and eclectic description of change"

奇怪的是,我发现虽然结帐将已修改的文件从 9c06fb7 恢复到 a0c53b9,但它并没有删除已在 a0c53b9 中添加的文件。这些不是未跟踪的文件,因此许多其他答案中的 git clean -fd 建议不适用。

假设修改了先前存在的文件 u-boot/common/usb.c,并添加了新文件 u-boot/board/xilinx/zynq/crypto/sha.h。在 git checkout a0c53b9 u-boot/ 之后,文件 u-boot/common/usb.c 已恢复到其 a0c53b9 状态,但文件 u-boot/board/xilinx/zynq/crypto/sha.h 仍然存在而不是被删除。

我是不是做错了什么?如何让这些以前添加的文件消失?

How do I get these previously added files to go away?

您总能找到所需文件的 SHA-1,检查它,添加并再次提交所需内容。

# Checkout the given file from a given SHA-1
git checkout SHA-1 -- full_path_fo_file

另一个更简单的选项是查看旧文件的内容(如果你使用 IDE 你通常会有文件的本地历史记录,或者使用 git log --follow 并且一旦你有内容和以前一样添加并提交它。

如何做你想做的事

问题总结:当你使用git checkout <em>commit-hash</em> <em>path</em>(在本例中为 git checkout a0c53b9 u-boot/),git 提取该路径——在本例中为一个充满文件的目录——但不 git rm 任何 的文件not 在该特定提交中 当前在该目录中 。这意味着如果您现在要进行新的提交,它将具有较旧的 u-boot 代码,但会将新文件与旧代码混合在一起。

简单的解决方案是,首先,确保您不需要任何这些文件——没有未提交的工作要保存——然后只需要 git rm -rf u-boot/(不是常规的 rm)整个目录。这将清除工作树并安排删除所有文件,但在您实际提交任何内容之前,now 您 运行 git checkout a0c53b9 u-boot/,这将重新填充提交 a0c53b9u-boot/ 文件的索引和工作树。删除的没有旧版本的文件保持删除状态,删除的有旧版本的文件从旧版本恢复。


背景,或 TL;DR 部分:为什么 git 会这样

git checkout命令真的有好几个(多少取决于你怎么算)不同的命令:

  • git checkout <em>name</em>(其中 name 名称一个有效的现有分支)从当前提交切换到新提交,将您置于给定分支 name。即使根据 gitrevisionsname 应该解析为提交 ID 而不是分支名称(尽管这种情况很少见,不应该发生)。如果某些工作树文件被破坏(但 -f 无论如何都会强制切换),此操作可能会失败。
  • git checkout <em>hash</em> 从当前提交切换到新提交,让您离开任何分支(进入 "detached HEAD"模式)。在其他方面与给出分支名称相同。 hash 可以是解析为提交 ID 的任何内容,包括标签或远程跟踪分支名称;它不能是现有的分支名称,因为它属于第一种情况。
  • git checkout -b <em>name</em> 使用给定的 [=20= 创建一个新分支](附加参数会影响分支的创建方式)。它通常不会失败,因为您通常会在当前提交时创建一个新分支,这意味着只有 HEAD 间接需要更改:工作树中未提交的工作仍然未提交。
  • git checkout -- <em>path</em> 从索引中提取一些文件到工作树中,破坏任何未提交的工作。请注意,如果您之前 git add 编辑了 path,然后又修改了文件,这将恢复您 git add 编辑的版本,而不是HEAD 提交中的版本。
  • git结帐<em><code>hash--path 从给定的 hash 中提取一些文件到工作树中,将它们 写入 索引(即,索引条目也会更新)。它在其他方面与之前的(从索引中检出)方法相同:在这两种情况下,工作树在命令完成时与索引匹配。 hash 参数可以是 git 可以解析为树的任何内容:这包括分支、标记和远程跟踪分支名称。 Git 不更改当前提交,仅更改索引和工作树。

请注意,前三个列出的形式小心地 避免 破坏工作树中的工作,而后两个故意破坏工作。 (我个人认为至少这两种模式应该是不同的命令。也就是说,如果 git <something-like-checkout> 总是安全的并且 git <something-like-clobbber> 是 "unsafe" 命令就好了。那么更容易记住何时要小心。)

当您这样做时:

git checkout a0c53b9 u-boot/

您使用的是 git checkout 的最后一种形式,即 git checkout <em>hash</em> -- <em>path </em>。 (这里的双破折号实际上并不是必需的。它把散列和路径分开,并且 是必需的——至少在使用第四种形式时可能是这样。假设你想查看一个 leet -说出索引中名为 f33dc47 的文件。但是文件名 f33dca7—1337 中的 "feed cat"—也是一个有效的哈希前缀。双破折号是您告诉 [=165 的方式=] 您正在执行第四种结帐而不是第二种结帐。)

当您使用 git checkout 的最后两种形式之一时,git 不会 改变 您的 当前 提交,它只是 提取一些文件 。在这种情况下,git 将 不会 删除文件。您提取的文件是 "everything in u-boot/",如您所见,git 确实更新了这些文件,只是没有删除 u-boot/ 中的任何其他文件。这是 git checkout.

的正确行为

在您的情况下,您想要进行具有 "new everything else, but an old u-boot/, including not having the new u-boot/ files that came along for the ride when we got everything" 的新提交。那么,如何清理目录,以避免有多余的文件呢?答案在于注意到 git checkout <em>hash</em> -- <em>path</em> 通过索引写入进入工作树;因此,如果您首先清空该目录中所有文件的工作树和索引,然后从旧提交中重新填充两者,您将得到您想要的。