处理Git中CRLF(回车换行)的策略是什么?

683

我尝试使用CRLF结尾的行提交文件,但失败了。

我在Windows电脑上花费了一整天的时间尝试不同的策略,几乎要放弃使用Git而改为尝试使用Mercurial

如何正确处理CRLF行结尾?

9个回答

846

距离我提出这个问题已经将近四年,我终于找到一个完全让我满意的答案

具体详情请参见github:help中关于处理换行符的指南。

Git允许您直接使用.gitattributes文件中的text属性为存储库设置行尾属性。该文件被提交到存储库并覆盖了core.autocrlf设置,确保所有用户的行为都是一致的,而不管他们的git设置如何。

因此

这样做的好处是,您的行尾配置现在可以随着您的存储库一起传输,您不需要担心协作者是否具有正确的全局设置。

这里是一个.gitattributes文件的示例:

# Auto detect text files and perform LF normalization
*        text=auto

*.cs     text diff=csharp
*.java   text diff=java
*.html   text diff=html
*.css    text
*.js     text
*.sql    text

*.csproj text merge=union
*.sln    text merge=union eol=crlf

*.docx   diff=astextplain
*.DOCX   diff=astextplain

# absolute paths are ok, as are globs
/**/postinst* text eol=lf

# paths that don't start with / are treated relative to the .gitattributes folder
relative/path/*.txt text eol=lf

有方便的准备好的 .gitattributes 文件集合可供最流行的编程语言使用。这对于入门非常有用。

一旦您创建或调整了.gitattributes,您应该执行一次性的行尾重新规范化

请注意,GitHub Desktop 应用程序可以在您打开项目的 Git 存储库后建议并创建一个.gitattributes文件。要尝试这个功能,请单击齿轮图标(位于右上角)> 存储库设置... > 行尾和属性。系统会要求您添加推荐的.gitattributes,如果您同意,该应用程序还将对存储库中的所有文件进行规范化处理。

最后,Mind the End of Your Line这篇文章提供了更多背景知识,并解释了Git在相关问题上的发展。我认为这是必读的。

你可能有团队中使用EGit或JGit(像Eclipse和TeamCity这样的工具)提交其更改的用户。那么你就没有办法了,因为@gatinueta在此答案的评论中解释道:

如果你的团队中有人使用EGit或JGit工具,那么这个设置将无法完全满足你,因为这些工具会忽略.gitattributes并愉快地检入CRLF文件https://bugs.eclipse.org/bugs/show_bug.cgi?id=342372

一个技巧可能是让他们在另一个客户端中提交更改,比如SourceTree。我们当时的团队喜欢这个工具,因为它适用于许多用例。

谁说软件容易呢? :-/


9
可以分享一下 Windows 的 .gitattributes 文件吗? - Colonel Panic
6
如果你的团队中有使用 Egit 的人员,那么这个设置可能不能完全满足你的需求,因为 Egit 会忽略 .gitattributes 文件,然后开心地提交 CRLF 文件。参考链接:https://bugs.eclipse.org/bugs/show_bug.cgi?id=342372。 - gatinueta
27
对于Windows环境下,我通常倾向于将全局变量core.autocrlf=false——我喜欢在任何地方使用LF(换行符),但是一些Windows工具(例如Visual Studio)坚持在某些文件中使用CRLF(回车和换行符),甚至在某些文件中混合两种格式。不改变行结束符是最安全的选择。如果你知道自己在做什么,我可能会使用core.autocrlf=input,并针对Windows上对行结束符敏感的项目进行特殊处理。正如其他人指出的那样,每个像样的文本编辑器现在都支持LF结束符。实际上,我认为core.autocrlf=true可能会引起更多麻烦,而不是防范问题。 - Adrian
1
更具体地说,这是一个JGit问题。这意味着使用JGit的TeamCity直接忽略了.gitattributes。 - sdds
3
我建议使用 *.sh text eol=lf。这会将文本文件行尾的换行符规范化为 LF 格式,以确保在不同操作系统和编辑器之间的兼容性。 - Christophe Roussy
显示剩余13条评论

132

不要转换行尾符。这不是版本控制系统的工作,它只是存储和版本化数据。现代文本编辑器都可以读取两种类型的行尾符。


32
同意。如果您遇到了不一致的行尾问题,最好的解决方法是向那些使用错误编辑器设置的人大喊,直到他们修复为止。 - Mike F
147
不同意。所有平台上的本地换行符是一种方便之举。 - Jonas Byström
27
对于除了CRLF以外的任何内容,使用Visual Studio都会非常麻烦。 - Brett Ryan
32
Git有一个选项可以不转换行尾,即autocrlf=false。除非您正在进行跨平台开发,比如说Mono,否则在Windows下运行时最好保持为false,如果您将为Mono开发开源项目,则应将其设置为true。 - Chris Nicola
32
行尾符问题会导致计算出错误的差异。因此,原答案是错误且具有误导性的。 - cos
显示剩余15条评论

90

你几乎总是希望使用autocrlf=input,除非你确切知道自己在做什么。

以下是一些额外的上下文:

如果你喜欢 DOS 结尾符,则应该设置为 core.autocrlf=true,而如果你更喜欢 Unix 换行符,则应该设置为 core.autocrlf=input。在这两种情况下,你的Git存储库中只会有 LF,这是正确的方式。唯一不能设置为core.autocrlf=false 的场景是:自动启发式可能会错误地将某些二进制文件检测为文本,从而破坏您的文件,为此引入了一个名为core.safecrlf的选项来警告用户发现不可逆转的更改。 实际上,存在两种不可逆转的更改可能性——混合换行符的文本文件,其中规范化很重要,因此可以忽略此警告,或者(非常不太可能)Git 错误地将您的二进制文件检测为文本。然后,您需要使用属性来告诉 Git 这个文件是二进制的。

上面的段落最初摘自 gmane.org 上的一个主题,但该网站已经关闭。


37
为什么这是正确的事情? - Artem Tikhomirov
38
core.autocrlf=true 是一个糟糕的想法。我曾经因为这个选项遇到过许多麻烦,而且每次克隆仓库时都需要记得设置它。 - Luís Oliveira
33
除非您知道自己在做什么,否则不要使用autocrlf=true。如果您在DOS/Win中开发,则将autocrlf=false设置为最佳选项,可以保持远程和本地库之间的行结尾相同,在几乎所有情况下都是最好的选择。请注意,将其设置为true可能会导致问题。 - Chris Nicola
14
如果你的开发人员有使用Windows和跨平台项目,其中一些跨平台开发人员在OSX或Linux上工作,那么最好的选择不是autocrlf=true吗? - Brett Ryan
28
带有保留意见的点赞。简介段落没有帮助。core.autocrlf=input是标准答案。对于大多数用例,core.autocrlf=truecore.autocrlf=false过于热衷于处理换行符(当然是以相反但同样可怕的方式),因此本质上是具有破坏性的。"Git for Windows" 真的应该默认为“按原样提取,提交 Unix 风格的换行符”(即 core.autocrlf=input)作为其换行策略。它没有。所以在这里,我们还在无休止地争论这个问题,竟然已经到了 2015 年。 - Cecil Curry
显示剩余10条评论

64

在混合环境(Microsoft + Linux + Mac)中,有两种替代策略可以使行结束符保持一致:

A. 全局所有版本库的设置

  1. 将整个目录树中的所有文件转换为同一格式

  2. find . -type f -not -path "./.git/*" -exec dos2unix {} \;
    git commit -a -m 'dos2unix conversion'
    
  3. 在Linux/UNIX上设置core.autocrlfinput,在MS Windows上设置为true(可以设置为存储库级别或全局级别)

    git config --global core.autocrlf input
    
  4. 可选地,将core.safecrlf设置为true(停止)或warn(提示)可以添加额外的保护,比较反转换行符转换是否会导致相同的文件。

  5. git config --global core.safecrlf true
    

B. 或者按仓库设置

  1. 将整个目录树中的换行符转换为同一格式

find . -type f -not -path "./.git/*" -exec dos2unix {} \;
git commit -a -m 'dos2unix conversion'
  • 向您的代码库添加一个.gitattributes文件

    echo "* text=auto" > .gitattributes
    git add .gitattributes
    git commit -m 'adding .gitattributes for unified line-ending'
    
  • 不用担心二进制文件,Git应该能够很好地处理它们。


    了解有关safecrlf/autocrlf变量的更多信息


    7
    全局方式 == 适用于所有仓库的设置并忘记 vs. 每个仓库 == 不需要其他人更改其全局配置。 - lukmdo
    5
    dos2unix 是一个命令行工具,根据你的系统可能需要额外安装。它的作用是将文本文件从 DOS 或 Windows 格式转换为 Unix 格式。 - lukmdo
    4
    它们不是互斥的,你可以同时使用这两种方法。此外,在使用dos2unix时要非常小心 - 存在破坏.git/index的风险,而且我们不需要将其应用于每个文件。最好使用类似于 find ./ -name "*.html" 这样的命令,并指定你想应用它的文件。 - cregox
    6
    警告:在运行 find 命令之前,请注意:Git for Windows 自带的 dos2unix 命令在无参数情况下具有特殊(在我看来是愚蠢和危险的)行为:它不会改变为 UNIX 的换行格式,而是切换换行格式(DOS <-> UNIX)。请注意。 - leonbloy
    4
    另外警告:不要将您的.git文件夹进行DOS2UNIX转换。只是说一下。 - hakre

    19

    --- 更新 3 --- (与更新 2 不冲突)

    考虑到 Windows 用户更喜欢在文本文件中使用 CRLF,而 Linux/Mac 用户更喜欢使用 LF,从一个仓库维护者的角度提供答案:

    对我来说,最好的策略(解决问题较少)是:即使你正在开发一个仅限于 Windows 的项目,也要将所有文本文件保留为LF格式,并且让客户自由选择他们喜欢的换行符风格,只要他们选择了一个 core.autocrlf 属性值,这个值将遵循你的策略(仓库中的 LF)在提交时暂存文件。

    暂存是许多人在尝试理解换行策略时所困惑的内容。在选择正确的 core.autocrlf 属性值之前,理解以下几点至关重要:

    • 添加文本文件以进行提交(将其暂存)就像在 .git/ 子目录内的另一个位置复制该文件,并转换行尾(取决于客户端配置中的 core.autocrlf 值)。所有这些都是本地完成的。
    • 设置 core.autocrlf 就像提供一个回答问题的答案(在所有操作系统上完全相同的问题):“Git 客户端应该:
      • a. 在从远程获取仓库更改时将 LF 转换为 CRLF 吗?
      • b. 在添加文件以进行提交时将 CRLF 转换为 LF 吗?
    • 可能的答案(值)包括:
      • false: "不要做任何事情",
      • input: "只做 b"
      • true: "同时做 a 和 b"
      • 请注意,没有 "只做 a" 的选项

    幸运的是

    • git客户端默认设置(Windows:core.autocrlf: true,Linux/Mac:core.autocrlf: false)与LF-only-repo策略兼容。
      意思是:Windows客户端在检出存储库时默认转换为CRLF,并在添加提交时转换为LF。而Linux客户端默认不进行任何转换。这理论上使您的存储库仅限于LF。

    不幸的是:

    • 可能会有GUI客户端不遵守git的core.autocrlf
    • 可能会有人不使用值来遵守您的lf-repo策略。例如,他们使用core.autocrlf=false并添加了一个带CRLF的文件以进行提交。

    为了尽快检测到由上述客户端提交的非lf文本文件,您可以按照---更新2---所述的操作进行(git grep -I --files-with-matches --perl-regexp '\r' HEAD,在使用--with-libpcre标志编译的客户端上)

    这里有个问题:作为存储库维护者,我保持git.autocrlf=input,这样我就可以通过再次添加提交来修复任何错误提交的文件。然后我提供一个提交文本:“修复错误提交的文件”。

    .gitattributes而言,我不依赖它,因为有更多的ui客户端不理解它。我只使用它为文本和二进制文件提供提示,并可能标记一些应在任何地方保持相同行结尾的异常文件:

    *.java          text !eol # Don't do auto-detection. Treat as text (don't set any eol rule. use client's)
    *.jpg           -text     # Don't do auto-detection. Treat as binary
    *.sh            text eol=lf # Don't do auto-detection. Treat as text. Checkout and add with eol=lf
    *.bat           text eol=crlf # Treat as text. Checkout and add with eol=crlf
    

    问题: 但我们为什么对换行符处理策略感兴趣呢?

    答案: 为了避免在提交时出现单个字母更改,看起来像5000行更改,只是因为执行更改的客户端在添加之前将整个文件从crlf转换为lf(或反之亦然)。当涉及到冲突解决时,这可能会非常痛苦。或者在某些情况下,这可能是不合理冲突的原因。


    --- 更新2 ---

    Git客户端的默认设置在大多数情况下都有效。即使您只有Windows客户端、Linux客户端或两者都有。它们是:

    • windows: core.autocrlf=true 表示在检出时将行转换为CRLF,并在添加文件时将行转换为LF。
    • linux: core.autocrlf=input 表示在检出时不要转换行(因为文件应该以LF提交),并在添加文件时将行转换为LF(如果需要)。 (-- 更新3 -- :似乎这默认为false,但同样可以正常工作)

    该属性可以在不同的范围内设置。我建议在--global范围内明确设置,以避免描述在最后的一些IDE问题。

    git config core.autocrlf
    git config --global core.autocrlf
    git config --system core.autocrlf
    git config --local core.autocrlf
    git config --show-origin core.autocrlf
    

    此外,我强烈不建议在Windows上使用git config --global core.autocrlf false(如果你只有Windows客户端),与git文档中所提出的相反。将其设置为false会在repo中提交带有CRLF的文件。但真的没有理由这样做。你永远不知道是否需要与linux用户共享项目。而且这是每个加入项目的客户机使用默认值之外的额外步骤。
    现在针对某些特殊情况的文件(例如*.bat *.sh),您可以使用.gitattributes来检查它们是否使用LF或CRLF进行检出。
    对于我来说,最佳实践总结如下:
    - 确保每个非二进制文件都在git repo上以LF提交(默认行为)。 - 使用以下命令确保没有文件使用CRLF进行提交:git grep -I --files-with-matches --perl-regexp '\r' HEAD(注意:在Windows客户端上仅通过git-bash工作,在Linux客户端上仅在使用--with-libpcre编译时才能正常运行./configure)。 - 如果通过执行上述命令找到任何此类文件,请将其更正。这涉及以下步骤(至少在Linux上): - 设置core.autocrlf=input (--- 更新3 ---) - 更改文件 - 恢复更改(文件仍显示为已更改) - 提交它 - 仅使用最少的.gitattributes - 指示用户将core.autocrlf描述为其默认值。 - 不要完全依赖于.gitattributes的存在。 IDE的git客户端可能会忽略它们或以不同的方式处理它们。
    如上所述,一些内容可以添加到git属性中:
    # Always checkout with LF
    *.sh            text eol=lf
    # Always checkout with CRLF
    *.bat           text eol=crlf
    

    我认为对于二进制文件,使用自动检测之外的其他安全选项是可行的,这些选项如下:
    - -text(例如*.zip*.jpg文件):不会被视为文本。因此不会尝试进行换行符转换。通过转换程序可能还是有差异的。 - text !eol(例如*.java*.html文件):被视为文本,但没有设置EOL样式偏好。因此使用客户端设置。 - -text -diff -merge(例如*.hugefile文件):不被视为文本。没有差异/合并。
    --- 上一次更新 ---
    一个痛苦的例子是,某些客户端将文件错误地提交:
    netbeans 8.2(Windows上),除非您已经显式设置全局core.autocrlf,否则会错误地提交所有具有CRLFs的文本文件。这与标准git客户端行为相矛盾,并在更新/合并时引起许多问题。即使您已经为项目添加了正确的.gitattributes文件,netbeans仍然会出现相同的行为。
    在提交后使用以下命令可以帮助您尽早发现您的git存储库是否存在行结束符问题:git grep -I --files-with-matches --perl-regexp '\r' HEAD 我已经花了很多时间来找到最好的使用.gitattributes的方法,最终意识到我不能依赖它。不幸的是,只要存在基于JGit的编辑器(无法正确处理.gitattributes),安全的解决方案就是在编辑器层面上强制使用LF。
    您可以使用以下反CRLF消毒剂:

    我同意你的看法,这是最好的方法,没有 LF 支持的编辑器不应该被使用。但是要小心你的 .gitattributes 行,在 Git < 2.10 中会产生意想不到的后果,请参考 https://dev59.com/Tl0b5IYBdhLWcg3wO_I_#29508751。 - phk
    糟糕...我有很多回答都支持使用 git config --global core.autocrlf false,并建议仅通过 .gitattributes 指令来处理行尾符。 - VonC

    12

    使用core.autocrlf=false可以防止我在Visual Studio 2010项目中签出文件时立即将所有文件标记为已更新。开发团队的其他两名成员也使用Windows系统,因此不涉及混合环境,但是与存储库一起提供的默认设置始终会在克隆后立即将所有文件标记为已更新。

    我想关键是找到适合您环境的CRLF设置。特别是由于在我们Linux系统上的许多其他存储库中设置autocrlf=true可以产生更好的结果。

    20多年过去了,我们仍然在处理操作系统之间的行结尾差异...悲哀。


    32
    @orange80,这种不平等现象很遗憾,但没有理由归咎于Windows。从极简主义的角度来看,只使用LF是有道理的;但基于CR和LF的含义,使用CRLF更加合理。"回车"指回到行首;"换行符"指向下移动一行,而不是移到下一行的行首。从语义上讲,Windows在同时使用CR和LF方面更正确:先回到行首(CR),再向下移动一行(LF)。 - Ryan Lundy
    41
    “更正确”的做法是,不再把计算机当作打字机来模拟比喻,因为事实上二者并不相同。保持打字机的比喻已经没有任何意义,因为这不是最终用户需要关注的内容,同时输入两个字符与一个字符并没有意义。 - jpswain
    1
    虽然有些晚了,但你忽略了CR和LF是光标定位工具这一事实。在当今时代,“CR”可能就等同于“光标返回”。如果我想让光标回到行首,我会告诉应用程序去做。否则,它需要停留在我放置的位置。 - EKW
    2
    此外,如果CRLF更正确,因为文本文件的换行符实际上是“向下移动一行”和“移动到行首”,那么仅使用CR将导致文本编辑器用以下行覆盖当前行。我不知道有哪些编辑器实际支持这种情况,这意味着表达CRLF和CR作为不同事物的需要并不存在。 - avl_sweden
    在DOS出现之前,这种行为非常普遍,由于Microsoft认为兼容性很重要,因此它一直保持着这种方式。这也是美国的标准方式(按照ASA),ISO允许CR+LF和LF的两种方式(所以再次证明DOS符合标准)。自六十年代以来,在这两种情况下都是如此。Multics(Unix前身)支持CR用于加粗/删除线。现在许多应用程序(包括.NET的“按行拆分”功能)都寻找这三个中的任何一个(孤立的CR、孤立的LF、CRLF),并将每个都视为行末。然而,仍有许多应用程序对文件中混合的行尾感到困惑。 - Luaan
    @avl_sweden Unicode有八个新的换行符,所有符合规范的应用程序都必须支持。但是,Unicode不再假装存在“纯文本文件”这样的东西——它精确地定义了每个字符在每个系统上的通用编码方式。遗憾的是,Unicode支持仍然相当缺乏,并且似乎在Unix世界中仍然不太受欢迎。程序员通常甚至不考虑潜在的问题,直到问题出现——我甚至看到过使用LF作为行分隔符的HTTP库,尽管文本Internet协议必须使用CRLF... - Luaan

    11

    尝试将core.autocrlf配置选项设置为true。还要查看core.safecrlf选项。

    实际上,由于(重点在此):“如果对于core.autocrlf的当前设置不是这种情况,则git会拒绝文件”,因此听起来像你的存储库中已经设置了core.safecrlf

    如果是这种情况,则您可能需要检查文本编辑器是否配置为一致使用换行符。如果文本文件包含混合的LF和CRLF行尾,那么您可能会遇到问题。

    最后,我认为简单地“使用所给出的”在Windows上使用LF终止行将引起更多问题。 Git具有上述选项,以尝试以明智的方式处理行尾,因此使用它们是有意义的。


    2
    使用 .gitattributes 文件的存储库范围设置不是更好吗?我只是在想:强制每个用户在自己的机器上处理行尾设置很不方便...或者还有其他缺点吗? - trainoasis

    9

    以下是供 WindowsVisual Studio 用户与 Mac 或者 Linux 用户共享代码的两种选择。详细解释请参考gitattributes手册

    * text=auto

    在你的repo的.gitattributes文件中添加如下内容:

    *   text=auto
    

    这将使仓库中所有使用LF行结尾的文件得到规范化。根据你所用的操作系统(core.eol设定),工作树中的文件会被规范化为Unix系统的LF或Windows系统的CRLF。这是Microsoft .NET仓库使用的配置。例如:
    Hello\r\nWorld
    

    将始终在存储库中标准化为:

    Hello\nWorld
    

    在结账时,Windows中的工作树将被转换为:

    Hello\r\nWorld
    

    在结账时,Mac中的工作树将保留为:
    Hello\nWorld
    

    注意:如果您的存储库已包含未标准化的文件,则下次对这些文件进行任何更改时,git status 将显示这些文件已完全修改,这可能会给其他用户合并其更改带来麻烦。有关更多信息,请参见更改行尾之后刷新存储库

    core.autocrlf = true

    如果在 .gitattributes 文件中未指定 text,Git 将使用 core.autocrlf 配置变量来确定是否应转换该文件。

    对于 Windows 用户,git config --global core.autocrlf true 是一个很好的选择,因为:

    • 仅当添加到存储库时,文件才会被标准化为 LF 行尾。如果存储库中存在未标准化的文件,则此设置将不会触及它们。
    • 所有文本文件都将转换为工作目录中的 CRLF 行尾。

    这种方法的问题是:

    • 如果您是具有 autocrlf = input 的 Windows 用户,则会看到一堆带有 LF 行尾的文件。对团队的其他成员没有危害,因为您的提交仍将使用 LF 行尾进行标准化。
    • 如果您是具有 core.autocrlf = false 的 Windows 用户,则会看到一堆带有 LF 行尾的文件,并且您可能会将带有 CRLF 行尾的文件引入存储库中。
    • 大多数 Mac 用户使用 autocrlf = input,可能会从具有 core.autocrlf = false 的 Windows 用户那里获得带有 CRLF 文件行尾的文件。

    2
    你的Windows用户命令应该是 git config --global core.autocrlf true,而不是 git config --global core.autocrl true - JellicleCat

    6
    这只是一个“变通”的解决方案:
    通常情况下,请使用与git一起提供的解决方案。这些在大多数情况下都很有效。通过设置.gitattributes,如果您在Windows和Unix系统上共享开发,则强制为LF。
    在我的情况下,有超过10个程序员在Windows上开发项目。该项目以CRLF检入,而且“没有选项可以强制转换为LF”。
    某些设置在我的计算机上内部编写,没有对LF格式产生任何影响;因此,在每次小文件更改时,某些文件会全局更改为LF。
    我的解决方案:
    Windows设备:
    让一切保持原样。不要担心任何事情,因为你是默认的Windows“孤独”开发者,并且你必须像这样处理:“世界上没有其他系统,是吗?”
    Unix设备:
    1. Add following lines to a config's [alias] section. This command lists all changed (i.e. modified/new) files:

      lc = "!f() { git status --porcelain \
                   | egrep -r \"^(\?| ).\*\\(.[a-zA-Z])*\" \
                   | cut -c 4- ; }; f "
      
    2. Convert all those changed files into dos format:

      unix2dos $(git lc)
      
    3. Optionally ...

      1. Create a git hook for this action to automate this process

      2. Use params and include it and modify the grep function to match only particular filenames, e.g.:

        ... | egrep -r "^(\?| ).*\.(txt|conf)" | ...
        
      3. Feel free to make it even more convenient by using an additional shortcut:

        c2dos = "!f() { unix2dos $(git lc) ; }; f "
        

        ... and fire the converted stuff by typing

        git c2dos
        

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接