这是 Git 手册系列的最后一篇。前三篇解决了"会用 → 用得对 → 懂原理",本篇解决最后一件事:让 Git 在团队和工程体系里成为可靠的基础设施,并在灾难发生时救得回来。


目录

  • Monorepo vs Polyrepo:仓库拓扑决策
  • 大型仓库的性能优化
  • 提交规范:Conventional Commits
  • 签名提交与提交者验证
  • 密钥防护:从预防到泄漏处置
  • CI/CD 中的 Git 技巧
  • 灾难恢复 SOP
  • 一套可直接落地的团队 Git 规范
  • 终极速查表

Monorepo vs Polyrepo:仓库拓扑决策

Monorepo(一个仓库存所有项目)Polyrepo(每项目一个仓库)
跨项目改动一次提交原子完成要发版、升依赖、多 PR 联动
代码共享直接 import发包 / submodule
CI需要增量构建,配置复杂简单直接
权限边界靠 CODEOWNERS,粒度粗天然隔离
克隆体积大(可用 sparse checkout 缓解)
代表Google、Meta、Vue、React 生态多数中小团队

决策建议

  • 项目间强耦合、频繁联动发布(如前端组件库 + 应用)→ monorepo
  • 团队/项目独立、发布节奏不同、需要强权限边界 → polyrepo
  • 不确定就先 polyrepo;拆容易,合回去很难

monorepo 配套工具:Nx、Turborepo、Bazel(构建缓存与增量)、changesets(多包版本管理)。


大型仓库的性能优化

仓库几万提交、几百 MB 之后,这些手段能救体验:

# 浅克隆:只拿最近的历史(CI 必备)
git clone --depth 1 <url>

# 部分克隆:不下载全部 blob,按需懒加载(本地开发大仓库神器)
git clone --filter=blob:none <url>

# 稀疏检出:只 checkout 需要的目录
git sparse-checkout init --cone
git sparse-checkout set apps/web packages/ui

# 让 status / diff 在大仓库里更快
git config core.fsmonitor true
git config core.untrackedCache true
git maintenance start        # 后台自动做 gc、打包、提交图等维护

提交图(commit-graph)单独提一句:它让 git log --graph、合并基计算在大仓库里快一个数量级,git maintenance start 会自动开启。


提交规范:Conventional Commits

格式:

<type>(<scope>): <subject>

<body>

<footer>

常用 type:

type含义影响版本
feat新功能次版本 +1
fix修 bug修订号 +1
perf性能优化修订号 +1
refactor重构(不改行为)不升
docs / style / test / chore / ci / build文档 / 格式 / 测试 / 杂务不升
! 或 footer 写 BREAKING CHANGE:破坏性变更主版本 +1

示例:

feat(auth): 支持手机号验证码登录

- 新增 /auth/sms/send 与 /auth/sms/verify 接口
- 验证码 5 分钟有效,限频 1 条/分钟

Closes #128

为什么值得推行:版本号和 CHANGELOG 可以全自动生成。配套工具链:

# commitizen:交互式生成规范提交信息
pnpm add -D commitizen cz-conventional-changelog

# commitlint + husky:不合规的提交直接拒绝
pnpm add -D @commitlint/cli @commitlint/config-conventional
echo "npx commitlint --edit \$1" > .husky/commit-msg

# semantic-release / release-please:按提交记录自动升版本、发 CHANGELOG

签名提交与提交者验证

Git 的 user.name / user.email 可以随便填——git commit --author="Linus <[email protected]>" 就能伪装任何人。要证明"这个提交真的是我提交的",用签名:

SSH 签名(推荐,零门槛复用现有密钥)

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true      # 之后每次提交自动签名

GitHub 设置里把同一个公钥添加为 Signing Key,你的提交就会显示 Verified 徽章。

验证别人的提交

git log --show-signature         # 查看签名验证结果
git merge --verify-signatures main

为什么这很重要

  • 供应链安全:防止有人伪装核心维护者提交恶意代码
  • 审计合规:金融、政企场景要求提交可追溯真人
  • 配合分支保护规则"要求签名提交",从平台侧强制

密钥防护:从预防到泄漏处置

密钥进仓库是最高频的安全事故。三层防御:

第一层:本地预防

# gitleaks:提交/推送前扫描
brew install gitleaks
gitleaks git --pre-commit --staged --verbose   # 接入 husky pre-commit

.gitignore 里永远有:.env*.pem*.keycredentials*

第二层:服务端拦截

  • GitHub:开启 Push Protection(推送时扫描,疑似密钥直接拒绝)
  • GitLab:开启 Secret Detection
  • 自建:pre-receive hook 跑 gitleaks

第三层:泄漏后的处置 SOP

密钥一旦进过任何一次提交,就视为已泄漏,即使后来的提交删了它:

  1. 立即吊销 / 轮换密钥(这是唯一有效的补救,改历史只是清理痕迹)
  2. git filter-repo 从全历史删除该文件(见高级篇)
  3. 全员重新克隆
  4. 排查密钥使用日志,评估影响面
  5. 复盘:为什么绕过了第一层和第二层

