在 Windows / macOS / Linux 混合团队中,如何让代码换行符、缩进、代码风格保持一致,避免无意义的 diff 噪音。

问题的本质

跨平台协作中,格式不一致通常来自两个独立的源头,需要分开解决:

  1. 换行符差异:Windows 用 CRLF,macOS/Linux 用 LF,Git 默认行为因人而异,导致同一份代码在不同机器上产生整文件级的假 diff。

  2. 代码风格差异:缩进、引号、分号、行宽等由编辑器和格式化工具决定,因人而异、因项目配置而异。

解决方案是分层治理:Git 层 → 编辑器层 → 代码格式化工具层 → CI 强制层。每一层职责单一,互不重叠。


第一层:Git 层 — .gitattributes 统一换行符

这是最容易被忽视、但影响最大的一层。项目根目录建 .gitattributes

# 默认所有文本文件用 LF,交给 Git 自动处理
* text=auto eol=lf

# 明确指定常见文本类型
*.ts   text eol=lf
*.tsx  text eol=lf
*.js   text eol=lf
*.json text eol=lf
*.md   text eol=lf
*.yml  text eol=lf
*.css  text eol=lf

# Windows 专用脚本按需保留 CRLF
*.bat text eol=crlf
*.cmd text eol=crlf

# 二进制文件禁止换行符转换
*.png  binary
*.jpg  binary
*.woff2 binary

要点.gitattributes 是仓库级配置,随代码一起提交,团队成员无需手动配置即可生效,比个人的 core.autocrlf 更可靠、更该作为唯一真源。

配合每台机器一次性设置(仅作兜底,不作为主要依赖):

# Windows
git config --global core.autocrlf true

# macOS / Linux / WSL2
git config --global core.autocrlf input

已有历史仓库的修复方式:加完 .gitattributes 后,需要重新规范化已提交的文件:

git add --renormalize .
git commit -m "chore: normalize line endings"

⚠️ 这一步会产生一次性的大范围 diff,建议单独提交,并在提交信息中说明原因,避免与功能改动混在一起。


第二层:编辑器层 — .editorconfig 统一基础格式

跨 VS Code / JetBrains 系列 / Visual Studio 都能自动识别,解决缩进、编码、尾随空格等编辑器级差异:

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

# Markdown 中尾随空格可能有意义(软换行),不强制清理
[*.md]
trim_trailing_whitespace = false

# 某些语言生态传统上仍用 CRLF,可按需覆盖
[*.cs]
indent_size = 4
end_of_line = crlf

要点.editorconfig 不做代码风格判断(不管引号用单还是双),它只管"编辑器落盘时的基础行为",职责要收窄。


第三层:代码格式化工具层 — Prettier / 等价工具

.editorconfig 只管基础格式,真正的代码风格统一要靠格式化工具,并且必须做到 锁版本、锁配置、进仓库

// .prettierrc
{
  "semi": false,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 100,
  "endOfLine": "lf"   // 关键:格式化输出的换行符不受运行者所在 OS 影响
}
// package.json(片段)
{
  "devDependencies": {
    "prettier": "3.3.3"
  },
  "scripts": {
    "format": "prettier --write .",
    "format:check": "prettier --check ."
  }
}

两个容易被忽略的细节

  • endOfLine: "lf" 必须显式设置。否则即使 .gitattributes 已经统一了换行符,格式化工具本身仍可能因运行平台不同而输出不同换行符,产生"越格式化越乱"的悖论。

  • 格式化工具的版本要锁死(精确版本号而非 ^ / ~ 范围),否则团队成员本地版本漂移会导致格式化结果不一致,等于白做。


第四层:CI 强制层 — 防止本地遗漏

本地格式化依赖人的自觉,唯一可靠的把关方式是在 CI 中加一道强制检查:

# GitHub Actions 示例片段
- name: Check formatting
  run: pnpm format:check

要点:这一步不是"锦上添花",而是整套方案能否长期生效的关键。没有 CI 强制,前三层配置迟早会因为个别开发者未安装编辑器插件、未跑格式化命令而失效。


Monorepo 场景下的补充建议

如果项目是多包仓库(如 pnpm workspace / Turborepo 等),额外注意:

  • 所有格式化配置(.gitattributes.editorconfig.prettierrc)应放在仓库根目录,作为唯一真源;各子包不应自行覆盖,除非有明确的、文档化的特殊需求。

  • 格式化命令建议通过根目录的统一脚本调用(例如 pnpm -w format),而不是让每个子包各自维护一份跑法,避免配置漂移。


总结表格

核心原则:越靠前的层级越应该"仓库级、不可绕过";越靠后的层级越依赖个人习惯,因此必须用 CI 兜底。四层配置互不重叠、职责单一,是长期维持跨平台格式一致性的关键,而不是依赖某一层"包打天下"。


MD format: https://blog-media.ropean.org/2026/07/git-cross-platform-formatting.md