Git 行尾符治理:将 CRLF 归一化为 LF 的完整方案

kkcode
kkcode
2026-09-22阅读 20

摘要

在跨平台 Git 协作中,CRLF 与 LF 混用会导致 diff 噪声、合并冲突、CI 失败以及脚本执行异常。仅依赖 core.autocrlf 往往无法得到稳定、可复现的结果。本文给出一套以 .gitattributes 为核心的方案,确保仓库与索引中的行尾符统一为 LF,并说明迁移已有仓库、验证和 CI 强制的完整流程。

1. 问题根源

Windows 默认使用 CRLF,Unix 类系统使用 LF。Git 提供了自动转换机制,但不同平台、不同 Git 版本、不同本地配置会产生不一致行为。典型症状包括:

  • 仅行尾符变化的大面积 diff;
  • 合并时大量冲突;
  • Shell 脚本因 CRLF 导致 bad interpreter
  • 二进制文件被错误转换而损坏;
  • CI 与本地行为不一致。

2. Git 行尾转换模型

Git 涉及三个位置:

  • 工作区:开发者实际编辑的文件;
  • 索引:暂存区;
  • 仓库对象:提交历史。

理想模型是:仓库和索引中统一为 LF,工作区可按需适配。控制行尾符的主要机制如下:

  • core.autocrlf:本地自动转换开关;
  • core.eol:未指定 eol 属性时的默认行尾;
  • core.safecrlf:转换不可逆时警告或阻止;
  • .gitattributes:仓库级属性,优先级高于本地 core.* 配置。

.gitattributes 中的关键属性:

  • text:将文件视为文本并启用行尾规范化;
  • text=auto:由 Git 自动判断是否为文本;
  • eol=lf / eol=crlf:指定检出时的行尾,并隐含 text
  • binary:等价于 -text -diff,禁止行尾转换和 diff。

3. 推荐方案:以 .gitattributes 为唯一事实源

在仓库根目录创建 .gitattributes,并提交到版本库。推荐模板如下:

# 默认:自动识别文本文件,并在索引与工作区统一使用 LF
* text=auto eol=lf

# 明确要求 LF 的脚本与文本文件
*.sh text eol=lf
*.bash text eol=lf
*.js text eol=lf
*.ts text eol=lf
*.json text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.css text eol=lf
*.html text eol=lf

# Windows 批处理文件保留 CRLF
*.bat text eol=crlf
*.cmd text eol=crlf

# 二进制文件禁止转换
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.pdf binary
*.zip binary
*.gz binary
*.tar binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary

说明:

  • * text=auto eol=lf 是核心规则。它让 Git 自动识别文本文件,并强制这些文件在索引和工作区中都使用 LF。
  • 后续规则按顺序覆盖前面的规则。因此 *.bat text eol=crlf 可以覆盖默认的 LF。
  • 二进制文件必须显式标记为 binary,避免被误判为文本后转换行尾符。
  • 如果团队希望 Windows 工作区保留 CRLF,但提交时归一化为 LF,可将默认规则改为 * text=auto,并设置 core.autocrlf=falsecore.eol=native。不过,这种方式的工作区行为依赖本地配置,一致性不如强制 LF。

4. 本地 Git 配置

.gitattributes 优先级最高,但仍建议统一本地配置,避免隐式转换。推荐所有平台使用:

git config --global core.autocrlf false
git config --global core.safecrlf warn

core.autocrlf=false 禁用 Git 的自动转换,将控制权交给 .gitattributescore.safecrlf=warn 在转换不可逆时给出警告。若需要更严格,可设为 true,但迁移阶段可能因大量 CRLF 文件而中断。

不建议在 Windows 上使用 core.autocrlf=true。它会在检出时隐式将 LF 转为 CRLF,可能破坏必须使用 LF 的脚本,并与 .gitattributes 的显式规则产生冲突。

5. 迁移已有仓库

如果仓库已存在 CRLF 污染,需要一次性重新规范化。操作前确保工作区干净,避免丢失未提交修改。

# 1. 确认工作区干净
git status --porcelain

# 2. 添加 .gitattributes 并提交
git add .gitattributes
git commit -m "chore: add .gitattributes for line ending normalization"

# 3. 按新属性重新规范化索引
git add --renormalize .

# 4. 检查改动,确认仅涉及行尾符
git status
git diff --cached --stat
git diff --cached --ignore-cr-at-eol

# 5. 提交规范化结果
git commit -m "chore: normalize line endings to LF"

提交后,工作区文件可能仍保留旧行尾符。若希望工作区也统一为 LF,可在干净工作区执行:

git rm --cached -r .
git reset --hard

该命令会从 HEAD 重新检出所有文件,并应用 .gitattributes 规则。执行前必须确认没有未提交更改,否则会被覆盖。团队其他成员也可以选择重新克隆仓库,这是最安全的方式。

6. 验证与 CI 强制

使用 git ls-files --eol 查看索引和工作区的行尾符状态:

git ls-files --eol

输出示例:

i/lf    w/lf    attr/text=auto eol=lf   src/index.js
i/lf    w/crlf  attr/text=auto eol=lf   README.md

其中:

  • i/ 表示索引中的行尾;
  • w/ 表示工作区中的行尾;
  • attr/ 表示生效的属性。

i/crlfmixed,说明索引中仍存在 CRLF。可在 CI 中加入检查:

if git ls-files --eol | grep -E 'i/(crlf|mixed)' >/dev/null; then
  echo "Error: CRLF detected in Git index"
  exit 1
fi

这可以防止后续提交再次引入 CRLF。

7. 常见问题

7.1 出现 warning: CRLF will be replaced by LF

该警告表示工作区文件包含 CRLF,Git 准备在提交时将其转换为 LF。如果这符合预期,可以忽略。若希望工作区也使用 LF,请确认 .gitattributes 已生效,并重新检出文件。

7.2 .gitattributes 不生效

检查以下事项:

  • 文件是否位于仓库根目录或正确的子目录;
  • 是否已提交到仓库;
  • 是否执行过 git add --renormalize .
  • 工作区是否已重新检出;
  • 本地 core.autocrlf 是否设置为 false

7.3 二进制文件被转换

确保二进制扩展名已标记为 binary。如果仍有问题,可使用 -text 明确禁止文本处理:

*.bin -text

7.4 子模块

子模块是独立仓库,需要在子模块内部单独配置 .gitattributes 和本地 Git 设置。

7.5 全局配置与仓库配置冲突

.gitattributeseol 属性会覆盖 core.autocrlfcore.eol 的检出行为。但为避免未指定属性的文件出现意外转换,仍建议全局设置 core.autocrlf=false

8. 结论

将 CRLF 归一化为 LF 的可靠方案不是依赖单一 Git 配置,而是建立明确的仓库级规范:

  1. 使用 .gitattributes 声明文本、二进制和特殊文件的行尾规则;
  2. 所有平台统一设置 core.autocrlf=false,避免隐式转换;
  3. 对已有仓库执行 git add --renormalize . 并提交;
  4. 在 CI 中检查索引,阻止 CRLF 再次进入仓库。

这套方案可以确保提交历史、索引和跨平台协作中的行尾符行为一致、可预测、可验证。

评论数量:0