首页 > 解决方案 > .DS_Store 文件在 .gitignore 中,但它一直在状态中弹出

问题描述

我搜索了..我阅读了我可以在这里找到的类似甚至相同问题的所有答案。我看了教程。当它发生在某人的终端时,这似乎非常简单和合乎逻辑。在我的,它只是不工作。显然,我一定遗漏了一些非常明显的东西。我提交了.gitignore,但.DS_Store 仍然出现在状态中。然后我使用 git rm --cached .DS_Store 删除了 .DS_Store (以防万一,即使它不在我的仓库中)。没有。现在我看到它在暂存区域中显示为“要提交的更改 => 删除:.DS_Store”。有没有办法完全摆脱它?所以它停止在我的状态中弹出?

这里是:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        deleted:    .DS_Store

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md
        modified:   assets/.DS_Store
        modified:   assets/stylesheets/main.css
        modified:   index.html

标签: gitgitignore

解决方案


这里的(相当轻微的)问题是您有一些现有的提交.DS_Store存在。请注意,当前状态表明至少有两个这样的文件,一个在树的顶层,一个在assets

deleted:    .DS_Store
modified:   assets/.DS_Store

这些现有的提交不能更改。他们将继续.DS_Store永远持有该文件的副本(或者只要这些提交继续存在,无论如何)。

如果您希望它们被存储在未来的提交中,您必须从 Git 的索引中删除这些.DS_Store文件(所有这些文件)。通过这样做——运行并提交——你是在告诉 Git,当你检查其中一个历史提交时,它应该提取历史文件,并且当你从那个历史提交切换到缺少的更现代的提交之一时这些文件,Git 应该删除它们。git rm --cached.DS_Store.DS_Store

由于 macOS Finder 将在文件丢失时创建一个新的,只要它在 Finder 窗口中显示目录,此特定操作在这种特殊情况下.DS_Store就足够安全了。但是,如果您也需要在其他文件上使用,有几件事情需要注意,这可能会使其他文件变得更加棘手。git rm --cached

选读:为什么这么复杂

Git 的index,它有这个相对较差的名称(索引?它是什么索引?),还有另外两个名称。Git 也称这个东西为staging area,指的是你如何使用它,而cache,指的是它在内部是如何使用的。名称“缓存”主要出现在 的拼写中git rm --cached,它告诉 Git 从索引中删除它,而不是从工作树中删除它。

好的,所以我们为这个东西取了三个名字。这告诉我们什么?好吧,一方面,它告诉我们 Git 的索引真的很重要。事实上,这绝对是至关重要的。但为什么?为什么 Git 坚持一遍又一遍地把它的索引推到我们面前?最终,这个问题的真正答案只是“这是一个深思熟虑的选择”——但值得看看让 Linus Torvalds 做出这个选择的因素。事实证明这是从commits开始的。

最后,Git 是关于提交的。这与文件无关,尽管文件在某种意义上包含在每个提交中。这也与分支名称无关,尽管分支名称对于帮助您(和 Git)找到提交是必要的。但是,提交是存储库的历史记录。它们保存文件,并形成我们有时称为分支的东西(请参阅我们所说的“分支”到底是什么意思?)。

每个提交都由一个大而难看的哈希 ID 编号。每个提交存储两件事:

  • 在您(或任何人)进行提交时 Git 知道的所有文件的快照;和
  • 元数据,或有关提交本身的信息,例如提交人、时间和原因(您的日志消息)。

在提交的元数据中,每个提交都包含一些较早提交的哈希 ID,这就是使提交充当历史的原因。我们不会在这里详细介绍,但这就是分支的实际工作方式。

这里要知道的关键一点是,任何提交的任何部分都不能更改。这是因为提交的哈希 ID是提交中所有数据校验和。Git 在提交时计算这些校验和,并在提取提交时验证它们。如果它们不匹配,则提交在存储中以某种方式损坏,1并且无法使用。

因此,存储提交中的所有文件都是只读的。它们还被压缩(以节省空间)和重复数据删除(以节省空间和时间)。如果连续两次提交共享 1000 个文件中的 999 个,它们实际上是共享文件:只有一个更改的文件必须进入存储以供以后提交。但这意味着提交的文件对于完成任何新工作完全没有用:

  • 你不能改变它们,并且
  • 大多数程序甚至无法读取它们。

所以 Git必须将提交提取到可用区域。Git 将此区域称为工作树工作树,因为它是您工作的地方。这里的文件是普通的日常文件,你可以完成工作。

那么 Git 需要什么——这在所有版本控制系统中都很常见;他们都共享这种设置——是:

  • 已提交的、不变的历史文件;和
  • 完成工作的工作区(工作区或工作树或任何你喜欢的东西)。

