# Git 跨平台项目格式一致性指南

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

## 问题的本质

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

1. **换行符差异**：Windows 用 `CRLF`，macOS/Linux 用 `LF`，Git 默认行为因人而异，导致同一份代码在不同机器上产生整文件级的假 diff。
2. **代码风格差异**：缩进、引号、分号、行宽等由编辑器和格式化工具决定，因人而异、因项目配置而异。

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

---

## 第一层：Git 层 — `.gitattributes` 统一换行符

这是最容易被忽视、但影响最大的一层。项目根目录建 `.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` 更可靠、更该作为唯一真源。

配合每台机器一次性设置（仅作兜底，不作为主要依赖）：

```bash
# Windows
git config --global core.autocrlf true

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

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

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

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

---

## 第二层：编辑器层 — `.editorconfig` 统一基础格式

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

```ini
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` 只管基础格式，真正的代码风格统一要靠格式化工具，并且必须做到 **锁版本、锁配置、进仓库**：

```jsonc
// .prettierrc
{
  "semi": false,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 100,
  "endOfLine": "lf"   // 关键：格式化输出的换行符不受运行者所在 OS 影响
}
```

```json
// package.json（片段）
{
  "devDependencies": {
    "prettier": "3.3.3"
  },
  "scripts": {
    "format": "prettier --write .",
    "format:check": "prettier --check ."
  }
}
```

**两个容易被忽略的细节**：

- `endOfLine: "lf"` 必须显式设置。否则即使 `.gitattributes` 已经统一了换行符，格式化工具本身仍可能因运行平台不同而输出不同换行符，产生"越格式化越乱"的悖论。
- 格式化工具的版本要锁死（精确版本号而非 `^` / `~` 范围），否则团队成员本地版本漂移会导致格式化结果不一致，等于白做。

---

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

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

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

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

---

## Monorepo 场景下的补充建议

如果项目是多包仓库（如 pnpm workspace / Turborepo 等），额外注意：

- 所有格式化配置（`.gitattributes`、`.editorconfig`、`.prettierrc`）应放在**仓库根目录**，作为唯一真源；各子包不应自行覆盖，除非有明确的、文档化的特殊需求。
- 格式化命令建议通过根目录的统一脚本调用（例如 `pnpm -w format`），而不是让每个子包各自维护一份跑法，避免配置漂移。

---

## 总结表格

| 层级 | 工具 | 解决什么问题 | 是否可被绕过 |
|---|---|---|---|
| Git 层 | `.gitattributes` | 换行符差异，仓库级 diff 噪音 | 否（仓库级生效） |
| 编辑器层 | `.editorconfig` | 缩进 / 编码 / 尾随空格 | 是（依赖编辑器插件） |
| 代码层 | Prettier 等格式化工具 | 实际代码风格统一 | 是（依赖本地执行） |
| 流程层 | CI 校验 | 兜底强制，防止前三层被绕过 | 否（合并门禁） |

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