devAlice
← Multi-OS

Git 换行符 — 终结 Mac/Linux 与 Windows 之间的 CRLF 混乱

一个 .gitattributes 文件即可保证所有地方使用相同的换行符。core.autocrlf 的陷阱与正确做法。

Mac/Linux 与 Windows 共用同一仓库时,几乎必然会遇到这个问题——CRLF vs LF。PR diff 炸开成「明明什么都没改,却有 1000 行变更」,shell 脚本报 \r: command not found,Python 抛 SyntaxError: invalid character

我认为换行符问题的根本不在于 Windows 的历史遗留,而在于团队没有在 .gitattributes 中明确声明意图。以前靠 core.autocrlf 的个人设置;如今通过仓库级的 .gitattributes 声明,不论操作者用什么系统,换行符行为都一致,因为配置活在代码库而不是个人机器上。

本指南介绍如何通过 .gitattributes 在所有操作系统上统一换行符的正确方法。配置一次,永不复顾。

TL;DR

  1. 在仓库根目录添加 .gitattributes,写入 * text=auto eol=lf
  2. 关闭 core.autocrlf.gitattributes 会覆盖它
  3. 对 Windows shell 脚本强制 LF(*.sh text eol=lf
  4. 对 Windows 批处理文件强制 CRLF(*.bat text eol=crlf
  5. 统一后,运行 git add --renormalize . 重新对齐现有文件

前置条件

  • Git 2.10+(支持 .gitattributes 中的 eol=
  • 仓库被 Mac/Win 用户同时使用

1. 问题根源 — CRLF 为何挥之不去

1.1 各 OS 默认换行符

系统换行符历史
Unix · macOS · LinuxLF\n,0x0A)Unix 1970+
WindowsCRLF\r\n,0x0D 0x0A)DOS 1980+
经典 Mac OS 9CR\r1984–2001

1.2 Git 的自动转换 — core.autocrlf

检出时提交时
true(Windows 默认)LF → CRLFCRLF → LF
input(Unix 推荐)不转换CRLF → LF
false不转换不转换

问题在于:每个用户的设置不同,新机器的默认值各异,隐式转换会导致「我没改任何东西,但它变了」这类事故。

1.3 正确做法是使用 .gitattributes

.gitattributes 放入仓库,对所有人强制统一规则,并覆盖 core.autocrlf


2. .gitattributes 基础配置 — 5 分钟

仓库根目录的 .gitattributes

# 默认 — 将所有文本文件存储为 LF;自动检测文本文件
* text=auto eol=lf
 
# 显式文本文件 — 强制 LF
*.c          text eol=lf
*.cc         text eol=lf
*.cpp        text eol=lf
*.h          text eol=lf
*.hpp        text eol=lf
*.cs         text eol=lf
*.go         text eol=lf
*.java       text eol=lf
*.kt         text eol=lf
*.py         text eol=lf
*.rb         text eol=lf
*.rs         text eol=lf
*.swift      text eol=lf
*.ts         text eol=lf
*.tsx        text eol=lf
*.js         text eol=lf
*.jsx        text eol=lf
*.mjs        text eol=lf
*.cjs        text eol=lf
*.json       text eol=lf
*.jsonc      text eol=lf
*.yml        text eol=lf
*.yaml       text eol=lf
*.toml       text eol=lf
*.md         text eol=lf
*.mdx        text eol=lf
*.txt        text eol=lf
*.html       text eol=lf
*.css        text eol=lf
*.scss       text eol=lf
*.svg        text eol=lf
*.xml        text eol=lf
*.sql        text eol=lf
 
# Shell 脚本 — LF(CRLF 会导致脚本损坏)
*.sh         text eol=lf
*.bash       text eol=lf
*.zsh        text eol=lf
*.fish       text eol=lf
Dockerfile*  text eol=lf
Makefile*    text eol=lf
 
# 仅限 Windows — CRLF
*.bat        text eol=crlf
*.cmd        text eol=crlf
*.ps1        text eol=crlf
*.psm1       text eol=crlf
 
# 二进制文件 — 不转换
*.png        binary
*.jpg        binary
*.jpeg       binary
*.gif        binary
*.ico        binary
*.webp       binary
*.avif       binary
*.pdf        binary
*.zip        binary
*.gz         binary
*.tar        binary
*.7z         binary
*.exe        binary
*.dll        binary
*.so         binary
*.dylib      binary
*.woff       binary
*.woff2      binary
*.ttf        binary
*.otf        binary
*.mp3        binary
*.mp4        binary
*.mov        binary
 
# 静默 diff — 文本但无需 diff
*.lock       text eol=lf -diff
pnpm-lock.yaml text eol=lf -diff
package-lock.json text eol=lf -diff
yarn.lock    text eol=lf -diff
Cargo.lock   text eol=lf -diff

⚠️ PowerShell 文件(*.ps1)需要 CRLF。Windows PowerShell 偶尔会拒绝仅含 LF 的 ps1 文件。


3. 应用到现有仓库 — 5 分钟

添加 .gitattributes 并提交;然后重新对齐已有文件中错误的换行符

3.1 renormalize

# 在仓库根目录执行
git add .gitattributes
git commit -m "feat: add .gitattributes baseline"
 
# 按 .gitattributes 重新对齐所有文件
git add --renormalize .
git status   # 查看变更
git commit -m "chore: renormalize line endings"

--renormalize 不修改工作区,只重写 index。其他人拉取后会自动对齐。

3.2 大型仓库 / 活跃协作

--renormalize 会产生一个巨大的 PR。建议提前协调:

  • 设定时间窗口(「X 点之后暂停推送」)
  • 合并 renormalize 提交后,让所有人执行一次 git pull
  • 或在特性分支上执行 git rebase origin/main 来吸收变更

3.3 小型仓库 / 个人项目

合并为一个 PR 即可。


4. 用户全局设置 — 驯服 core.autocrlf — 1 分钟

.gitattributes 使 core.autocrlf 在该仓库中无关紧要。但对于没有 .gitattributes 的旧仓库/外部仓库,它仍然有影响。建议:

# macOS / Linux
git config --global core.autocrlf input
# 检出时不转换;提交时 CRLF → LF
 
# Windows
git config --global core.autocrlf input
# 即使在 Windows 上,也优先使用 input — 你提交的文件始终是 LF。别人的 CRLF 同样会被规范化。
 
# 或仅依赖 .gitattributes
git config --global core.autocrlf false

为什么在 Windows 上也用 input:不受 OS 默认值影响,始终输出 LF。

core.eol

git config --global core.eol lf

检出时的默认换行符。.gitattributes 中的 eol= 优先级更高。


5. 编辑器设置 — 对齐 IDE

VS Code(.vscode/settings.json 或用户设置):

{
  "files.eol": "\n",
  "files.insertFinalNewline": true,
  "files.trimTrailingWhitespace": true
}

JetBrains:Settings → Editor → Code Style → Line separator → Unix and macOS (\n)

Vim:

" ~/.vimrc
set fileformats=unix,dos
set fileformat=unix

6. 验证 — 换行符是否真的是 LF?

6.1 单个文件

# macOS/Linux
file scripts/build.sh
# ASCII text  ← OK
file scripts/build.sh | grep -q CRLF && echo has CRLF
 
# 或
od -c scripts/build.sh | head -3
# 行尾仅有 \n。如果看到 \r \n,则存在 CRLF。

6.2 整个仓库

# 查找含 CRLF 的文件
git grep --cached -lI $'\r' -- ':!*.bat' ':!*.cmd' ':!*.ps1'

-I 排除二进制文件。排除已知使用 CRLF 的文件(ps1、bat)。

6.3 添加 CI 门禁

GitHub Actions:

- name: Check no CRLF in LF files
  run: |
    crlf=$(git grep --cached -lI $'\r' -- ':!*.bat' ':!*.cmd' ':!*.ps1' || true)
    if [ -n "$crlf" ]; then
      echo "CRLF found in LF-expected files:"
      echo "$crlf"
      exit 1
    fi

7. 常见问题与解决方法

7.1 .sh 在 Windows 上报 ^M: command not found

提交了含 CRLF 的 shell 脚本。快速修复:

dos2unix scripts/build.sh
# 或
sed -i 's/\r$//' scripts/build.sh

根本修复:在 .gitattributes 中添加 *.sh text eol=lf + 执行 git add --renormalize

7.2 Python SyntaxError: invalid character

.py 文件含 CRLF + UTF-8 BOM。在 .gitattributes 中强制 LF + 在编辑器中移除 BOM。

sed -i '1s/^\xEF\xBB\xBF//' file.py

7.3 .gitattributes 未实际生效

  • .gitattributes 不在根目录而在子目录 — 仅对该子目录生效
  • 文件未经 renormalize — 现有换行符保持不变(必须执行一次 renormalize)

7.4 IDE 重新保存时转为 CRLF

添加 .editorconfig

# .editorconfig(位于仓库根目录)
root = true
 
[*]
end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
 
[*.{bat,cmd,ps1}]
end_of_line = crlf

VS Code、JetBrains、Vim、Sublime 等均会自动读取 .editorconfig

7.5 Windows 编辑器自动添加 BOM

PowerShell Out-File 默认输出 UTF-16 BOM。强制使用 UTF-8:

$content | Out-File "file.txt" -Encoding utf8 -NoNewline
# 或 PowerShell 7+
$content | Out-File "file.txt" -Encoding utf8NoBOM

8. 修复现有仓库的 PR 检查清单

  • 添加 .gitattributes 基础配置(第 2 节)
  • 添加 .editorconfig(第 7.4 节)
  • git add --renormalize . + 提交
  • 添加 CI CRLF 检查(第 6.3 节)
  • 通知团队 — 「拉取后,确认 git config --global core.autocrlf input
  • README 中加一行说明 — 「本仓库统一使用 LF,参见 .gitattributes

下一步

参考资料

更新日志

  • 2026-05-12 — 初稿(devAlice M2 seed 扩充)