在 Git 的情况下,工作区实际上是的,你可以随意使用。这意味着您可以在其中创建希望 Git 永久保存的文件。你可能会认为,这就是.gitignore进来的地方。这个假设没有,但它是不完整的。


1在现实世界中,存储介质确实会失败。大多数故障都会被检测到,但有可能(通常声称大约 10 16或更高)可能会遗漏一些故障,从而返回错误数据。谷歌做了分析,发现实际的错误率往往比声称的要高。


指数

Git 真正需要的是要提交的文件列表。也就是说,假设你在你的工作树中,做你的工作。你创建了一堆文件——可能有数百个,或者数千个,或者其他什么。 其中两个文件应作为新文件进入下一次提交,其余文件应被忽略。

解决此问题的一种可能方法是仅使用“忽略这些文件”文件,并从“忽略这些文件”文件中未列出的每个文件中自动生成要提交的文件列表。但是如果你尝试一下,你会发现它很容易出错。相反,Git 和几个类似的版本控制系统使用显式的“添加一些文件”命令它们添加到文件列表中。

那么,索引可能只是一种清单:这些是要包含的文件;当您询问 status 时,所有其他文件都将被称为未跟踪。假设是这种情况,并且您说添加所有文件。您没有明确告诉 Git忽略这些.DS_Store文件。他们进入列表。您进行提交,并且提交具有.DS_Store文件。后来,您意识到您并不打算提交这些.DS_Store文件。

太晚了。这些提交现在存在。无论您对清单做什么,最多只会从未来的提交中省略.DS_Store文件。您无法修复现有提交,因为它们是只读的。充其量,您可以返回所有旧提交,将它们一个一个取出,删除文件,然后进行新的和改进的提交,该提交与原始提交相同,但现在缺少文件。.DS_Store.DS_Store

(实际上你可以做到这一切。但这意味着你需要让其他所有人——所有其他拥有你的存储库克隆的人——停止使用旧的提交,转而使用新的和改进的提交。)

现在,与 Mercurial 的清单相比,Git 的索引特别不寻常的原因在于,有了这个文件列表,Linus 决定公开它,并用它做一些特殊的技巧:

  • 该索引不仅包含进入下一次提交的所有文件的名称(最初,通过提取您的任何提交来填充),git checkout还包含每个此类文件的内部 Git blob 哈希 ID

  • 在合并期间,索引扩展为一次最多容纳三个文件,所有文件都具有相同的名称

  • git commit命令不会费心查看您的工作树。2相反,它只是在您运行时 打包索引中的所有内容git commit。这非常快,因为这些内部 blob 哈希 ID 是 Git 存储文件的方式:事实上,它们已经存在,预压缩和预去重。

  • git add命令相当于:对这些文件进行压缩和去重并将它们放入您的索引中,替换任何以前的同名文件,或者如果没有以前的文件则创建一个新条目3

  • git rm命令意味着从您的索引和我的工作树中删除该文件。添加--cached意味着不理会我的工作树副本

所有这一切的一个结果是git commit不会提交您的工作树中的内容。您可以使用此属性来处理工作树中的文件以进行测试,而无需实际提交测试代码。这可能是好是坏;不同的人对是好还是坏有不同的看法;但这就是 Linus 选择这样做的方式,并且这种行为现在已经嵌入到许多 Git 用户的心中。

这一切归结为一个相对简单的语句:索引始终保存您提议的下一次提交,或者保存尚未解决的合并冲突,因此当前无法提交。 如果我们省略合并冲突的情况,您可以将索引视为保存您提议的下一次提交。

当签出一些现有的提交时,Git那个提交中填充它的索引,然后使用填充的索引来用文件填充你的工作树。这意味着 Git 现在已准备好进行的提交,该提交将与当前提交完全匹配。4


2为便于使用,这在很久以前进行了一些更改:git commit现在在git status内部运行,并在您可以编辑的提交消息中生成一个注释掉的git status部分。

3实际上,git add也可以表示使索引匹配到如果您删除工作树文件,git add则可以删除该文件的索引副本。例如:rm path/to/file; git add path/to/file是一种冗长的跑步方式git rm path/to/file

4如果索引和当前提交确实匹配,git commit通常会拒绝进行新的提交,迫使您使用git commit --allow-empty进行提交。新提交不是的——它包含索引中的任何内容——但与当前提交的差异将为空。


到目前为止的总结

Git 不会从您的工作树中进行新的提交,因此您的.gitignore文件内容与命令无关git commit。相反,Git 从 Git 索引中的任何内容进行新的提交。Git 的索引索引的内容通常来自当前提交。5

