首页 > 解决方案 > 从 Git 中删除文件,但不要为远程用户删除,只是忽略它

问题描述

我和其他几个人都可以访问包含一个由 IDE 自动生成的文件的存储库。这个文件是相当特定于 PC 的,所以不应该在源代码控制中,但目前是。我想将其删除并将其添加到 中.gitignore,但我不希望在其他协作者拉取我的更改时将其删除。关于删除文件但保留我的本地副本有很多问题;但他们不涵盖其他用户,所以当他们拉出时,尽管我保留了我的副本,但他们仍然会丢失他们的副本:

从 Git 存储库中删除文件而不从本地文件系统中删除它

如何在不从磁盘中删除文件的情况下 git rm 文件?

也有拉的时候不丢失本地文件的问题和解决方案,所以他们可以保留文件,但这需要那些拉的明确的动作,我不想去告诉大家具体如何拉这个时间。我确实发现了两个重复的问题。那里的答案是它无法完成,但它们都是 5 年前 - 在此期间有什么变化吗?

Git删除跟踪的文件,但保持本地和远程

Git忽略文件,而不删除它

这很重要,因为该文件是在您首次导入整个项目时自动生成的,并且包含有关本地编译器/库版本的信息。所以删除它需要重新导入。如果它有任何区别,那就是.idea/scala_compiler.xmland (实际上应该忽略.idea/scala_settings.xml整个目录)。.idea基本上我想让 Git 将一个文件设置为不再跟踪,但不要为任何人删除它。

标签: gitintellij-ideaignore

解决方案


你不能。

嗯,让我再试一次:不能,但他们可以。好吧,你可以,但只为你,他们可以,但只为他们。你,或者他们,必须git rm --cached 在正确的时间运行。当然,这是您不想使用的解决方案。

更有用的是(冒着重复前面问题的风险):对于这些文件,就 Git 提交而言,您唯一能做的就是在未来的 Git 提交中忽略它们。由于不在提交中,它们也不会通过推送和获取操作传输。

