Chapter 03

Git Commit Message 与分支命名

50 字符内说清楚一件事——commit message 是程序员英语的"标题党艺术"。

3.1 commit message 为什么重要

很多刚入行的程序员不理解为什么大家这么计较 commit message。明明能 push 上去就行,为什么 reviewer 总要让你 reword?三个原因:

  1. 它是历史档案。 五年后某个 bug 复发,你或继任者要靠 git loggit blame 倒推根因。"fix bug" 等于零信息。
  2. 它驱动自动化。 Conventional Commits 格式可以自动生成 changelog、决定 semver 版本号、关联 issue、触发 release pipeline。乱写就破坏整套自动化。
  3. 它是你的"门面"。 招聘官、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新功能MINORfeat(api): add bulk export endpoint
fixbug 修复PATCHfix(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性能优化PATCHperf(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
ciCI/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% 场景):

  1. Context(背景):为什么需要这个改动?什么问题?
  2. Approach(做法):用了什么思路?关键决策?
  3. Tradeoff(权衡):放弃了什么?已知限制?
  4. Reference(关联):相关 issue / PR / RFC / 文档。
// rule

"如果一个新人看了这条 commit 的 diff,还想问 '为什么要这么改'——那 body 就还没写完。"

body 的语态

body 的语态相对自由,但有惯例:

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?
// danger

这 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

命名要点:

# 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 抬高到整个 PR——以及 issue 的写作。