bigywater.tech
Git · 15

换行符:autocrlf 与 .gitattributes 的 text 属性

换行符问题到底怎么回事?这篇用 git ls-files --eol 看暂存区(i/)和工作区(w/)的换行,对照 autocrlf 的 true、input、false 以及 .gitattributes 的 text、eol、binary,再看 --renormalize 和 safecrlf。

i/ 与 w/
git ls-files --eol 同时显示暂存区和工作区的换行,是排查换行问题的主要工具
属性优先
.gitattributes 的 text / eol / binary 比每个人的 autocrlf 设置优先,而且随仓库走
--renormalize
给已提交的 CRLF 文件补上 text 属性后,重新规范会产生一个改动每一行的提交

默认不管

实测 · 本机执行输出
  $ git config --get core.autocrlf || echo "(未设置)"; git config core.eol || echo "core.eol 未设置(默认 native,macOS 上是 LF)"
  (未设置)
  core.eol 未设置(默认 native,macOS 上是 LF)
  $ git add -A && git ls-files --eol
  i/lf    w/lf    attr/                 	unix.txt
  i/crlf  w/crlf  attr/                 	win.txt
  blob 原始字节: b'line1\r\nline2\r\n' b'line1\nline2\n'

没有任何设置时,git 不转换换行,字节怎么存怎么取。git ls-files --eol 的 i/ 是暂存区里的换行风格,w/ 是工作区里的,attr/ 是生效的属性。

autocrlf=true

实测 · 本机执行输出
  $ git add -A 2>&1; git ls-files --eol
  warning: in the working copy of 'unix.txt', LF will be replaced by CRLF the next time Git touches it
  i/lf    w/lf    attr/                 	unix.txt
  i/lf    w/crlf  attr/                 	win.txt
  blob 原始字节(win.txt / unix.txt): b'line1\nline2\n' b'line1\nline2\n'
  $ git commit -q -m x; rm win.txt unix.txt; git checkout -- win.txt unix.txt; git ls-files --eol
  i/lf    w/crlf  attr/                 	unix.txt
  i/lf    w/crlf  attr/                 	win.txt
  检出后工作区文件的字节: b'line1\r\nline2\r\n' b'line1\r\nline2\r\n'
  $ git status --short; echo "(status 为空:工作区的 CRLF 与 blob 的 LF 被视为相同)"
  (status 为空:工作区的 CRLF 与 blob 的 LF 被视为相同)

添加时把 CRLF 转成 LF,检出时再把 LF 转成 CRLF,工作区是 CRLF、仓库里是 LF。git status 认为两者相同,不会报改动。适合 Windows。

autocrlf=input

实测 · 本机执行输出
  $ git add -A 2>&1; git ls-files --eol
  warning: in the working copy of 'win.txt', CRLF will be replaced by LF the next time Git touches it
  i/lf    w/lf    attr/                 	unix.txt
  i/lf    w/crlf  attr/                 	win.txt
  blob 原始字节: b'line1\nline2\n' b'line1\nline2\n'

只做添加时的转换,检出时保持 LF。适合 Linux / macOS,防止别人带来的 CRLF 进入仓库。

.gitattributes

实测 · 本机执行输出
  $ git add -A 2>&1; git ls-files --eol
  warning: in the working copy of 'a.txt', CRLF will be replaced by LF the next time Git touches it
  warning: in the working copy of 'b.bat', LF will be replaced by CRLF the next time Git touches it
  warning: in the working copy of 'c.sh', CRLF will be replaced by LF the next time Git touches it
  i/lf    w/lf    attr/                 	.gitattributes
  i/lf    w/crlf  attr/text             	a.txt
  i/lf    w/lf    attr/text eol=crlf    	b.bat
  i/lf    w/crlf  attr/text eol=lf      	c.sh
  i/crlf  w/crlf  attr/-text            	d.bin
  i/crlf  w/crlf  attr/                 	e.dat
  blob 原始字节:
    a.txt: b'line1\nline2\n'
    b.bat: b'line1\nline2\n'
    c.sh: b'line1\nline2\n'
    d.bin: b'line1\r\nline2\r\n'
    e.dat: b'line1\r\nline2\r\n'
  $ git commit -q -m x; rm b.bat c.sh a.txt; git checkout -- b.bat c.sh a.txt; git ls-files --eol
  i/lf    w/lf    attr/                 	.gitattributes
  i/lf    w/lf    attr/text             	a.txt
  i/lf    w/crlf  attr/text eol=crlf    	b.bat
  i/lf    w/lf    attr/text eol=lf      	c.sh
  i/crlf  w/crlf  attr/-text            	d.bin
  i/crlf  w/crlf  attr/                 	e.dat

text 启用规范化,eol=crlf / eol=lf 指定检出时的换行,binary(即 -text)禁止任何转换。因为随仓库提交,它比每个开发者自己的 autocrlf 更可靠。

改属性之后

实测 · 本机执行输出
  $ git ls-files --eol; printf '*.txt text\n' > .gitattributes; git add .gitattributes; git status --short
  i/crlf  w/crlf  attr/                 	old.txt
  A  .gitattributes
   M old.txt
  $ git add --renormalize . ; git status --short; git diff --cached --stat; git ls-files --eol
  A  .gitattributes
  M  old.txt
   .gitattributes | 1 +
   old.txt        | 4 ++--
   2 files changed, 3 insertions(+), 2 deletions(-)
  i/lf    w/lf    attr/                 	.gitattributes
  i/lf    w/crlf  attr/text             	old.txt

给已经提交了 CRLF 的文件补 text 属性,不会自动改历史;git add --renormalize . 把暂存区按新属性重写,会产生一个几乎每一行都改的提交,最好单独提交并写清楚。

safecrlf

实测 · 本机执行输出
  $ git add mix.txt 2>&1; git ls-files -s mix.txt; echo "(退出情况见上)"
  fatal: LF would be replaced by CRLF in mix.txt
  (退出情况见上)
  $ git config core.safecrlf warn; git add mix.txt 2>&1; git ls-files --eol mix.txt
  warning: in the working copy of 'mix.txt', LF will be replaced by CRLF the next time Git touches it
  i/lf    w/mixed attr/                 	mix.txt
  $ git add bin.dat 2>&1; git ls-files --eol bin.dat
  i/-text w/-text attr/                 	bin.dat

混合换行的文件无法无损往返转换。core.safecrlf=true 直接拒绝添加,warn 只警告。含 NUL 字节的文件会被识别成二进制,不转换。

分步演示

换行符
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。macOS 上 core.eol 默认 native 即 LF;文件用 printf 写入 \r\n;i/ w/ 来自 git ls-files --eol
    • 没有验证:Windows 上 core.eol 的 native 取值与实际表现;编辑器自己的换行设置(.editorconfig)与 git 的交互;大文件、非 UTF-8 编码(core.autocrlf 对 UTF-16 的影响);git diff 中 ^M 的显示(core.whitespace);ident、filter 等其它属性与 text 的组合
    京ICP备2026067502号-1京公网安备11010502063487号