我以前把 core.autocrlf 当成一个简单的开关:Windows 设成 true,Linux 设成 false。直到仓库里加了 .gitattributes,格式化器也固定输出 LF,Git 仍然不断提示:
warning: in the working copy of '.editorconfig', CRLF will be replaced by LF the next time Git touches it更奇怪的是,git status 有时显示上百个文件发生变化,重新加入暂存区以后又恢复干净。这件事如果只盯着 core.autocrlf,很容易越查越乱。Git 实际上同时面对三种状态:提交对象里的内容、索引里的内容和工作区里的文件。换行转换发生在哪一步,取决于路径属性和配置层级。
true 通常不是仓库自己设置的
先分清配置来自哪里。Git for Windows 安装器有一页专门询问换行符策略,默认选项是 CRLFAlways。按照 Git for Windows 的安装参数说明,它对应 core.autocrlf=true。
安装器通过 system 配置写入这个值,而不是进入每个仓库修改 .git/config。Git for Windows 安装器源码中的配置写入函数也明确使用了 git config --system。
所以,在仓库中执行:
git config core.autocrlf得到 true,只能说明当前生效值是 true,不能证明它来自 local 配置。下面这条命令才会只查询当前仓库:
git config --local --get core.autocrlf如果它没有输出并返回非零退出码,说明 .git/config 根本没有设置这个选项。
为什么安装器偏向 CRLF
这是一个照顾传统 Windows 工具的兼容性选择。
Windows 长期使用 CRLF 作为文本换行符,Linux 和 Git 仓库通常使用 LF。安装器无法预先知道用户接下来会维护 JavaScript 项目、Visual Studio 工程,还是一批只能识别 CRLF 的旧文件,于是选择了一个偏向 Windows 工作区的默认策略:仓库尽量保留 LF,检出后让本地工具看到 CRLF。
它的目标大致是:
Git 仓库和索引:LF
↓ checkout
Windows 工作区:CRLF
↓ git add
Git 索引:LF这个默认值有历史理由,但它不是仓库规范。安装器只看得到操作系统,看不到项目里有没有 Shell 脚本、容器构建、Oxfmt,也不知道团队是否要求所有平台的工作区都保持 LF。
true、input 和 false 分别做什么
Git 的 core.autocrlf 文档说明,true 相当于对所有未明确指定属性的文件使用 text=auto,并把工作区换行设为 CRLF。三个取值的差别可以这样理解:
| 配置 | 加入索引时 | 检出到工作区时 | 常见用途 |
|---|---|---|---|
true |
文本中的 CRLF 规范化为 LF | LF 转成 CRLF | 希望 Windows 工作区使用 CRLF |
input |
文本中的 CRLF 规范化为 LF | 不做输出转换 | Linux、macOS 或只想在提交方向清理换行 |
false |
不根据 core.autocrlf 自动转换 |
不根据 core.autocrlf 自动转换 |
由 .gitattributes 明确管理仓库 |
最后一行最容易误读。core.autocrlf=false 不是“禁止 Git 做任何换行转换”。它只关闭这个配置带来的自动行为;如果文件命中了 .gitattributes 中的 text 或 eol,Git 仍然会按照属性转换。
为什么不同命令查出来的值不一样
Git 配置不只有一份。常见层级包括:
- system:Git 安装目录中的系统配置,Git for Windows 安装器通常写在这里;
- global:当前 Windows 用户的全局配置;
- local:当前仓库的
.git/config。
离仓库越近的配置优先级越高。因此 system 和 global 都可以是 true,而当前仓库通过 local 设置为 false,最终生效值仍然是 false。
我现在会直接查看所有来源:
git config --show-origin --show-scope --get-all core.autocrlf输出可能类似:
system file:C:/Program Files/Git/etc/gitconfig true
global file:C:/Users/USER/.gitconfig true
local file:.git/config false再查询一次最终值:
git config --get core.autocrlf这里应该得到 false。只看 git config --global core.autocrlf,无法判断当前仓库实际采用什么配置。
仓库规则应该写进 .gitattributes
core.autocrlf 是某台电脑上的个人配置,.gitattributes 则会随仓库提交。对于多人参与或需要在 Windows、Linux 之间构建的项目,后者更适合表达仓库规则。
一份常见配置是:
* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlfGit attributes 文档说明,text 属性会让文本在加入索引时规范化为 LF,eol 则决定检出到工作区时使用哪一种换行符。上面的配置让普通文本在各平台都使用 LF,Windows 批处理文件仍然使用 CRLF。
如果项目还包含必须保留原始字节的文件,可以明确排除:
*.png -text
*.jpg -text
*.pdf -texttext=auto 本来就会尝试识别二进制文件,这些规则不是所有仓库都必须写。只有自动识别不可靠,或者项目希望把边界写得非常明确时才需要补充。
.editorconfig 管的是另一件事
.gitattributes 决定 Git 如何把内容放入索引和检出工作区,却不能要求编辑器保存时使用哪一种换行符。编辑器应通过 .editorconfig 统一:
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true如果项目使用 Oxfmt,还应让格式化器使用相同的值:
{
"endOfLine": "lf",
"insertFinalNewline": true
}这几份配置看起来重复,处理的阶段却不同:编辑器负责保存文件,格式化器负责重写内容,Git 负责索引和检出。只配置其中一个,其他工具仍可能写出另一种结果。Oxfmt 的其他接入问题,我放在《用 Oxfmt 统一 TypeScript 项目的代码格式》里继续讨论。
警告不等于文件损坏
看到下面这条警告:
CRLF will be replaced by LF the next time Git touches it它表达的是当前状态与目标策略不同:工作区文件是 CRLF,Git 判断该路径应当规范化为 LF。git add 本身不一定改写工作区文件,但索引会使用规范化后的内容,后续检出或其他 Git 操作可能重写工作区。
反方向的提示:
LF will be replaced by CRLF the next time Git touches it说明当前策略准备让工作区使用 CRLF,常见原因是 core.autocrlf=true,并且该路径没有更明确的 eol=lf 属性。
偶尔在调整配置后看到一次提示,不算故障。编辑、格式化、加入暂存区时反复来回转换,才说明工具之间没有统一。
git status 干净也可能是 CRLF
Git 比较文本文件时会先按照属性规范化内容。于是索引中是 LF、工作区实际是 CRLF,两边规范化后仍然相同,git status 可以保持干净。
检查物理换行状态可以使用:
git ls-files --eol -- .editorconfig一种可能的输出是:
i/lf w/crlf attr/text=auto eol=lf .editorconfigi/lf:索引使用 LF;w/crlf:工作区文件当前使用 CRLF;attr/text=auto eol=lf:该路径实际命中的属性。
也可以单独检查属性来源:
git check-attr text eol -- .editorconfig这两个命令比重复修改 core.autocrlf 更有用。它们回答的是“这个文件现在怎样”和“这个路径应该怎样”。
已有仓库怎样统一
对于使用现代编辑器、格式化器和 Linux 构建环境的跨平台项目,我现在采用下面这组配置:
git config --local core.autocrlf false* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf同时让 .editorconfig 和格式化器输出 LF。这样 system 或 global 中遗留的 core.autocrlf=true 不必急着删除,仓库级配置会覆盖它,其他依赖 CRLF 的旧仓库也不会受到影响。
如果 .gitattributes 是后来加入的,已有文件不会凭空全部重写。先把业务改动提交或另行妥善保存,确认工作区干净,再执行:
git add --renormalize .
git status --short
git diff --cached --stat
git diff --cached --checkGit 官方也在跨平台换行规范化示例中使用 git add --renormalize .。它会更新暂存区,因此要查看完整暂存差异,并把这次规范化单独提交:
git diff --cached文件数量很多并不自动意味着代码被改坏,但也不能因为“应该只是换行”就跳过审查。尤其要确认生成文件、二进制文件和有特殊换行要求的脚本是否被正确分类。
安装时应该选哪一个
如果只是给自己的 Windows 电脑安装 Git,而接下来维护的主要是现代跨平台项目,我会在安装器中选择 CRLFCommitAsIs,也就是 core.autocrlf=false,然后让每个仓库通过 .gitattributes 决定换行策略。
如果工作内容依赖要求 CRLF 的传统 Windows 工具,保留安装器默认的 CRLFAlways 也合理。新仓库仍然可以用 local 配置覆盖它。
问题不在于 Git for Windows 为什么至今还默认 true。安装器只能提供一个兼容面较大的起点。真正的问题是把这个起点误认为所有仓库都必须遵守的规则。先看配置来自哪一层,再看具体路径命中了什么属性,换行警告就没有那么神秘了。