CI/CD 中的 Git 技巧

浅克隆 + 按需加深

# GitHub Actions
- uses: actions/checkout@v4
  with:
    fetch-depth: 0        # 需要 tag / 全历史(如算版本号)时才用 0,否则保持默认 1

只跑受影响范围的测试

# 与合并基比较,得到改动文件列表
git diff --name-only origin/main...HEAD | grep -q '^apps/web/' && run_web_tests

版本号来自 Git

git describe --tags --always --dirty
# v1.2.0-5-ga1b2c3d  → 距 v1.2.0 之后第 5 个提交,哈希 a1b2c3d

这是给构建产物打版本标识的零维护方案,前端塞进 __VERSION__、后端塞进 /healthz 返回值。

自动打 tag / 生成 CHANGELOG

release-please 或 semantic-release 会读取 Conventional Commits,自动算出版本号、生成 CHANGELOG、创建 release PR 和 tag。从此"发版"不是一个手工动作。

保护分支 + 必需检查

平台侧配置(GitHub Branch Protection / GitLab Protected Branches):

  • 禁止直接 push main,必须走 PR
  • 必须 ≥1 人 approve
  • 必须通过 CI 检查
  • 禁止 force push 和删除

灾难恢复 SOP

场景 1:误删了远程分支

# 任何拉过该分支的人本地都有完整历史
git switch -c feature/xxx <已知的最新提交id 或 reflog 里找>
git push -u origin feature/xxx

场景 2:对 main 执行了 force push,覆盖掉别人的提交

# 找到被覆盖前的 main 位置(任何同事本地、CI 缓存、或平台 Events API)
git reflog show main           # 在还没 pull 的机器上
git push --force origin <旧提交id>:main

教训:在平台设置里禁掉 main 的 force push,从制度上消灭这个场景。

场景 3:仓库本地损坏(对象丢失、索引损坏)

git fsck --full                # 诊断
git clone <remote-url> rescue  # 最省事:远程还在就直接重克隆
# 远程也没了但同事机器上有:任何人的 clone 都是完整备份

场景 4:rebase / amend 错了,想回到操作前

git reflog                     # 找到 rebase 之前的 HEAD 位置
git reset --hard HEAD@{5}

场景 5:整个 .git 目录被删了

未推送的提交彻底丢失——这就是为什么"每天至少 push 一次"写进了初级篇。能做的只有从远程重新克隆,工作区文件还在的话手动对比恢复。

通用原则:Git 里 99% 的"丢失"都能用 git reflog + git fsck 找回;剩下 1% 是"从未 commit / 从未 push",只能靠备份习惯预防。


一套可直接落地的团队 Git 规范

以下是本篇的浓缩,可以直接贴进团队 Wiki:

分支

  1. main 永远可发布,受保护,禁止直推、禁止 force push
  2. 功能分支命名:<type>/<简述>,如 feat/sms-loginfix/pay-timeout
  3. 功能分支活不过 3 天,合并后删除

提交

  1. 遵循 Conventional Commits,commitlint 强制
  2. 一次提交做一件事;wiptmp 类提交合并前必须 squash
  3. 开启 SSH 提交签名

同步

  1. 每天开工先 git rebase origin/main(功能分支上)
  2. 每天至少 push 一次
  3. 公共分支只用 merge,功能分支同步用 rebase,永不 rebase 公共历史

评审

  1. 一切代码经 PR 合入 main,CI 通过 + 至少 1 人 approve
  2. PR 保持小:≤400 行变更为佳,大了拆

发布

  1. tag 遵循 semver,由 release-please 自动生成
  2. 线上 bug 走 hotfix/* 分支,修完同时回合 main

安全

  1. pre-commit 跑 gitleaks,服务端开 Push Protection
  2. 密钥泄漏 = 立即轮换,其次才是清历史

终极速查表

场景方案
大仓库克隆--filter=blob:none + sparse-checkout
提交规范Conventional Commits + commitlint
自动发版release-please / semantic-release
构建版本号git describe --tags --always --dirty
提交防伪SSH 签名 + 平台 Verified
密钥扫描gitleaks(本地)+ Push Protection(服务端)
密钥泄漏轮换密钥 → filter-repo 清历史 → 全员重克隆
误删/覆盖找回git reflogreset --hard / 强推恢复
仓库损坏git fsck 诊断,重新克隆
协作红线保护 main、禁 force push、一切走 PR

系列回顾

  • 初级篇:四个区域、三板斧、远程同步——能用
  • 中级篇:分支模型、merge/rebase、reflog——用得对
  • 高级篇:对象模型、bisect、worktree、submodule、LFS——懂原理
  • 终极篇:工程化、安全、SOP、团队规范——成体系

Git 的学习曲线不在命令数量,而在心智模型。四篇读完,剩下的就是每天在真实项目里踩坑、翻 reflog、读 graph——祝你的每一次 git push 都心安理得。