跳至主要內容

换行符

Sankgao约 2276 字大约 8 分钟DevOpsGit

每一个通用的版本控制系统,无论是 CVS、Subversion、Git 或是其他,都要面对换行符转换的问题。这是因为作为通用的版本控制系统要面对来自不同操作系统的文件,而不同的操作系统在处理文本文件时,可能使用不同的换行符。

不同的操作系统可能使用不同的换行符

文本文件的每一行结尾用一个或者两个特殊的 ASCII 字符进行标识,这个标识就是换行符。主要的换行符有三种:

  • CR(Carriage Return):回车,用 \r 表示,相当于十六进制的 0x0D
  • LF(Line Feed):换行,用 \n 表示,相当于十六进制的 0x0A
  • CRLF:由 CR + LF 组成,用 \r\n 表示,相当于十六进制的 0x0D 0x0A

分别用在不同的操作系统中。(以下内容摘自 http://en.wikipedia.org/wiki/Newline。)

  • CR 换行符:用于 Commodore 8 位机、TRS-80、苹果II家族、Mac OS 9 及更早版本
  • LF 换行符:用于 Multics、Unix、类 Unix(如:GNU/Linux、AIX、Xenix、Mac OS X、FreeBSD 等)、BeOS、Amiga、RISC OS 等操作系统中
  • CRLF 换行符:用于 DEC TOPS-10、RT-11 和其他早期的非 Unix,以及 CP/M、MP/M、DOS(MS-DOS、PC-DOS 等)、Atari TOS、OS/2、Microsoft Windows、Symbian OS、Palm OS 等系统中

实际上,自从苹果的 Mac OS 从第 10 版转向 Unix 内核开始,依据不同的文本文件换行符,主流的操作系统可以划分为两大阵营,一个是微软 Windows 作为一方,使用 CRLF 作为换行符,另外一方包括 Unix、类 Unix(如:Linux 和 Mac OS X 等)使用 LF 作为换行符。分属不同阵营的操作系统之间交换文本文件会因为换行符的不同造成障碍,而对于使用版本控制系统,也同样会遇到换行符的麻烦。

  • 编辑器不能识别换行符,可能会显示为特殊字符,如 Linux 上的编辑器显示的 ^M 特殊字符,就是拜 Windows 的 CRLF 换行符所赐。或者丢弃换行符,如来自 Linux 的文本文件,在 Windows 上打开可能会因为识别不了换行符,导致所有的行合并
  • 版本库中的文件被来自不同操作系统的用户改来改去,在某一次提交中换行符为 LF,在下一次提交中被替换为 CRLF,这不但会在查看文件版本间差异时造成困惑(所有的行都显示为变更),还给版本库的存储带来不必要的冗余
  • 可能会在一个文件中引入混杂的换行符,即有的行是 LF,而有的行是 CRLF。无论在那个操作系统用编辑器打开这样的文件,都会或多或少感到困惑
  • 如果版本控制系统提供文本文件换行符的自动转换,在 Windows 平台进行版本库文件导出为源码包并发布,当该源码包被 Linux 用户下载,编译、运行可能会有问题,反之亦然

文本文件和二进制文件的判别,是换行符转换的基础

几乎所有的版本库控制系统都采用这样的解决方案:对于文本文件,在版本库中保存时换行符使用 LF,当从版本库检出到工作区时,则根据平台的不同或者用户的设置的不同,对文本文件的换行符进行转换(转换为 LF、CR 或 CRLF)。

为什么换行符转换要特意强调文本文件呢?这是因为如果对二进制文件(程序或者数据)当中出现的换行符进行上述转换,会导致二进制文件被破坏。因此判别文件类型是文本文件还是二进制文件,是正确进行文件换行符转换的基础。

有的版本控制系统,如 CVS,必须在添加文件时人为的设定文件类型(用 -kb 参数设定二进制文件),一旦用户忘记对二进制文件进行标记,就会造成二进制文件被破坏。这种破坏有时藏的比较深,例如:在 Linux 上检出文件一切正常,因为版本库中被误判为文本文件的图形文件中所包含字符 0x0A 在 Linux 上检出没有改变,但是在 Windows 上检出会导致图形文件中的 0x0A 字符被转换为 0x0D 0x0A 两个字符,造成图片被破坏。

有的版本控制系统可以自动识别文本文件和二进制文件,但是识别算法存在问题。例如:Subversion 检查文件的前 1024 字节的内容,如果其中包含 NULL 字符(0x00),或者超过 15% 是非 ASCII 字符,则 Subversion认定此文件为二进制文件(参见 Subversion 源代码 subversion/libsvn_subr/io.c 中的 svn_io_detect_mimetype2 函数)。这种算法会将包含大量中文的文本文件当作二进制文件,不进行换行符转换,也不能进行版本间的比较(除非强制执行)。

Git 显然比 Subversion 更了解这个世界上文字的多样性,因此在判别二进制文件上没有多余的判别步骤,只对 blob 对象的前 8000 个字符进行检查,如果其中出现 NULL 字符(0x00)则当作二进制文件,否则为文本文件(参见 Git 源代码 xdiff-interface.c 中的 buffer_is_binary 函数)。Git 还允许用户通过属性文件对文件类型进行设置,属性文件设置优先。

Git 默认并不开启文本文件的换行符转换,因为毕竟 Git 对文件是否是二进制文件所做的猜测存在误判的可能。如果用户通过属性文件或者其他方式显式的对文件类型进行了设置,则 Git 就会对文本文件开启换行符转换。

使用 Git 配置变量控制换行符转换

在 Git 1.7.4 之前,用属性文件的方式来设置文件的换行符转换,只能逐一为版本库进行设置,如果要为本地所有的版本库设定文件换行符转换就非常的麻烦。Git 1.7.4 提供了全局可用的属性文件,实现了对换行符转换设定的全局控制。现在介绍另外一个方法,即通过配置变量 core.autocrlf 来开启文本文件换行符转换的功能。例如:执行下面的命令,对配置变量 core.autocrlf 进行设置。

git config --global core.autocrlf true

默认 Git 不对配置变量 core.autocrlf 进行设置,因此在也没有通过属性文件指定文件类型的情况下,Git 不对文件进行换行符转换。但是将配置变量 core.autocrlf 设置为下列值时,会开启 Git 对文件类型的智能判别并对文本文件执行换行符转换。

  • 设置配置变量 core.autocrlftrue

    即通过 Git 对文件类型的自动判定,对文本文件进行换行符转换。在版本库的 blob 文件中使用 LF 作为换行符,而检出到工作区时无论是什么操作系统都使用 CRLF 为换行符。注意当设置了 core.autocrlftrue 时,会忽略 core.eol 的设置,工作区文件始终使用 CRLF 作为换行符,这对于 Windows 下的 Git 非常适合,但不适用于 Linux 等操作系统。

  • 设置配置变量 core.autocrlfinput

    同样开启文本文件的换行符转换,但只是在文件提交到版本库时,将新增入库的 blob 文件的换行符转换为 LF。当从版本库检出文件到工作区,则不进行文件转换,即版本库中文件若是采用 LF 换行符,检出仍旧是 LF 作为换行符。这个设置对 Linux 等操作系统下的 Git 非常适合,但不适合于 Windows。

配制 core.safecrlf 捕捉异常的换行符转换

无论是用户通过属性文件设定文件的类型,还是通过 Git 智能判别,都可能错误的将二进制文件识别为文本文件,在转换过程中造成文件的破坏。有一种情况下破坏最为严重,就是误判的文件中包含不一致的换行符(既有 CRLF 又有 LF),这就会导致保存到版本库中的 blob 对象无论通过何种转换方式都不能还原回原有的文件。

Git 提供了名为 core.safecrlf 的配置变量,可以用于捕捉这种不可逆的换行符转换,提醒用户注意。将配置变量 core.safecrlf 设置为 true 时,如果发现存在不可逆换行符转换时,会报错退出,拒绝执行不可逆的换行符转换。如果将配置变量 core.safecrlf 设置为 warn 则允许不可逆的转换,但会发出警告。