想要“历史好读、可回溯、能自动化”,从今天开始把提交消息写好看一点。别卷,先把 80% 的收益捡起来。
TL;DR:一屏清单#
- 提交头格式:
type(scope): subject
,72 字符内,祈使句,不加句号 - 类型推荐:
feat
、fix
、perf
、refactor
、docs
、test
、build
、ci
、chore
、revert
- scope 取“模块/包/目录名”,不确定就留空
- 有动机写在 body,多点信息少点形容词;关联 Issue 用
Closes #123
- 破坏性变更:
feat(api)!: ...
,并在 footer 写BREAKING CHANGE: ...
- 大改动拆 commit;重复修正用
--fixup
+--autosquash
- 本地阻断:Husky + Commitlint;交互式提交:Commitizen + cz-git
- 团队默认用 squash merge,保持主干干净
写法规范(就这几条,够用了)#
1) Header:类型、作用域、主题#
- 格式:
type(scope)?: subject
- 主题(subject)写祈使句:增加/修复/优化,用动词开头,不加句号
- 长度:尽量 ≤ 72 字符
示例
feat(auth): 支持邮箱验证码登录fix(cart): 结算金额精度问题refactor(checkout): 拆分支付步骤为独立组件docs(readme): 补充部署说明
2) Body:讲人话,讲关键#
- 结构建议:动机(为什么)→ 方案(怎么做)→ 影响(有什么副作用)
- 用列表写要点,比长段更好读
- 可贴关键设计取舍或回滚指引
示例
修复在 iOS 16 下键盘弹出导致的布局抖动方案:改为使用 SafeArea + scrollToFocusedInput影响:老设备滚动条样式略有变化
3) Footer:机器能读的关键信息#
- 关联:
Closes #123
、Refs #456
- 破坏性变更:
feat(api)!: 订单详情移除 legacy 字段BREAKING CHANGE: /orders/:id 返回不再包含 legacyTotal,请使用 total
[!tip] 中文/英文都行,统一就好;类型用英文,内容可中文。
类型与作用域(别纠结,先统一)#
-
推荐类型
- feat(新功能)、fix(修 bug)、perf(性能)
- refactor(重构,无行为变化)
- docs、test、build、ci、chore、revert
-
作用域 scope
- 单仓:功能域名,如
auth
、cart
- Monorepo:包名或子系统,如
web
、api
、pkg/core
- 不确定就先留空,别编造
- 单仓:功能域名,如
Do / Don’t
- Do:
feat(search): 支持拼音首字母匹配
- Don’t:
update
、fix bug
、misc
(信息不足)
粒度与节奏(写多少合适)#
- 一次提交做“一件清晰的事”;功能开发可以是多次小步提交,合 PR 时 squash
- 代码格式化、依赖升级尽量独立提交
- WIP 不建议进主干;确实需要,可在个人分支用:
chore(wip): ...
多次修正建议
git commit --fixup <sha>git rebase -i --autosquash origin/main
团队工具最小闭环(5 分钟落地)#
1) 本地阻断:Commitlint + Husky#
- 安装
pnpm add -D @commitlint/cli @commitlint/config-conventional huskypnpm dlx husky init
commitlint.config.cjs
module.exports = { extends: ['@commitlint/config-conventional'] };
.husky/commit-msg
#!/usr/bin/env sh. "$(dirname -- "$0")/_/husky.sh"pnpm commitlint --edit "$1"
2) 交互式提交(可选但真省心)#
pnpm add -D commitizen cz-git
package.json
{ "scripts": { "commit": "cz" }, "config": { "commitizen": { "path": "node_modules/cz-git" } }}
现在用:
pnpm commit
3) CI 再验一次(防“跳钩子”)#
- GitHub Actions 简例
- name: Commitlint run: | pnpm install --frozen-lockfile pnpm commitlint --from=origin/main --to=HEAD
协作流与合并策略#
- 分支命名:
feat/login-otp
、fix/cart-precision
- PR 标题沿用提交风格:
feat(auth): 支持 OTP 登录
- 合并策略:默认 squash merge(保持主干“一提交代表一件事”)
- 回滚:
git revert <sha># 合并提交用 -m 选择主线:git revert -m 1 <merge-sha>
- 还原被回滚:对 revert 再次 revert,或 cherry-pick 原提交
Monorepo 小贴士#
- scope 就用包名:
feat(pkg/ui): 新增日期选择器
- 变更日志建议走 Changesets 或 semantic-release 的多包模式
- 统一工具在根目录配置,子包避免自搞一套
自动生成 CHANGELOG 与发版(可选增强)#
- 简单路线:
standard-version
pnpm add -D standard-versionpnpm releasegit push --follow-tags origin main
- 全自动:
semantic-release
(配合 CI)
pnpm add -D semantic-release @semantic-release/changelog @semantic-release/git \ @semantic-release/github @semantic-release/commit-analyzer @semantic-release/release-notes-generator
[!info] 规则映射
- feat → minor,fix/perf → patch,包含
BREAKING CHANGE:
或!
→ major
常见问题(快问快答)#
- 可以用 Emoji 吗?可以,但放在 body;header 更建议标准格式
- 中文可以吗?可以,团队统一即可;英文类型 + 中文主题是常见搭配
- 提交太碎?PR 层 squash;或在开发时用 fixup,评审完再 autosquash
- 一次提交涉及多模块?用多 scope 或拆两次提交,后者更好读
- 格式化工具引发海量改动?单独一提交处理,避免和功能混在一起
团队落地 Checklist#
- 确认类型与 scope 列表,写进
CONTRIBUTING.md
- 上线 Commitlint + Husky,本地阻断
- 推荐使用
pnpm commit
(cz-git)交互式提交 - CI 上跑一次 commitlint
- 默认 squash merge;大 PR 倾向拆分
- 破坏性变更必须包含
!
与BREAKING CHANGE
描述