.DS_Store 文件在 .gitignore 中,但它不断弹出状态

.DS_Store file is in .gitignore, but it keeps popping up in status

我搜索了..我阅读了我可以在这里找到的关于相似甚至相同问题的所有答案。我看了教程。当它发生在某人的终端上时,它看起来超级简单和合乎逻辑。在我的,它只是不工作。显然,我一定错过了一些非常明显的东西。 我提交了 .gitignore,但是 .DS_Store 仍然出现在状态中。然后我删除了 .DS_Store 使用 git rm --cached .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

这里的(相当轻微的)问题是您有一定数量的 existing 提交,其中 .DS_Store 存在。请注意,当前状态指示至少有两个这样的文件,一个在树的顶层,一个在 assets:

deleted:    .DS_Store
modified:   assets/.DS_Store

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

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

由于 macOS Finder 会在文件丢失时创建一个新的 .DS_Store 每当它在 Finder window 中显示目录时,此特定操作在 中足够安全这个特殊情况。但是,如果您还需要对它们使用 git rm --cached,那么对于 other 文件,有几件事情需要注意,这可能会使这变得更加棘手。

可选阅读:为什么这么复杂

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

好的,我们给这东西取了三个名字。这告诉我们什么?嗯,一方面,它告诉我们 Git 的索引 非常重要 。事实上,这绝对是至关重要的。但为什么?为什么 Git 坚持要一遍又一遍地把它的索引推到我们脸上?最终,那个 问题的真正答案只是“这是一个深思熟虑的选择”——但值得研究让 Linus Torvalds 做出这个选择的因素。结果是从 commits.

开始

Git 最后,都是关于提交的。这与 files 无关,尽管从某种意义上说,文件包含在每个提交中。这也与 b运行ch names 无关,尽管 b运行ch names 对帮助你是必要的(和 Git)找到 提交。但是,提交 存储库中的历史记录。它们保存文件,并形成我们有时称为 b运行ches 的东西(参见 What exactly do we mean by "branch"?)。

每个提交都由一个丑陋的大哈希 ID 编号进行编号。每个提交存储两件事:

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

在提交的元数据中,每个提交都包含一些较早提交的哈希ID,这就是使提交充当历史记录的原因。我们不会在这里深入细节,但这就是 b运行ches 实际工作的方式。

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

因此,所有存储在 提交的文件都是read-only。它们也被压缩(以节省 space)和 de-duplicated(以节省 space 和时间)。如果连续两次提交共享 1000 个文件中的 999 个,他们 字面上 共享文件:只有一个 changed 文件必须进入存储后来的承诺。但这意味着 committed 文件对于完成任何新工作完全无用:

  • 您无法更改它们,并且
  • 大多数程序甚至无法读取它们。

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

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

  • 已提交,未更改历史 个文件;一个
  • 一个工作区(工作space或工作树或任何你喜欢的),你可以在那里完成工作。

在Git的情况下,工作区实际上是您的,您可以随意使用。这意味着您可以在其中创建您 想要 Git 到 save-for-all-time 的文件。您可能会认为,这就是 .gitignore 的用武之地。这个假设并不是 错误的 ,而是 不完整的


1存储媒体失败,在现实世界中。大多数故障都会被检测到,但也有可能——通常声称约为 1016 中的 1 或更好——可能会遗漏某些故障,从而返回错误数据。 Google 做了分析,发现实际错误率往往比声称的要高。


索引

Git 真正需要的是要提交的文件列表。也就是说,假设您正在 work-tree 中工作。您创建了一堆文件——可能是数百个、数千个或其他。 其中两个 文件应作为新文件进入下一个 提交,其余文件应被忽略。

一种可能的处理方法是只拥有一个“忽略这些文件”文件,并从“忽略这些文件”文件中未列出的每个文件自动生成 files-to-be-committed 列表。但是如果你尝试一下,你会发现它是 error-prone。 Git 和几个类似的版本控制系统使用明确的“添加一些文件”命令来 添加 它们到文件列表。

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

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

(事实上你可以做到这一切。但这意味着你需要让其他人——所有其他拥有你的存储库克隆的人——停止使用旧的承诺支持新的和改进的承诺。)

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

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

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

  • git commit命令懒得看你的work-tree.2 相反,它只是在 运行 git commit 时打包 索引中的任何内容。这非常快,因为那些内部 blob 哈希 ID 是 Git 存储文件的方式:事实上,它们已经存在,pre-compressed 和 pre-de-duplicated.

  • git add命令相当于:压缩和de-duplicate这些文件并将它们放入您的索引中,替换任何以前的同名文件,或者如果没有以前的文件.3

    则创建一个新条目
  • git rm命令意味着从你的索引和我的work-tree中删除文件。添加 --cached 意味着 保留我的 work-tree 副本

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

