换行符问题到底怎么回事?这篇用 git ls-files --eol 看暂存区(i/)和工作区(w/)的换行,对照 autocrlf 的 true、input、false 以及 .gitattributes 的 text、eol、binary,再看 --renormalize 和 safecrlf。
$ 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/ 是生效的属性。
$ 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。
$ 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 进入仓库。
$ 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.dattext 启用规范化,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 . 把暂存区按新属性重写,会产生一个几乎每一行都改的提交,最好单独提交并写清楚。
$ 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 字节的文件会被识别成二进制,不转换。