07 · Commit / PR 工作流
写给未来的自己,不是写给现在。
开场比喻
commit 历史不是"完成的记录",是未来 debug 时的档案。
半年后某个功能炸了,你 git bisect 找凶手——届时帮你的不是代码本身,而是 commit 粒度和 message 质量:
- 每个 commit 只做一件事 → bisect 能精准定位到一行
- Message 写清楚 why → 你一眼看懂当时为什么这么改
每次写 commit 都在给未来的自己留证据。Claude 帮你写得差,未来的你就要付利息。
第 1 条铁律:Claude 不该主动提交
除非你明说"提交一下",否则 Claude 不应该 git commit。
这是 Claude Code 的默认规则,也是你该严格守的规则:
- 主动提交会把不完整的改动封进历史(没人想要半成品 commit)
- 会打乱你的节奏(本来想先 review 再提)
- 会生成草率的 commit message(没和你对齐 why)
判断信号:
- ✅ 你说"commit 一下"、"提交" → 明确指令
- ❌ 你说"改完了" / "完成了" → 不是提交指令,别脑补
第 2 条铁律:一个 commit 一件事
这些都该拆
| 混合内容 | 拆法 |
|---|---|
| 修 bug + 顺手重构一个无关文件 | 两个 commit:fix: + refactor: |
| 加功能 + 改代码风格 | feat: + style: |
| 功能 A + 功能 B | 两个 feat: |
| 加依赖 + 用了新依赖实现功能 | chore: + feat: |
为什么拆不是洁癖
拆 commit 是为了保护未来的 revert 能力:
- 发现
feat:引入 bug →git revert <那个 commit>就能单独撤掉 - 混在一起 → revert 会把重构也撤掉 → 你得手动挑改
判断标准:问自己"这个 commit 单独 revert 会不会出问题?"
- 是 → 必须拆
- 否 → 可以合
Commit Message 模板
最小化模板(Conventional Commits):
<type>(<scope>): <what changed in one line, imperative>
<optional body: why, not what>
<optional footer: BREAKING CHANGE, refs>Type 速查
| type | 用途 |
|---|---|
feat | 新功能 |
fix | Bug 修复 |
refactor | 重构(不改行为) |
style | 代码格式(空格、分号) |
docs | 文档 |
test | 测试 |
chore | 构建、依赖、CI |
perf | 性能优化 |
好 vs 坏 对比
❌ 差:
update code
fix bug
wip
改一下✅ 好:
fix(checkout): prevent duplicate submissions on fast double-click
debounce was missing on handleSubmit; reproduced by QA in #234.要点:
- 标题用祈使语气(
add,不是added/adding) - 标题小写开头、不加句号
- Body 写 why,不写 what —— 代码已经告诉你 what 了
Why 才是价值
一年后翻这条 commit:
- "what":看 diff 就知道了
- "why":只有 commit message 能告诉你
fix(pricing): fallback to USD when exchange rate API returns 502
日区 API 偶发 502,之前 service 抛异常整页挂。
改成返回默认价 + 标记数据陈旧,比崩页好。
相关告警:Sentry AP-4271这种 message 是未来的救命稻草。
PR 描述三段式
## Why
<动机 / 来源:解决什么问题、对应哪个 issue / 需求>
## What changed
<核心改动 1-3 条,不要罗列文件>
## How to test
<reviewer 能按步骤验证的 checklist>写作要点
- Why 放最前面——reviewer 最想知道的是动机
- What 不罗列文件——reviewer 会自己看 diff;你要说的是"改动的意图分组"
- How to test 可执行——"点 X 按钮,看 Y 应该发生"而不是"测一下功能"
真实对比
❌ 差:
改了一些东西,修了 bug。请 review。✅ 好:
## Why
QA 报告双击 Pay 按钮会重复扣款(#234)。根因:handleSubmit 没做
debounce,且 server action 没幂等检查。
## What changed
- `<CheckoutForm>` 在 submit 期间锁定按钮(loading 状态 + disabled)
- `submitPayment` action 增加 idempotency key 校验
## How to test
1. `pnpm dev`,打开 /checkout
2. 快速双击 Pay 按钮
3. 确认只产生一笔订单(检查 /api/orders 日志)
4. 用相同 idempotency key 再次调用 action,应返回原订单让 Claude 帮你写的正确姿势
Pattern A:让 Claude 先打 message,你审过再提交
不要这么说:
"提交一下"→ Claude 自己决定分几个 commit、怎么写 message → 你可能要返工
该这么说:
"先看一下 git status 和 diff,打印你建议的 commit 拆分和 message,
我审过再提。"→ Claude 交方案 → 你修改 → 批准 → 提交。
Pattern B:多 commit 的 PR
"把 <base>..HEAD 的所有 commit 合起来总结 PR 描述,不要只看最后一个。"Claude 默认可能只看 HEAD,漏掉前面的改动。显式让它 git log <base>..HEAD。
Pattern C:别跳过 hook
pre-commit hook 报错时——不要 git commit --no-verify:
- Hook 存在是有原因的(lint / typecheck / format)
- 绕过只是把问题推到下一个阶段(CI 报错 / 线上出问题)
正确做法:修 hook 报的问题 → 重新 stage → 提交。
危险操作的红线
这些操作你不该轻易让 Claude 做,Claude 也应该先问再做:
| 操作 | 风险 |
|---|---|
git push --force(到共享分支) | 覆盖别人的提交 |
git push --force 到 main / master | 公司事故级操作 |
git reset --hard | 抹掉本地未提交工作 |
git clean -fd | 删除所有未跟踪文件 |
git branch -D <branch> | 强删分支(未合并的也不警告) |
git commit --amend(已推送的 commit) | 改历史,强推会影响协作者 |
rm -rf 在 repo 里 | 字面意思 |
双重保险:
- CLAUDE.md 里明确:"这些操作前必须先问"
- 关键项加 hook(PreToolUse + Bash matcher,检测危险命令)
反模式五连
① "提交吧"然后不看
Claude 自己决定拆分、写 message、提交——你省了 30 秒,未来 30 分钟还回来。
② Commit message 写 what
update user.ts → 代码已经说了。写 why。
③ 标题超过 72 字符
git log --oneline 会截断。标题精炼,细节放 body。
④ 混 commit 不拆
一个 commit 里既修 bug 又重构又改格式——未来 revert / bisect 全废。
⑤ Force push 到 main
整个团队的修罗场。Claude Code 默认拒绝,不要强行让它做。
一句话总结
Git 历史是写给未来的,不是写给自己的。
三条底线:Claude 不主动提交、一个 commit 一件事、message 写 why。
做到这三条,半年后 debug 时未来的你会感谢现在的你。
判断速查
准备提交了
↓
改动是一件事吗? ── 否 → 先拆 stage
↓ 是
让 Claude 打 message 草稿 ← 不要直接 "commit"
↓
你审过 message ok? ── 否 → 改
↓ 是
提交(不要 --no-verify)
↓
Hook 报错? ── 是 → 修问题,不要绕
↓ 否
✓