Git在还原之前的快照时不会删除新文件。

3

我开始在快照中使用一个特定的代码库。我更改了一些文件并添加到了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仍然存在,而不是已被删除。 我做错了什么吗?我该如何让这些以前添加的文件消失?
2个回答

4

如何做你想做的事情

问题概述: 当你使用 git checkout commit-hash path (在这种情况下是 git checkout a0c53b9 u-boot/) 时,Git会提取该路径(在这种情况下是一个充满文件的目录),但不会git rm任何当前位于该目录中但不在该特定提交中的文件。 这意味着如果您现在进行新的提交,它将具有旧的u-boot代码,但将新文件与旧代码混合。

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

背景,或者简洁版:为什么Git会表现出这样的行为

git checkout 命令实际上是几个不同的命令(取决于如何计算):

  • git checkout name(其中name是有效的、存在的分支名称)将当前提交切换到新提交,使您处于给定的分支name上。即使根据gitrevisionsname应该解析为提交ID而不是分支名称,这也是正确的(尽管这种情况非常罕见,不应该发生)。如果某些工作树文件会被覆盖,则此操作可能失败(但-f将强制执行切换)。
  • git checkout hash 将当前提交切换到新提交,使您退出任何分支(进入“游离 HEAD”模式)。否则它与提供分支名称相同。 hash 可以是解析为提交ID的任何内容,包括标签或远程跟踪分支名称;但不能是现有的分支名称,因为那将属于第一种情况。
  • git checkout -b name 使用给定的name创建一个新分支(其他参数可以影响分支的创建方式)。它通常不会失败,因为您通常会在当前提交处创建一个新分支,这意味着只需要更改HEAD间接引用:工作树中未提交的工作仍然未提交。
  • git checkout -- path 将一些文件从索引中提取到工作树中,覆盖任何未提交的工作。请注意,如果您先前使用git add添加了一个path,然后修改了该文件的一些内容,则此操作将恢复您添加的版本,而不是HEAD提交中的版本。
  • git checkout hash -- path 将一些文件从给定的hash中提取到工作树中,并通过索引将它们写入(即,索引条目也会更新)。否则它与之前的(从索引检出)方法相同:在两种情况下,当命令完成时,工作树与索引匹配。 hash 参数可以是Git可以解析为树的任何内容:这包括分支、标签和远程跟踪分支名称。Git不会更改当前提交,只会更改索引和工作树。
请注意,前三种列出的形式小心地避免了对工作目录中的工作进行破坏,而最后两种则故意破坏了工作。 (我个人认为至少这两种模式应该是不同的命令。也就是说,如果git <something-like-checkout>始终是安全的,而git <something-like-clobbber>是“不安全”的命令,那就更容易记住何时要小心。)
当您执行此操作时:
git checkout a0c53b9 u-boot/
你使用的是最后一种形式的git checkout,即git checkout hash -- path。(这里的双破折号实际上并不是必需的。它将哈希值与路径分开,并且在使用第四种形式时至少可能是必需的。假设您想要从索引中检出一个名为f33dc47的Leet-speak文件。但是文件名f33dca7——1337中的“feed cat”也是有效的哈希前缀。双破折号是告诉git您正在执行第四种形式的checkout而不是第二种形式的方法。)
当您使用git checkout的最后两种形式之一时,git不会更改您当前的提交,它只是提取了一些文件。在这种情况下,git不会删除文件。您提取的文件是“u-boot/中的所有内容”,正如您所看到的,git确实更新了这些文件,但没有删除u-boot/中的任何其他文件。这是git checkout的正确行为。
在您的情况下,您想要创建一个新的提交,其中包含“新的其他所有内容,但是旧的u-boot/,包括没有随之而来的新的u-boot/文件”。那么,如何清除目录,以避免多余的文件呢?答案在于注意到git checkout hash -- path会将索引写入工作树中;因此,如果您首先清空该目录中所有文件的工作树和索引,然后从旧提交重新填充两者,您将得到所需的结果。

感谢您的回答。一开始我有点吃惊rm -rf u-boot/,直到我意识到实际上是git rm -rf u-boot/,而且那个目录下也没有未提交的更改。您回答中对我有帮助的部分是"在你的情况下..."段落以及后续的命令。(我已经编辑了我的问题来纠正您指出的错误哈希值。) - undefined
为了其他读者的利益,删除大部分答案可能是值得的。虽然它非常完整,但其中大部分与实际问题无关,我认为修剪一下可能会使其更好。 - undefined
我会看看是否可以编辑这部分内容,让“如何”部分先出现,然后再解释为什么会有这种行为。虽然你没有明确问为什么额外的文件被保留下来,但你使用了“奇怪”的词,所以我也试着解释了一下。 - undefined

0
如何让这些先前添加的文件消失? 您可以始终查找所需文件的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 a0c53b9 u-boot/board/xilinx/zynq/crypto/sha.h 的命令时,Git会理所当然地告诉我 error: pathspec '...' did not match any file(s) known to git. - undefined
需要在文件名前添加--,像这样:git checkout a0c53b9 -- <路径> - undefined
尝试了git checkout a0c53b9 -- u-boot/board/xilinx/zynq/crypto/sha.h,但结果仍然相同。只有在剩余的参数可能被误认为是哈希值时,才需要使用-- - undefined

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