这一切归结为一个相对简单的陈述:索引始终保持您的提议的下一次提交,否则保持合并冲突还没有解决所以当前不可能进行提交。 如果我们忽略合并冲突的情况,您可以将索引视为持有您提议的下一次提交。

当检查一些现有的提交时,Git 从该提交的 填充它的索引 ,然后使用填充的索引用文件填充您的 work-tree .这意味着 Git 现在已准备好进行 new 提交,这将与当前提交完全匹配。4


2为了可用性,这很久以前做了一点改变:git commit 现在 运行s git status 内部,并产生一个 commented-out git status 提交消息中您可以编辑的部分。

3事实上,git add也可以表示使索引匹配,如果你删除一个work-tree文件,git add可以删除那个文件的索引副本。例如:rm path/to/file; git add path/to/file 是 运行 宁 git rm path/to/file.

的 long-winded 方式

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


到目前为止的总结

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

一旦你添加了一些文件,然后,文件 继续进入新的提交 直到你明确地 删除 它。无论文件名是否在 .gitignore 文件中,都是如此。要使该文件真正消失,您必须 将其删除。那么当前提交和下一次提交之间的区别将包括 删除 文件。

那么:做什么 列出文件名、目录名、模式,或者任何你可以在 .gitignore 文件中列出的东西,在 .gitignore 做?有什么好处?


5这条规则有一些例外;参见,例如 Checkout another branch when there are uncommitted changes on the current branch.


.gitignore 的作用

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

  • 首先是en-massegit add操作,比如git add --all或者git add .或者git add *.6 或者,就此而言,您可以在 .gitignore 中列出类似 *.pyc 的文件模式,然后 运行 git add file.pyc 无论如何。这里发生的事情很简单:如果文件不在 Git 的索引中,并且名称在排除文件中,git add 不会添加它。

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

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

我们马上就会谈到危险的事情。现在让我们定义 trackedGit 中的跟踪文件是当前在 Git 的索引中的文件。 就是这样。真的就这么简单。如果您 work-tree 中的某个文件现在 Git 的索引 中,则它会被跟踪。如果它现在 Git 的索引 中不存在,则它未被跟踪。

记住 Git 的索引内容会改变!如果你 git checkout 一些提交,Git 填充它的索引。现在跟踪这些文件。如果您 运行 git add 在一个新文件上,该文件将进入 Git 的索引。现在跟踪该文件。如果您 运行 git rm—有或没有 --cached—在一个文件上,该文件来自 out Git 的索引。该文件现在 未跟踪 。当然,如果你 运行 git rm 没有 --cached,那个文件也会从你的 work-tree 中消失。7

git status 的作用是 运行 two git diff 命令:

  • 第一个 git diff 当前提交 与 Git 的索引进行比较。对于相同的每个文件,Git 什么都不说。对于每个不同、新的或删除的文件,Git 表示该文件暂存以提交,并进行修改,添加或删除。
  • 第二个 git diff 将 Git 的索引中的内容与您的 work-tree 中的内容进行比较。对于相同的每个文件,Git 什么都不说。对于 不同 的文件,Git 表示该文件 未暂存提交 。什么是这里不寻常的是,对于 new 的文件,Git 调用这些文件 untracked。 当然,最后一点是因为未跟踪文件的定义。

.gitignore 中列出文件会使 git status 闭嘴 关于 untracked-ness。它对实际的 tracked-ness 根本没有任何影响!它只是让抱怨闭嘴。

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

  • 假设您正在提交 a123456...,其中 没有 有一个 .DS_Store 文件,并且有一个 .DS_Store 文件中的 work-tree。也就是说,.DS_Store 目前 未被追踪
  • 您现在发出 git checkout 命令来检出提交 4321cab...,其中 确实 有一个 .DS_Store 文件。

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

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

由于 .DS_Store 中的数据很少被认为是珍贵的,所以在这里可能没问题。但总的来说要小心。


6Git 命令中 * 的精确操作取决于您是否使用 Unix-style shell 例如 bash,或 DOS-style command-interpreter 例如 CMD.EXE,但是 Git 本身会进行 glob 扩展,所以结果非常相似。不过,我们不会在此处介绍细微的差异。

7有点哲学倾向的练习:如果名为 ghost 的文件不 在您的 work-tree 并且不在 Git 的索引中,non-existent ghost 文件是否也未被跟踪? (它不在 Git 的索引中,所以无论如何它都不会在下一次提交中。)那么 的名为 ghost 的文件呢?在 Git 的索引中,但不在您的 work-tree 中?这个文件被跟踪了吗?它会在下一次提交中吗?