Git 在结帐时删除了我的 .gitignore 文件

Git is dropping my .gitignore files at checkout

当我将分支 A 签出到分支 B 时,我的 .gitignore A 中的某些文件如果不在 .gitignore 分支 B 中,则会被删除。 我试图编辑我的 .git/info/exclude 文件,但我看不出有什么不同。 我尝试使用 --assume-unchanged 和 --skip-worktree (将文件保存在 .gitignore + make add -f 中)但如果我的文件被修改我无法签出。我真的需要忽略我的文件... 您有一些技巧可以分享吗?

谢谢

不可思议

问题归结为:Git does not think .gitignore means what you think it means.

.gitignore 中列出的文件实际上并未被忽略。如果文件名与 .gitignoreexcludefiles 控件的内容匹配,则:

  • git status 和其他命令会将它从 "files to complain about as untracked" 列表中删除。也就是说,如果它实际上未跟踪的,git status会抱怨它,它不会抱怨。这个动作相当 "ignore"-y,但是还有整个“if 它实际上 is 未跟踪”的事情:路径名的事实匹配忽略条目不会使文件成为未跟踪。
  • git add 带有各种 "add all files" 标志将跳过它,如果它当前未被跟踪。 (这是三个动作中唯一真正完全 "ignore"-y 的动作。)
  • 最糟糕的是,至少从某些角度来看,是这样的:如果 Git 遵循一些会导致其覆盖文件的指令集,现在确实未被跟踪,有一个匹配的忽略条目的事实告诉Git:"Feel free to overwrite the file."换句话说,这不是一个文件列表要忽略,它是要(安全地)破坏 的文件列表。 (在这个特殊情况下这不是问题,但它是 .gitignore 的一个重要方面,而不是人们认为的意思。)

你在这里被击中的原因是因为有问题的文件实际上被跟踪了,尽管在 .gitignore.

旁白:索引,以及 "really untracked"

的含义

单词 "untracked" 或短语 "really untracked" 在上面多次出现。 Git 中未跟踪的文件有点神秘:什么 恰好 生成文件 "untracked"?答案非常简单——"remarkably"因为这个Git。 当且仅当文件不在索引中时,该文件才被取消跟踪。

(有 git add -N 特殊索引条目,但它们在 Git 的许多版本中严重损坏,因此最好避免使用它们几年。其他可能的并发症是 "the index" 本身是一个复杂的野兽。它的大部分并发症至少 意味着 是不可见的和自动的,但在实践中它们大多是 。如果您将索引视为 "this is what I am going to put in my next commit" 就可以了。还请记住,索引是在合并期间存储额外信息的地方:如果您正在处理有冲突的合并,则冲突状态为保存在索引中。)

从提交切换到提交使得 Git 写入或删除文件

当你 运行 git checkout B 从 b运行ch A 切换到 b运行ch B 时,Git 做了两件事:

  • 将 "current branch"(存储在特殊的 HEAD 文件中)从 A 更改为 B。这实际上会发生 last,当且仅当其余结帐成功时.
  • 但首先,如果两个不同的 b运行 名称不同 commitsgit checkout 必须 更改索引和工作-树内容.

您的问题出现在第一步中。 B运行ch A 命名了一些提交(通过其原始哈希 ID,7fe3291... 或其他)。 B运行ch B 还命名了一些提交(通过 its 原始哈希 ID)。如果 b运行ch B 命名 same 提交,Git 的工作很容易,因为你不是要求 Git 从一个 提交 移动到另一个,只是为了改变它对 "current branch name" 的想法。但是如果两个 b运行ches name 两个不同的提交,Git 需要同时调整索引和工作树。

请记住,索引将进入您所做的下一个 提交。如果你现在要在 b运行ch B 上,索引最好与 existing 提交匹配,无论如何或多或少。同样,工作树或多或少需要匹配。

Git 尝试将已更改但未暂存的文件保持在已更改但未暂存的状态。 (事实上​​ ,它还会尝试将已更改和暂存的文件保持在已更改和已暂存的状态。请参阅 Git - checkout another branch when there are uncommitted changes on the current branch 了解更多信息。)但是如果文件当前 tracked——现在在索引中——并且 不存在 在我们切换 没有未提交的更改,Git 必须从索引中删除文件 ,并且在此过程中, 也会从工作中删除它们-树.

显然数据丢失没有问题。这些文件在索引中并且没有未提交的更改。这意味着这些文件的索引条目 匹配 这些文件的已提交 (HEAD) 版本。 (具体来说,存储在索引中的散列与存储在与 HEAD 提交关联的树中的散列相匹配。)这反过来意味着该文件的 contents 被安全地保存在HEAD 提交。如果你想要返回内容,你可以在切换到其他提交后,简单地从现有提交中提取内容,即现在的 HEAD@{1}:

git show HEAD@{1}:path/to/file > path/to/file  # skips smudge filter

或:

git checkout HEAD@{1} -- path/to/file          # applies smudge filter

git checkout 变体,它应用污迹过滤器并进行任何行尾 CRLF 操作,还将文件写入索引,因此文件——显然 在您刚刚切换到的提交中未被跟踪,这就是 Git 删除它的原因——现在 在索引中 ,因此被跟踪。

关于潜在错误的附注

我不知道任何实际 Git 版本中的任何 实际 错误,但我使用 Git 2.10 对该帖子进行了抽查.1.当 Git 在删除文件作为切换到另一个提交的一部分之前检查文件是否有未暂存的更改时,可能会出现明显的 潜在 错误。请记住,这里安全删除或覆盖文件的测试是:

  1. 此文件是否被跟踪且未被修改,如果是,安全
  2. 这个文件是否被跟踪和修改?如果是这样,安全。
  3. 文件是否未被跟踪且可破坏?如果是这样,安全.
  4. 否则(未跟踪但无法破坏):安全。

案例 1 是潜在错误所在。我们如何测试"tracked but unmodified"? 如果您为文件设置了 --assume-unchanged--skip-worktree 标志怎么办? 这些索引标志通常使 Git 将文件视为 "unmodified".如果 Git 在 git checkout 期间遵守这些标志,它可能会覆盖或删除文件。

我对 2.10.1 进行的(轻度)测试表明,Git 在测试是否可以删除文件时将这些标志放在一边。也就是说,我修改了一个跟踪文件,但在索引中设置了该文件的位。然后我试图切换到缺少该文件的提交。 Git 给我一个错误信息。

如果Git的某些其他版本不小心遵守这里的索引标志,他们可能会删除文件即使它 匹配 HEAD 提交。在那种情况下,没有 in-Git 方法来恢复内容。