请记住,每个提交都包含Git 知道的所有文件的完整快照。 (我们稍后会进一步完善。)如果 Git 知道.idea/*,Git 会将它们放入新的提交中,当你推送这些提交时——你不能推送文件,只能提交——那些提交,完成那些文件,将四处走动。当你获取新的提交时——同样,你得到的是整个提交,而不是文件——这些提交将与这些文件一起出现。

那么根本问题就变成了这样:

  • 你或他们正在进行 Git 知道的提交.idea/*。您当前的提交包含文件。
  • 你,或者他们,已经获取了一些新的提交。这些新提交包含这些.idea/*文件。
  • 如果您(或他们)现在要求您(或他们的)Git 将您当前提交切换缺少文件的提交,您(或他们)的 Git 会看到您(或他们)明确告诉您(他们) Git删除文件。所以它会这样做。

这个问题的解决方法是:

  • 您(他们)必须告诉您(他们的)Git现在忘记这些文件,以便这些文件的工作树副本不被跟踪:

     git rm -r --cached .idea      # note the --cached
    
  • 现在你(他们)告诉你的 Git:切换到新的 commit。未跟踪的文件根本不在 Git 的视图中,也不在新提交中,因此 Git不会删除这些文件的工作树副本。

请注意,如果您切换包含这些文件的提交,您的 Git 将使用提交的文件覆盖您的工作树文件。(他们的 Git 将在相同条件下对他们的工作树文件执行相同的操作。)因此,在返回包含这些文件的历史提交时要非常小心。有关详细信息,请参阅下面的详细说明。

龙:这是怎么回事

正如我们刚刚提到的,每次提交都有每个文件的完整快照。这些快照以特殊的、只读的、仅限 Git 的格式保存。我喜欢称这种格式为冻干。这种形式的文件会自动进行重复数据删除,因此大多数提交主要重用以前提交的大多数文件这一事实意味着新提交几乎不会占用任何磁盘空间。

Git 重新使用这些冻干文件是安全的,因为任何现有提交的任何部分,包括保存的文件,都不能被更改。您可以进行与现有提交不同的新提交,但不能更改现有提交。甚至 Git 本身也无法做到这一点。

因为你实际上不能使用这些文件来做任何实际的工作,所以 Git 必须提取一个提交。这就是git checkout(或者,从 Git 2.23 开始git switch)所做的:它从某个提交中提取冻干文件,并将其提取为您可以实际使用(和更改)的形式。您选择提取,然后使用和/或处理的提交是您当前的提交

这意味着从当前提交中获取的每个文件实际上都有两个副本:与提交本身一起存储的冻干副本,以及用于执行实际工作的常规格式、再水化的副本。

要进行的提交,任何使用这种方案的版本控制系统(大多数都这样做,尽管内部细节差异很大)必须采用您当前的工作树版本并将它们转换回适当的已提交版本。在大型存储库中,这可能需要相当长的时间。为了让自己更容易,Git 实际上并没有这样做。

取而代之的是,Git 保留了第三份副本——嗯,不是真正的副本,确切地说,因为它使用冻干、去重的格式——在 Git 所谓的indexstaging area或(这些天很少)缓存中。这个缓存的、冻干格式的、预先去重的文件副本已准备好进入您将进行的下一次提交。

让我们用粗体重复一遍,因为它是这里的关键:Git 的索引包含将进入下一次提交的文件,采用冻干格式,准备就绪。git checkoutor操作从git switch提交中填充 Git 的索引和您的工作树,现在是当前提交。现在所有三个副本都匹配,除了工作树副本实际上是可用的,而不是被冻干。

如果您更改工作树副本,则必须git add在其上运行。该git add命令告诉 Git:使您的索引副本与我的工作树副本匹配。 Git 现在将读取工作树副本并将其压缩和重复数据删除为冻干格式,准备进入下一次提交。因此索引中的文件不再匹配当前提交中的文件。换句话说,索引提交之间的一个关键区别是您可以通过像这样批量替换文件来更改索引内容。

从字面上看,这些索引副本是Git 知道的文件。它们是将在下一次提交中的文件。为确保下一次提交没有文件,您只需将其从 Git 的索引中删除即可。

git rm命令_

git rm命令从 Git 的索引中删除文件。如果没有--cached,它还会从您的工作树中删除这些文件。您想保留您的工作树副本,因此您需要告诉 Git:保留我的工作树副本,方法是添加--cached到您的git rm: 仅从索引中删除(“缓存”)。

现在该文件或文件不在Git 的索引中,它们不会在下一次提交中。因此,一旦删除文件,您就可以进行没有文件的新提交:

git rm -r --cached .idea && git commit

例如。

切换提交

当你使用git checkoutgit switch从一个提交切换到另一个提交时——例如,通过更改你所在的分支——你是在告诉 Git:删除与当前提交相关的所有内容并切换到另一个提交。 这让 Git 清空它的索引,删除每个相应文件的工作树副本——Git 知道的文件。然后,Git 可以重新填充其索引并使用您想要处理/使用的提交中的文件副本重新填充您的工作树:您的新当前提交。

如果 Git 知道.idea/*,这就是.idea/*文件被删除的原因。如果他们不在新的提交中,他们不会从新的提交中回来。

.gitignore有一个粗心的陷阱

.gitignore文件的名称有些错误。中列出的文件.gitignore不一定是untracked,如果它们被跟踪——如果 Git 知道它们,因为它们在 Git 的索引中——它们根本不会被忽略。

让我们在这里注意,一个未跟踪的文件现在在你的工作树中,但现在不在Git 的索引。这意味着如果.idea/*被跟踪——例如,从当前提交中出来——但你只是运行git rm --cached .idea/*or git rm -r --cached .idea,那些工作树副本现在未被跟踪。它们是否在当前提交中并不重要:重要的是它们是否在 Git 的索引

告诉Git.gitignore三件事。前两个通常是重要的两个。最后一个是陷阱。

  1. 如果未跟踪文件的名称或模式出现在 中.gitignore,则该git status命令不会抱怨该文件未跟踪。

  2. 如果未跟踪文件的名称或模式出现在 中.gitignoregit add则不会将该文件添加Git 的索引中(git add如果需要,您可以强制覆盖它)。这意味着该文件将在正常的日常git adds 中保持未跟踪。

  3. 如果未跟踪文件的名称或模式列在 中.gitignore,Git 有时会随意破坏该文件。

当你切换提交时,Git 尽量不破坏未保存的工作

您可能对这个问题很熟悉:您开始处理某个文件——即工作树中的副本——然后意识到:哎呀,我想在不同的分支上完成这项工作。 你运行or ,Git 用它有点神秘的方式说:我不能那样做。 Git 告诉你有未保存的更改会被破坏。git checkout branchgit switch branch

(有时 Git 会让你切换分支。这一切都与 Git 的索引有关。有关血腥细节,请参阅Checkout another branch when there are uncommitted changes on the current branch

如果此未保存的作品位于跟踪文件中,或者位于未在 中列出的未跟踪文件中.gitignore则此安全检查将防止您丢失数据。但是在其中列出文件有时会.gitignore允许Git 覆盖或删除工作树副本。发生这种情况的确切时间并不明显——有时即使这样,Git 也会告诉你先保存文件——但这一个问题。

唯一完整的解决方案是痛苦的

不幸的是,这个问题的唯一真正解决方案与问题本身一样痛苦,或者比问题本身更痛苦:您可以使用包含文件的提交的存储库,并使用它来构建一个新的、不兼容的编辑历史存储库,仅包含根本没有文件的提交。

为此,请使用git filter-branch、 或git filter-repo(相对较新且尚未随 Git 本身分发)、BFG 或任何此类 Git-commit-history-editing 系统。所有这些工作的方式,必然是它们将旧提交(那些拥有文件的提交)复制到具有不同哈希 ID 的新提交,这些文件永远不会出现在其中然后,此更改会“随着时间的推移而下降”到所有后续提交中。这就是使新存储库与旧存储库不兼容的原因。

如果您曾经让旧存储库和新存储库相遇,并且有任何相关的历史没有改变,1这两个 Git 将连接旧历史和新历史,并且您实际上将存储库的大小翻倍,同时添加回所有你认为你已经摆脱的提交。


1这将是在不需要的文件存在之前的历史提交。例如,如果您使用 GitHub 的以README.mdLICENSE文件开头的技巧,则该提交不需要重写,并且将保持不变并在新旧存储库之间建立共同的提交历史。

除此之外,如果您使用可以追溯到--allow-unrelated-histories标志之前的旧 Git,或者提供--allow-unrelated-historiesgit merge,也可以将旧历史融合回新历史。


推荐阅读