【问题标题】:How to make git understand Mac (CR) line endings如何让 git 理解 Mac (CR) 行尾
【发布时间】:2018-09-05 07:43:08
【问题描述】:

由于某些原因,我的一个文件包含旧式 Mac 行尾(在 OSX 上编辑后)。这些是“CR”(回车)字符,在 git diff 中显示为 ^M。

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

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

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

【问题讨论】:

  • Sublime 是我的主要文本编辑器,我发现View->Line Endings 下有一个替换行尾的选项(为什么它在“视图”下?)所以我想我会转在 git 中关闭 autocrlf 并自己管理行尾,除非有人用解决方案回答我的问题,让 git 处理所有行尾,而不仅仅是 Unix 和 Windows。

标签: git macos cross-platform compatibility line-endings


【解决方案1】:

TL;DR

创建一个过滤器驱动程序加上.gitattributes:创建一个运行tr '\n' '\r'涂抹过滤器和一个运行tr '\r' '\n'清洁过滤器,并标记文件( s) 在使用此过滤器时有问题。使用仅 LF 行结尾将文件存储在 Git 中。 (过滤器驱动程序在.git/config$HOME/.gitconfig 文件中定义,文件的名称或名称模式在.gitattributes 中。)

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

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

Git 没有内置 过滤器来转换或从此类行尾转换。然而,Git确实有一个通用机制用于对工作树文件进行更改。

请记住,当 Git 将任何文件存储在快照中时,该文件由 Git 所称的 blob 对象表示,该对象在内部存储在一个特殊的、压缩的(有时是高度压缩的)Git-只有形式。这种形式对任何东西 Git 没有用处,因此当您以有用的形式获取文件时(例如,通过git checkout),Git 会将它们扩展为您计算机的常用形式。同时,任何时候你把一个像这样的普通文件转换为仅 Git 的形式,Git 都会将文件压缩成它的仅 Git 形式。每当您使用 git add 将文件复制回 Git 的 index 时,就会发生这种情况。

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

这种与索引和工作树之间的复制是进行 Windows 风格的 LF 到 CRLF 转换的理想点,反之亦然,所以这就是 Git 执行此操作的地方。如果你有一些 other 转换要执行,而不是直接内置到 Git 中,这就是你告诉 Git 去做的地方。

涂抹并清洁过滤器

涂抹过滤器是 Git 在将文件从压缩索引副本转换为工作树副本时应用的过滤器。在这里,如果您选择用 CRLF Windows 样式的行尾或分隔符替换换行符,Git 有一个内部转换器可以做到这一点:eol=crlfclean filter 是 Git 在将文件从未压缩的工作树副本转换为压缩的索引副本时应用的过滤器;再次在这里,eol=crlf 指示 Git 进行反向转换。

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

*.csv   filter=convert-cr

(而不是*.csv eol=crlf)。这一行进入.gitattributes(这是一个可提交的文件,你应该提交它)。

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

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

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

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

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

但是,您可以指示 Git 如何diff 现有提交中的特定文件,也可以使用 .gitattributesdiff 驱动程序。有多种方法可以做到这一点,但最简单的是定义一个 textconv 属性,它将“二进制”文件(例如其存储版本可能包含 CR-only 字符的文件)转换为文本(面向行,即基于换行)文件。此处使用的 textconv 过滤器与 smudge 过滤器完全相同。

更多详情,请参阅the gitattributes documentation

【讨论】:

  • 我不确定我的代码中是如何引入 CR 行尾的 - 我还需要对此进行调查。
  • 感谢您详细易懂的回答。当我第一次遇到行尾问题时,我只想以任何行尾和git 提交文件以处理不同的行尾而不更改文件。现在我明白了它是如何产生问题的。例如,如果相同的源文件存在于不同的分支中但具有不同的行尾,那么尽管文件大小不同,git 仍必须将文件视为相同的。将行尾标准化更有意义。您的解决方案可能是我最接近我的乌托邦的解决方案。
  • 很高兴他们允许运行这样的任意命令,但如果能够提交一个配置文件,说明行尾模式是\r\n?|\n...我猜他们不过,希望避免在行拆分中使用任何模式。
  • @Andy:正则表达式在这里没有用——至少没有直接用——因为目标是进行某种转换。正则表达式在其他地方很有用,例如在执行 git merge 时(必须读取并组合两个单独的差异),但实际差异引擎出于性能原因在其中硬编码了换行符。
【解决方案2】:

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

您可以教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

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

【讨论】:

    猜你喜欢
    • 2011-06-13
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多