Git 在提交时不将行结尾转换为 LF

Dmi*_*117 2 git line-endings git-commit

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

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

我们怎样才能在非常活跃的存储库环境中解决这个问题并最大限度地减少未来的冲突?

tor*_*rek 7

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

\n

.gitattributes示例:

\n
*.ts        text eol=lf\n\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
git merge -X renormalize\n
Run Code Online (Sandbox Code Playgroud)\n

和/或设置merge.renormalizetrue.

\n

长:为什么

\n

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

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

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

\n

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

\n

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

\n

所做git add的就是告诉 Git:使索引副本与工作树副本匹配。 如果您更改了工作树副本, Git 现在将压缩并 Git-ify 工作树副本,并使用它来替换索引中的副本。不幸的是,对于 CRLF 行尾修复,如果 Git 认为不需要替换索引副本,Git 根本不会执行任何操作。1

\n

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

\n

将 Git 的索引视为保存建议的下一次提交。当您运行时git commit,Git 只是对索引进行快照。由于它已经保存了 Git 化的文件,因此速度非常快。

\n

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

\n
    \n
  • 当 Git 将文件从 Git 的索引\xe2\x80\x94 或使用git restore, 从提交\xe2\x80\x94 复制到您的工作树时,Git 将应用“使文件对您个人有用”结尾- 线路变化。

    \n
  • \n
  • 当 Git 将文件从工作树复制到 Git 的索引时,Git 会应用“使文件针对存储库进行规范化”更改。

    \n
  • \n
\n

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

\n
\n

1实际上将文件重新压缩为 Git 格式是一项艰巨的工作,因此 Git 尽可能巧妙地避免了这种情况。这就是 Git 索引存在的原因之一。其他版本控制系统在没有版本控制系统的情况下也能过得去。其他版本控制系统速度较慢。这里的问题只是 Git 认为这种逃避工作的情况太常见了

\n

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

\n
\n

core.autocrlf.gitattributes

\n

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

\n

作为一个已提交的文件会产生一系列后果:特别是,当您检查某些提交时,您会得到现有的文件。每个提交\xe2\x80\x94中都有该文件的副本,每个提交.gitattributes都是在 Git 的索引中进行的,无论是谁进行了提交,进行了提交\xe2\x80\x94,并且当您签出时对于该提交,Git 会遵守该提交的.gitattributes设置。当您git add归档时,Git 遵循您的工作树的.gitattributes设置,因此您可以更改设置和git add文件 xe2x80x94(包括.gitattributesxe2x80x94),并且您更新的设置将应用进入下一次提交。

\n

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

\n

有关文件中内容的详细信息.gitattributes,请参阅gitattributes 文档

\n

重整化选项

\n

当您用于git merge进行三向合并时,3存在三个输入提交:当前提交、您通过命令行选择的提交以及合并基础。您可以通过使用固定行结尾进行新提交来标准化当前提交中的行结尾。如果确实需要,您可以检查要在命令行上选择的提交,标准化其行结尾,然后也提交它,然后在命令行上使用该提交。但你实际上无法修复基于合并的提交:Git 根据提交图\xe2\x80\x94(存储在提交元数据中的提交之间的链接)自行找到这一提交。

\n

行结尾对 很重要git diff,合并取决于两个差异(从合并基础到您的提交,以及从合并基础到您选择的提交)。因此有时需要修复合并基础,以及当前提交和其他提交。重新规范化选项正是这样做的。这样,就不需要执行不可能的\xe2\x80\x94 来修复历史提交的行结尾。需要重新规范化的合并会稍微慢一些,但这比根本不进行要好。

\n
\n

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

\n