为什么 Git 想要将我的行尾更正为 CRLF,即使我希望它们位于 LF 中?

Why does Git want to correct my line endings to CRLF, even though I want them to be in LF?

在一个比较大的项目中,使用了checkout CRLF和commit LF的策略。为此,我的系统使用:

git config --global core.autocrlf true

然而,当提交文件时,在本例中为 .gitattributes 文件,返回警告:

LF would be replaced by CRLF in .gitattributes

.gitattributes 文件本身包含行 * text=auto !eol 并且文件本身使用 LF 行结尾。

为什么会这样?为什么 Git 告诉我要小心,因为它会将 LF 转换为 CRLF,即使我希望此文件在存储库中以 LF 结尾规范化?

我肯定遗漏了一些非常明显的东西,因为我已经经历过:

还有更多,但这仍然没有像我想象的那样工作。

让我们分几个部分来看:

  • !eol在这里没有作用。这会将 eol 设置为 unspecified,但这已经是默认值,未指定的 eol 值不会禁用 LF-to-CRLF t运行slation.

  • 由于您确实指定了 text=auto,Git 将检查 .gitattributes 的内容是否显示为文本或二进制,当然它们应该显示为是文本。

因此这个特定的条目告诉 Git 它 应该 .gitattributes.

上执行 t运行slations

同时,意识到行尾 t运行s 形式是一般的清洁和涂抹过滤器概念的特例是很有用的。 VonC's accepted answer at your third linksmudge 过滤器 的工作方式有很好的描述,但缺少 clean 过滤器的工作方式,让我们深入了解进入这个,有一点背景。

Git-ified ("freeze-dried") vs work-tree ("rehydrated") 文件和索引

Git的正常1原子存储单元是提交。提交包含源代码树的完整快照(加上提交 元数据,我不会在此处介绍)。出于许多充分的原因,提交中的文件以压缩、冻结、只读和 Git-only 存储格式保存。我最近开始将这些文件称为 冷冻干燥 。这有助于将它们与您实际使用的文件区分开来。

与 Git 的内部键值对象数据库中的所有内容一样,这些提交及其文件都是只读的。这意味着它们将永远保留(或者只要提交本身继续存在),这对于存档非常有用,但对于完成任何 new 工作完全没用。所以 Git 必须提供一种方法来 "rehydrate" 文件,将它们变成你可以使用的普通文件。

您的 work-tree 是 Git 放置再水化文件的地方。它们有它们的普通形式,在普通名称下的普通文件中。您计算机上的每个程序都可以处理它们,您可以随心所欲地操纵它们。

Git 可以 到此为止:您将拥有冻结的提交文件和可延展的工作树文件,Git 将构建 new 从工作树提交。 Mercurial,它在很多方面与 Git 非常相似,确实 到此为止。但是 Git 不止于此。取而代之的是,它继续将中间人混合在一起,坐在 之间 当前冻结的提交和工作树。这个中介就是Git的index。 Git 有时将此称为 暂存区 缓存 ,具体取决于 Git 文档的谁/哪一部分做电话。不过,这三个都是同一个实体的名称。

索引/暂存区只是保存每个文件的额外副本。此额外副本的 格式 是冻干的、内部的、仅 Git 的存储格式。这种格式的文件会在具有相同文件的所有提交之间自动共享,因此这意味着当中的副本索引是相同作为任何提交中的副本,它实际上 共享。

这也意味着 git commit,它必须冷冻干燥每个文件以永久存储它,实际上几乎是零工作要做:文件 已经冷冻干燥! 冷冻干燥过程发生在更早的时候,当你 运行 git add。这就是 Git 速度的主要原因。这也是 为什么 Git 一直要求你 git add2 请注意,这意味着当你 运行 git commit, Git 甚至不需要查看你的工作树。(它仍然可以快速完成一半-git status 运行 默认情况下,为您的提交消息创建评论文本。)


1我在这里说 normal 因为 Git 还提供了对简单键值存储的低级访问,通过什么它调用 blob 对象。但是,要使用它,您必须使用一些所谓的 plumbing 命令,而不是那些至少在理论上对用户友好的命令。 :-)

2Mercurial,它使用工作树作为建议的下一次提交,要求你保持hg add-ing 你的文件。完成初始 hg add 后,hg commit 会扫描您的工作树并提交您所做的任何更改。这对新手来说友好,但这也意味着在一个大项目中,当你运行hg commit时,要做好等待的准备。


索引/暂存区在行结束符中的作用运行sformations

请记住,索引存储每个文件的冻干 Git 化副本。这意味着索引到工作树 "rehydration" 步骤是一个 很棒的 地方,可以用来完成您想要完成的任何 t运行 信息。这就是链接答案中的涂抹过滤器的用武之地:涂抹过滤器可以修改提交的文本,以便工作树文本更有用。

同样,工作树到索引 "freeze-dry" 步骤——当你 运行 git add 时发生的步骤——是一个 伟大的 地方做你想做的任何 t运行sformations。这就是干净过滤器的用武之地:干净过滤器可以删除不应该进入存储库中实际提交的内容。

行尾 t运行sformations,在 Git 中,只是干净和污迹过滤器的特例。一个冻干的存储库文件可以有任何你喜欢的行结尾。3 当我们Git 复制那个文件来自索引/暂存区,工作树,在git checkout期间,我们可以Git更改那些例如,从 LF-only 到 CRLF 的行结尾。当我们 Git 复制那个文件 工作树, 索引/暂存区,我们可以 Git 更改 那些行尾从 CRLF 到 LF-only。

这是文本文件的 CRLF t运行格式的默认设置。这些 t运行sformations 会将仅 LF 冻干文件更改为 CRLF 再水化文件,并将 CRLF 再水化文件更改为仅 LF 冻干文件。

应该 每当 Git 检测到这可能会做一些与已经完成的事情不同的事情时,就会收到警告。因此,假设您的工作树 .gitattributes 中的文件现在 具有仅 LF 行尾。进一步假设 index/staging-area 中的提交 and/or 中的冻干副本也有仅 LF 行尾。并假设指令说 index -> work-tree 应该将 LF-only 更改为 CRLF:为什么,那么,有些东西很奇怪,并且 Git 应该警告。

我发现这些警告有时很容易触发。我无法将其固定到特定 Git 版本中的特定情况,因为我自己会尽力 永远,永远不要让 Git fiddle 使用我的数据 .我希望工作树副本每次都与冻干副本相匹配,因为我避免了需要愚蠢的行结束特殊性的操作系统。但以上是一般规则,您现在收到的警告是有道理的:实际的冻干文件 and 工作树文件 all 现在只有 LF 行结尾,但您的设置告诉 Git 来自 .gitattributes 的文本应该在您的工作中转换为具有 CRLF 行结尾-树.


3Linus Torvalds 要求您应该喜欢仅使用 LF 的行结尾。 :-) 开个玩笑,Git 有点喜欢这个。如果你禁用所有 t运行sformations——通过根本不启用 CRLF,或者通过将所有文件标记为 -text,Git 将存储——永久!——你说的任何行结尾。如果您随后 改变主意 ,您将 卡在您已经冻结的行尾 因为 任何提交中的任何内容都不会changed. 如果这些提交是错误的,你唯一能做的就是停止使用它们。您可以制作新的、改进的、更正的并改用它们。

我认为正是这些 "frozen committed copy is wrong because it has CRLF endings" 情况通常会触发伪造的 CRLF 行结束警告问题。因为我实际上并没有使用行尾-t运行sforming代码,所以很难确定。