一旦您添加了一些文件,该文件就会继续进行新的提交,直到您明确删除它为止。无论文件名是否在文件中,这都是正确的.gitignore。要使文件真正消失,您必须将其删除。那么当前提交和你下一次提交之间的区别将包括从字面上删除文件。

那么:列出文件名、目录名、模式或您可以在文件中列出的任何内容是什么它有什么好处?.gitignore.gitignore


5此规则有一些例外情况;例如,当当前分支上有未提交的更改时,请参阅 Checkout another branch


.gitignore做什么

有两件有用的事情.gitignore(或任何其他排除文件,如.git/info/exclude),还有一种危险的事情:

  • 首先,有整体git add操作,例如git add --allorgit add .git add *6 或者,就此而言,您可以列出类似*.pycin的文件模式.gitignore,然后仍然运行git add file.pyc。这里发生的事情很简单:如果文件不在 Git 的索引中,并且名称在排除文件中,git add则不要添加它。

    这意味着如果一个文件当前未被跟踪——请参阅下面的未跟踪定义——它保持未被跟踪。但如果该文件已经在 Git 的索引中,则该.gitignore条目无效

  • 其次,当你运行时git status,Git 经常会抱怨各种文件未被跟踪。在排除文件中列出名称或模式可以停止抱怨

我们马上就会谈到危险的事情。现在让我们定义跟踪Git 中的跟踪文件是当前位于 Git 索引中的文件。 而已。真的就是这么简单。如果你的工作树中的文件现在在 Git 的索引中,它就会被跟踪。如果它现在不在 Git 的索引中,则它是未跟踪的。

请记住,Git 的索引内容发生了变化!如果你git checkout有一些提交,Git 会填写它的索引。现在跟踪这些文件。如果您git add在一个新文件上运行,该文件将进入 Git 的索引。现在跟踪该文件。如果您在一个文件上运行git rm(无论是否--cached有),该文件都来自Git的索引。该文件现在未跟踪。当然,如果您在git rm没有的情况下运行--cached,该文件也会从您的工作树中消失。7

git status除了打印当前分支的名称之外,还有什么是运行两个 git diff命令:

  • 第一个git diff当前提交与 Git 的索引进行比较。对于每个相同的文件,Git 什么也没说。对于每个不同的、新的或已删除的文件,Git 表示该文件已暂存以进行提交,以及被修改、添加或删除。
  • 第二个git diff将 Git 索引中的内容与工作树中的内容进行比较。对于每个相同的文件,Git 什么也没说。对于不同的文件,Git 说该文件不是为提交而暂存的。这里有点不寻常的是,对于文件,Git 将这些文件称为untracked。当然,最后一点是因为未跟踪文件的定义。

列出一个文件.gitignore会让你对未跟踪的内容git status 闭嘴。它对实际的跟踪性根本没有任何影响!它只是关闭了抱怨。

在(或其他一些排除文件)中列出文件名或模式的最后一件事.gitignore是事情有点危险。这使 Git 有权销毁这样的文件:

  • 假设您正在提交a123456...,其中没有.DS_Store文件,并且您的工作树中有一个文件.DS_Store。即,.DS_Store当前未跟踪
  • 您现在发出一个git checkout命令来检查 commit 4321cab...,其中确实有一个.DS_Store文件。

要提取 commit 4321cab...,Git 必须将一个.DS_Store文件放入 Git 的索引中,然后将该文件复制到您的工作树中。您的工作树中已经有一个.DS_Store文件。该文件将被覆盖。

通常,Git 会停下来抱怨:嘿,如果我提取 commit 4321cab...,我会销毁你的.DS_Store文件! 如果它是宝贵的数据,这使您有机会将其移开。但是如果你将文件列为ignorable,Git 会随意破坏它。

由于 a.DS_Store中的数据很少被认为是宝贵的,所以这里可能没问题。但一般要小心。


6 Git 命令中的精确操作*取决于您使用的是 Unix 风格的 shell(如 )bash,还是 DOS 风格的命令解释器(如 )CMD.EXE,但 Git 本身会进行 glob 扩展,因此结果非常相似。不过,我们不会在这里介绍细微的差异。

7带有一点哲学倾向的练习:如果命名的文件ghost 不在您的工作树中并且不在 Git 的索引中,那么不存在的ghost文件是否也未被跟踪?(它不在 Git 的索引中,所以无论如何它不会在下一次提交中。)如果一个名为的文件ghostGit的索引中,但不在你的工作树中呢?该文件是否被跟踪?会在下一次提交中吗?


推荐阅读