3.1 commit message 为什么重要
很多刚入行的程序员不理解为什么大家这么计较 commit message。明明能 push 上去就行,为什么 reviewer 总要让你 reword?三个原因:
- 它是历史档案。 五年后某个 bug 复发,你或继任者要靠
git log和git blame倒推根因。"fix bug" 等于零信息。 - 它驱动自动化。 Conventional Commits 格式可以自动生成 changelog、决定 semver 版本号、关联 issue、触发 release pipeline。乱写就破坏整套自动化。
- 它是你的"门面"。 招聘官、reviewer、新同事第一次了解你的代码风格,不是看你的代码——是看你近 100 条 commit log。乱七八糟的 commit history 比丑陋的代码影响更坏。
3.2 Conventional Commits 规范
这是 https://www.conventionalcommits.org 的核心格式,绝大多数现代开源项目都用:
<type>[(scope)][!]: <subject>
[body]
[footer]
实例:
feat(auth): add OAuth2 PKCE flow for mobile clients
Native mobile apps cannot safely store a client secret. PKCE
(Proof Key for Code Exchange, RFC 7636) replaces the secret with
a per-request challenge, allowing public clients to use the
authorization code flow safely.
This commit:
- Adds PKCE challenge generation and verification
- Updates /authorize and /token endpoints to accept PKCE params
- Falls back to classic flow when no challenge is sent
Closes #4821
Co-authored-by: Alice Chen <alice@example.com>
type 字段(核心 11 个)
| type | 含义 | 触发版本 | 例子 |
|---|---|---|---|
feat | 新功能 | MINOR | feat(api): add bulk export endpoint |
fix | bug 修复 | PATCH | fix(parser): handle UTF-16 BOM correctly |
docs | 仅文档 | — | docs(readme): clarify install steps for Windows |
style | 格式(不影响行为) | — | style: run prettier on src/ |
refactor | 重构(不修 bug 不加功能) | — | refactor(db): extract pool config into struct |
perf | 性能优化 | PATCH | perf(query): cache prepared statements |
test | 测试 | — | test(auth): cover token refresh edge cases |
build | 构建系统 / 依赖 | — | build(deps): bump axios from 1.5.0 to 1.6.0 |
ci | CI/CD 配置 | — | ci: cache npm modules to speed up runs |
chore | 琐事 (eslintrc / gitignore) | — | chore: bump node engine to 20 |
revert | 回滚 | — | revert: feat(auth): add OAuth2 PKCE flow |
scope 字段
scope 是名词,描述被影响的模块。常见 scope:
auth, api, ui, db, cli, parser, router, config, deps,
core, utils, types, tests, docs, ci, build,
admin, billing, search, dashboard, notifications
scope 应当是项目内固定词表,不要每次发明新词。如果有多个 scope,工程界惯例是省略 scope(用裸 type)。
subject 字段:祈使句、现在时
这是最容易出错的一处。规则:把 subject 想象成"如果应用此 commit,则会..."的命令。
# Good — 祈使句、动词原形
fix(auth): handle expired token refresh
feat(api): add pagination to user endpoint
refactor(db): extract connection pool
# Bad — 过去时
fix(auth): handled expired token # 应为 handle
feat(api): added pagination # 应为 add
refactor(db): extracted connection # 应为 extract
# Bad — 现在分词
fix(auth): handling expired token # 应为 handle
# Bad — 第三人称单数
fix(auth): handles expired token # 应为 handle
为什么是祈使句?因为 git 自己生成的 message 用的也是祈使句:
Merge branch 'feature/x' into main
Revert "feat(auth): add OAuth2 PKCE"
你的手写 commit 应该和它们语气一致——构成一致的历史叙述。
subject 长度
规则:不超过 50 字符(含 type + scope)。GitHub 在 list 视图里截断 72 字符;50 字符是给空间的安全线。
# 工具会在 50 字处给警告:
$ git commit -m "..."
# 你可以用:commitlint 或 husky 强制此规则
3.3 body 怎么写:why 不是 what
新手最常见的错误是把 body 写成"做了什么"的列表——这是冗余的,diff 已经告诉读者做了什么。body 真正该写的是为什么:
# Bad — body 重复 diff 信息
feat(auth): add rate limiter
- Added rate limiter middleware
- Added redis client
- Added config for limit values
- Added tests
# Good — body 解释为什么
feat(auth): add rate limiter
Login endpoint has been the target of credential stuffing attacks
since launch. We see ~50k failed attempts per hour during peak
spikes, originating from a few hundred IPs.
This commit adds a sliding-window rate limiter (10 req/min per IP,
keyed in Redis). Limits are configurable via LOGIN_RATE_LIMIT env
var so we can tighten them during incidents.
Tradeoff: legitimate users behind shared NAT (corporate networks,
mobile carriers) may hit the limit. We accept this risk because
the attack volume is dominated by single-IP bursts.
Closes #4982
body 的写作公式(适合 80% 场景):
- Context(背景):为什么需要这个改动?什么问题?
- Approach(做法):用了什么思路?关键决策?
- Tradeoff(权衡):放弃了什么?已知限制?
- Reference(关联):相关 issue / PR / RFC / 文档。
"如果一个新人看了这条 commit 的 diff,还想问 '为什么要这么改'——那 body 就还没写完。"
body 的语态
body 的语态相对自由,但有惯例:
- 第一段背景常用第三人称现在时:"The current implementation uses..." / "Login endpoint has been the target of..."
- 动作描述用第一人称复数:"We use a sliding window..." / "We accept this risk because..."
- 避免第二人称 "you"——commit 不是写给某个特定人看的。
3.4 footer 字段:metadata 区
footer 用于结构化元数据,工具会自动解析:
# 关联 issue(不同关键词触发不同行为)
Closes #1234 # 合并后自动关闭 issue
Fixes #1234 # 同 Closes,但语义偏 bug
Resolves #1234 # 同 Closes,正式语
Refs #1234 # 仅引用,不关闭
See also #5678 # 弱引用
# 多人协作(GitHub 显示 co-author 头像)
Co-authored-by: Alice Chen <alice@example.com>
Co-authored-by: Bob Wang <bob@example.com>
# Linux / 部分公司要求
Signed-off-by: Your Name <you@example.com>
Reviewed-by: Lead Engineer <lead@example.com>
Tested-by: QA Engineer <qa@example.com>
Reported-by: User <user@example.com>
# 触发 BREAKING CHANGE(导致 MAJOR 版本号 bump)
BREAKING CHANGE: removed deprecated /v1/users endpoint;
clients must migrate to /v2/users by 2024-03-01.
BREAKING CHANGE 的两种写法
# 写法 A:在 type 后加 ! 标记
feat(api)!: redesign user payload schema
BREAKING CHANGE: response shape changed; see migration guide.
# 写法 B:仅 footer
feat(api): redesign user payload schema
BREAKING CHANGE: response shape changed; see migration guide.
! 标记可视性更高,强烈推荐。
3.5 50 个真实 commit message 拆解
下面是 React、Linux kernel、Rust、Vue 等大型项目的真实 commit。每条都附带"为什么这样写"。
React 仓库(精选 10 条)
1. fix: revert change that triggered the regression in #28910
(revert 当作 fix;用 #issue 引用而不是描述)
2. feat[react-dom]: add experimental useFormStatus
(scope 用方括号风格,experimental 提示稳定性)
3. refactor: reduce footprint of profiling build
(动词 reduce 比 "make smaller" 干练)
4. perf: avoid extra hash on cold paths
(cold paths = 不常走的代码路径,行业黑话)
5. fix: handle null ref in StrictMode double-render
(handle 是修 bug 的高频动词)
6. docs: clarify when to use useId vs useDeferredValue
(clarify 比 "fix doc" 信息量更大)
7. test: cover Suspense boundary unmount edge case
(cover 是测试领域的标准动词)
8. chore: bump flow to 0.225.0
(bump 是依赖升级的标准动词)
9. build: replace Rollup with Vite for dev server
(replace X with Y 模板)
10. revert: "feat: add useEvent hook"
(revert 时引用原 subject)
Linux kernel(精选 10 条)
11. mm: vmscan: don't watermark boost on swap-full and OOM
(subsystem 链 mm: vmscan: 体现层级)
12. net/sched: act_ct: switch to per-action label counting
(switch to 表示替换/迁移)
13. KVM: x86: Add helper to consolidate guest CR0/CR4 reads
(consolidate = 合并/统一,用于重构)
14. arm64: dts: imx8mm-evk: enable PCIe support
(enable XXX support 是硬件 patch 的高频模板)
15. drm/i915: Skip hibernation flow on broken hardware
(Skip ... on ... 模板表条件性跳过)
16. selftests/bpf: Fix flaky test under high CPU load
(flaky test = 偶发失败的测试,行业黑话)
17. mtd: spi-nor: clean up redundant chip support
(clean up + redundant 是重构语)
18. xfs: shut down filesystem when log space is exhausted
(shut down 是动词短语,连写是名词)
19. perf record: Improve --filter help text
(Improve XXX 用于小幅改进)
20. ftrace: Skip synchronization when ftrace_ops is being freed
(动名词 being freed 描述状态)
Rust 编译器 / Cargo(精选 10 条)
21. fix: don't ICE when const eval encounters dangling ref
(ICE = Internal Compiler Error,编译器术语)
22. feat(cargo): support workspace-level rust-version
(workspace-level 是修饰词组)
23. perf: avoid double allocation in HashMap shrink path
(double allocation 双重分配,性能术语)
24. refactor: split lifetime resolution into its own pass
(split X into Y 分离重构模板)
25. fix(rustdoc): preserve newlines in code blocks
(preserve = 保留,反义 strip/discard)
26. feat: stabilize cell::OnceCell::get_or_init
(stabilize = 稳定化,从 nightly 进 stable)
27. ci: skip slow tests on PR builds
(on XX builds 限定环境)
28. docs: explain why we forbid panicking in const fn
(explain why 是文档增量的优秀模板)
29. fix: emit better error when import shadows prelude
(emit error 是编译器领域的"输出错误")
30. test: add regression test for issue #98765
(regression test 防止 bug 再次出现)
Vue / 前端项目(精选 10 条)
31. fix(reactivity): handle Map iteration with shallowReactive
(handle XX with YY 模板)
32. feat(compiler-sfc): support generic component type params
(type params 是 TS 术语)
33. perf(runtime-core): avoid creating closure in hot path
(hot path = 热路径,性能优化术语)
34. fix(ssr): skip hydration mismatch warning in dev mode
(hydration mismatch 是 SSR 专有术语)
35. refactor(types): consolidate Ref types into single file
(consolidate into 整合到)
36. chore(deps): bump vite to v5.1
(前缀 deps 表示依赖更新)
37. docs(api): clarify reactive vs ref tradeoff
(tradeoff 取舍/权衡)
38. test(e2e): add Playwright suite for SSR streaming
(add suite for X 是测试新增模板)
39. build: enable tree-shaking for component imports
(enable XX 启用某能力)
40. revert: "feat(reactivity): introduce signal-like API"
Reason: ecosystem feedback indicates further design needed.
(revert 加 Reason: 是高质量做法)
10 条灾难级 commit(反例)
41. update # 没说更新了什么
42. fix bug # 哪个 bug
43. final # final 之后总会再来一次
44. asdf # 测试键盘?
45. work in progress # 不该 push 的状态
46. 修复了一个问题 # 中文 + 模糊
47. . # 一个点
48. fixed # 没说 fix 了什么
49. .DS_Store # 误提交了系统文件
50. WIP: please don't merge # 那为什么 push?
这 10 条不是夸张——是从真实公司仓库 git log 里摘出来的。每条背后都对应一次"五年后要 debug 时痛骂当年那个程序员"的瞬间。
3.6 分支命名规范
分支名也是 git history 的一部分,会出现在 PR 标题、CI/CD pipeline、release notes 里。规范:
<type>/<optional-issue>-<short-description-in-kebab-case>
常见 type:
| type | 用途 | 例子 |
|---|---|---|
feature/ 或 feat/ | 新功能开发 | feature/4821-oauth-pkce |
fix/ 或 bugfix/ | bug 修复 | fix/4982-rate-limit-redis-pool |
hotfix/ | 生产紧急修复 | hotfix/payment-callback-500 |
release/ | 发布分支 | release/v2.4.0 |
chore/ | 琐事 | chore/bump-node-20 |
refactor/ | 重构 | refactor/extract-pool-config |
docs/ | 仅文档 | docs/install-guide-windows |
spike/ | 技术调研 | spike/grpc-vs-rest-benchmark |
experiment/ 或 exp/ | 实验 | exp/edge-runtime-poc |
命名要点:
- kebab-case(短横连接),不要驼峰、下划线、空格。
- 带 issue 号最好(GitHub 自动关联)。
- 3-6 个词最佳,不要超过 8 个。
- 不要带个人名(
alice/feature-x)——除非是 fork。 - 不要中文 / 拼音。
# Bad
fix_a_bug
hotfix-生产环境-500
alice_branch_2
new-feature-final-v3-really-final
# Good
fix/4982-payment-500
hotfix/login-redirect-loop
feature/admin-bulk-export
chore/eslint-config-update
3.7 commit 工作流的英语场景
amend 已 push 的 commit
$ git commit --amend
$ git push --force-with-lease
# 在 PR review 中告知 reviewer:
"Amended the previous commit to include your suggested rename;
force-pushed the branch."
rebase 拆分 / 合并 commit
$ git rebase -i HEAD~5
# 在 PR 描述里告知:
"Squashed commits 3-4 (typo fixes) into commit 2.
History is now: setup → impl → tests → docs."
cherry-pick 跨分支
$ git cherry-pick abc123
# commit message 加注释:
fix(auth): handle expired token refresh
(cherry picked from commit abc123)
3.8 本章小结
- commit message 是历史档案 + 自动化驱动 + 个人门面,三个角色都重要。
- Conventional Commits 格式:
type(scope)!: subject \n body \n footer。 - subject:祈使句,动词原形,不超过 50 字符。
- body:写 why 不是 what,公式 = 背景 + 做法 + 权衡 + 引用。
- footer:
Closes/Fixes、Co-authored-by、BREAKING CHANGE。 - 分支命名:
type/issue-kebab-desc,永远不用中文 / 拼音。
下一章我们把视角从单条 commit 抬高到整个 PR——以及 issue 的写作。