首页 > 解决方案 > Git在提交时没有将行尾转换为LF

问题描述

即使在设置时,core.autocrlf=true我们仍然看到提交为 CRLF 而不是 LF 的行尾(^M运行时我可以看到符号git diffand git log -p

这有时会导致合并冲突,因为不同的开发人员在他们的编辑器中使用不同的设置。

我们如何在一个非常活跃的存储库环境中以最小的未来冲突来解决这个问题?

标签: gitgit-commitline-endings

解决方案


我通常建议.gitattributes在这里使用,而不是设置core.autocrlf(不是我实际处理这个,但 Git 项目的人就是这样做的,大概他们知道)。

这不会解决您的问题,但它应该有助于避免将来出现问题。

要处理合并问题,请考虑运行:

git merge -X renormalize

和/或设置merge.renormalizetrue.

龙:为什么

值得指出的是,Git在提交时从不进行任何转换。 实现 CRLF-to-LF-only 转换的机制不在,而是在. 要了解原因,我们必须从一些 Git 基础知识开始:git commitgit add

  • 每个提交都有每个文件的完整快照(Git 在您或任何人进行提交时知道的)。(每个提交也有一些元数据,但这与这个特定问题无关。)
  • 任何提交,一旦做出,就永远无法改变。一旦某个文件在提交中,它就是不可侵犯的。
  • 存储在提交中的文件不存储为普通文件。相反,它们以一种特殊的、只读的、仅限 Git 的、压缩的(有时是高度压缩的)和去重的形式存储。重复数据删除处理了这样一个事实,即大多数提交大多使用与其他早期提交相同的文件副本。但这也意味着您实际上无法处理/处理这些文件的副本。
  • 提交的快照不是来自文件的工作树副本,而是来自Git 的文件“副本”,因为它们出现在 GIt 的index中。

该索引也称为staging area,就用户如何使用它而言,这是一个更好的名称,尽管 Git 使用它的方式不止于此(这就是为什么它也有名称缓存的原因,给它三个名称:缓存索引暂存区)。无论如何,这些额外的文件“副本”存在于 Git 的索引中。我在这里把“副本”放在引号中,因为索引中的内容已经是 Git 使用的特殊形式。这些不是普通文件,也不能编辑(但可以替换它们)。

相反,每个文件都有第三个副本。这第三个副本一个普通文件。这些是您可以查看和编辑的文件。问题是这些文件不在 Git 中。例如,它们是在or期间或在使用or时Git 中提取的。git checkoutgit switchgit resetgit restore

在提取过程中,Git 有两个选择:它可以保留文件,也可以更改它。更改可以包括用 CRLF 行结尾替换仅 LF 行结尾。您现在有一个可以查看的文件。如果您选择提取文件以使其具有 CRLF 行结尾,则此时您还配置了 Git,以在您让 Git 替换索引副本时进行“撤消 CRLF 行结尾”更改。

什么git add是告诉 Git:使索引副本与工作树副本匹配。 如果您更改了工作树副本,Git 现在将压缩和 Git-ify 工作树副本,并使用它来替换索引中的副本。不幸的是,对于 CRLF-line-ending-fixing,如果 Git 认为不需要替换索引副本,Git 什么也不做。1

不幸的是,在决定是否需要替换某个文件的索引副本时,Git 既不检查core.autocrlf也不检查任何设置。.gitattributes因此,更改其中任何一个都不算是对任何文件的“更改”。2 在 Git 2.16 及更高版本中,git add --renormalize帮助告诉 Git:嘿,你这个笨蛋,我改变了我的 EOL 转换,所以即使我没有改变它,添加一个文件也会改变它。 在 2.16 之前的 Git 版本中,如果您想要做的只是修复行尾,您必须让 Git 相信您更改了文件。(有很多方法可以做到这一点,但让我们假装你有git add --renormalize。)

将 Git 的索引视为保存提议的下一次提交。当你运行时git commit,Git 只是简单地对索引进行快照。因为它已经保存了 Git 化的文件,所以速度很快。

无论如何,这里的最终结果是这样的:

  • 在 Git 将文件从 Git 的索引(或使用git restore,从提交)复制到您的工作树时,Git 会应用“使文件对您个人有用”的行尾更改。

  • 在 Git 将文件从工作树复制到 Git 的索引时,Git 应用“使文件标准化以用于存储库”更改。

运行时,Git 使用Git 索引git commit中的副本进行提交。因此,无论索引副本中出现什么行尾,这些行尾都是在新提交中进入永久副本的行尾。 不到那些副本,但可以。git ls-files


1实际上将一个文件重新压缩成 Git 格式需要做很多工作,所以 Git 尽可能巧妙地避免它。这就是 Git 的索引存在的原因之一。其他版本控制系统没有之一。其他版本控制系统速度较慢。这里的问题只是 Git 认为这种工作避免过于频繁

2当然,如果你修改.gitattributes了,Git 会意识到.gitattributes被修改了。只是它永远不会延伸到思考:哦,嘿!也许这意味着其他文件已更改 EOL 设置。


core.autocrlf对比.gitattributes

core.autocrlfusing和 using.gitattributes指定存储库中的行尾格式之间存在许多差异。最大和最明显的当然core.autocrlf是每个人都必须在他们的个人.git/config$HOME/.gitconfig任何他们喜欢放置的地方进行设置,但它.gitattributes是一个已提交的文件

作为一个提交的文件有很多后果:特别是,当你签出一些提交时,你会得到现有的文件。每个提交中都有该文件的副本——嗯,每次提交.gitattributes都在 Git 的索引中时,无论是谁做出了提交,做出了提交——当你签出那个提交时,Git 服从那个提交的.gitattributes设置。当您git add提交文件时,Git 会遵循您工作树的.gitattributes设置,因此您可以更改设置和git add文件(包括).gitattributes,并且您更新的设置将应用进入下一次提交。

重要的是,在列出文件时.gitattributes您可以控制. 如果文件是二进制文件,您可以告诉 Git 该文件xyz是二进制文件。如果文件是文本,您可以告诉 Git 该文件abc是文本文件。您可以说*.jsor*.py是文本并且*.jpg是二进制的。当您使用 时core.autocrlf,您只是在使用 Git猜测。Git 可能会猜错,并对二进制文件进行 CRLF 更改,或者不对文本文件进行更改。

有关放入.gitattributes文件的详细信息,请参阅gitattributes 文档

重整化选项

当你git merge用来做三路合并时,3有三个输入提交:当前提交、你通过命令行选择的提交和合并基础。您可以通过使用固定的行尾进行新提交来规范化当前提交中的行尾。如果你真的需要,你可以在命令行上检查你将要选择的提交,规范化它的行尾,然后也提交它,然后在命令行上使用那个。但是您实际上无法修复基于合并的提交:Git 根据提交图(提交之间的链接存储在提交的元数据中)自行找到这个提交。

行尾很重要git diff,合并取决于两个差异(从合并基础到您的提交,从合并基础到您选择的提交)。所以有时需要修复合并基础,以及当前提交和其他提交。renormalize 选项正是这样做的。这样,就没有必要做不可能的事——修复历史提交的行尾。需要重新规范化的合并会慢一些,但这总比不进行要好。


3请记住,这git merge可能会执行快进合并,这根本不是合并。


推荐阅读