如何让 git 理解 Mac (CR) 行结尾

How to make git understand Mac (CR) line endings

由于某些原因,我的一个文件包含旧式 Mac 行尾(在 OSX 上编辑后)。这些是 "CR"(回车 return)字符,在 git diff.

中显示为 ^M

Git 不理解它们是行结束代码(真的有多难?)并将整个文件解释为一行。

我知道我可以将文件转换为 LF 或 CRLF 结尾,然后将它们提交回去,但是由于 git 自动将我的 Windows (CRLF) 行结尾转换为 LF,我希望它也会处理 CR 行结尾。

有没有办法让 git 将 CR 解释为行结尾?

TL;DR

创建滤镜 driver 加 .gitattributes:创建 涂抹滤镜 运行s tr '\n' '\r'清理 运行s tr '\r' '\n' 的过滤器,并将有问题的文件标记为使用此过滤器。使用 LF-only 行结尾将文件存储在 Git 中。 (过滤器 driver 在 .git/config$HOME/.gitconfig 文件中定义,文件的名称或 name-patterns 进入 .gitattributes。)

如您所见,Git 非常喜欢 newline-terminated 行。 (它可以与 newline-separated 行一起使用,其中最后一行缺少终止符,但这意味着添加一行会导致对先前最后一行的更改,因为它现在有一个换行符终止符,而新的最后一行缺少换行符。)这对单个快照无关紧要,但对产生有用的差异很重要。

现代 MacOS 和其他人一样使用换行符。只有古老的 backwards-compatible 格式有 CR-only 行结尾。参见,例如,this SuperUser Stack Exchange web site posting.

Git 没有 内置 过滤器来转换到或从这样的行结尾。 Git 确实,但是,有一个 通用机制 用于在 work-tree 文件中进行更改。

请记住,当 Git 将任何文件存储在快照中时,该文件由 Git 所称的 blob object 表示,它以一种特殊的、压缩的(有时是高度压缩的)Git-only 形式在内部存储。这种形式对任何东西都没有用 Git,所以当你以有用的形式获得文件时——例如通过 git checkout——Git将它们扩展为您计算机的常用形式。同时,任何时候你将这样的普通文件转换为 Git-only 格式,Git 会将文件压缩为 Git-only 格式。每当您使用 git add.

将文件复制回 Git 的 index 时,就会发生这种情况

当您有 work-tree 时,每个文件的索引副本都存在,就像提交的副本一样。索引副本采用相同的 Git-only 格式。这里的关键区别是提交的副本不能被改变,但是索引副本可以被改变。 运行 git commit 为索引 中的任何内容拍摄快照 ,并将其作为新提交的新快照。因此,索引充当 将进入下一次提交的内容 。使用 git checkout,您将一些现有的提交 复制到 索引中,并让 Git 将其扩展到 work-tree;然后使用 git add,您有选择地将特定索引副本替换为您已更改的 work-tree 文件的压缩版本。

与索引和 work-tree 之间的这种复制是进行 Windows-style LF-to-CRLF 转换的理想点,反之亦然,所以这就是 Git 做到了。如果你有一些 other 转换要执行,而不是直接内置到 Git,这就是你告诉 Git 执行它的地方。

涂抹并清洁过滤器

涂抹过滤器 是 Git 在将文件从压缩索引副本转换为 work-tree 副本时应用的过滤器。在这里,如果您选择用 CRLF Windows-style 行 enders-or-separators 替换换行符,Git 有一个内部转换器可以做到这一点:eol=crlfclean 过滤器 是 Git 在将文件从未压缩的 work-tree 副本转换为压缩索引副本时应用的过滤器;同样,eol=crlf 指示 Git 进行向后转换。

如果你想用 CR-only 替换换行符,你必须发明自己的转换器。假设您将整个过程称为 convert-cr:

*.csv   filter=convert-cr

(而不是 *.csv eol=crlf)。此行进入 .gitattributes(这是一个 commit-able 文件,您应该提交它)。

现在您必须定义 convert-cr 过滤器。这在 Git 配置文件中,在这里我们发现一个小缺陷:配置文件不是 commit-able。这是一个安全问题:Git 将在此处 运行 任意命令,如果我可以提交此文件并且您克隆它,您将 运行 命令 I 指定,没有机会先审查它们。所以你必须自己把它放到你的 .git/config 中,或者放到你的全局配置中(例如 git config --global --edit):

[filter "convert-cr"]
    clean = tr '\r' '\n'
    smudge = tr '\n' '\r'

现在,每当 Git 将 Git-only 格式转换为 Git-only 格式时,它会将换行符转换为 CR,并且每当 Git 将 Git-only格式,它会将CR转换为换行符。

这对现有的存储文件没有帮助

您今天拥有的任何现有快照,其中包含 \r,将以这种方式永久存储。 Git 永远不会更改任何现有的存储文件!存储的数据是珍贵且不可侵犯的。你对此无能为力。好吧,几乎 什么都没有:您可以完全丢弃这些提交,而改用新的和改进的提交。但这非常痛苦:每个提交都会记住它的 parent 提交,所以如果你如果你在你的仓库中替换了一个早期的提交,你必须替换 every child, grandchild, 等等,这样他们都会记住这个新的提交顺序. (git filter-branch 完成这项工作。)

但是,您可以指示 Git 如何 diff 现有提交中的特定文件,也可以使用 .gitattributesdiff drivers。有多种方法可以做到这一点,但最简单的是定义一个 textconv 属性,它可以转换一个 "binary" 文件——比如一个文件,其存储版本可能有 CR-only 个字符——转换为文本(line-oriented,即 newline-based)文件。这里使用的 textconv 过滤器与 smudge 过滤器完全相同。

有关详细信息,请参阅 the gitattributes documentation

自接受答案以来,引入了一种新方法。

您可以在创建 diff 之前通过特殊命令将 git diffgit log 教给 运行 文件。这是一种单向过程,仅用于生成差异,不会影响文件在磁盘或存储库中的存储方式。

创建一个名为 "cr" 的新差异驱动程序,它在计算差异之前 运行 通过 tr 处理文件。在你的 .git/config:

[diff "cr"]
    textconv = tr '\r' '\n' <

或者:

git config diff.cr.textconv "tr '\r' '\n' <"

然后告诉 git 使用您的 .gitattributes 使用它(例如,对于所有 .csv 文件):

*.csv diff=cr

请注意,这 影响差异。它不会帮助你合并!