约定式提交指南:写出清晰、可自动化的 Git 提交信息

约定式提交指南:写出清晰、可自动化的 Git 提交信息

在多人协作和持续交付越来越普遍的今天,提交信息早已不只是“给自己留个备注”。一条清晰的提交记录,既能帮助团队快速理解变更内容,也能为自动生成 CHANGELOG、自动版本发布、代码审查和回溯问题提供基础。

约定式提交(Conventional Commits)就是一套围绕 Git 提交信息建立的轻量规范。它通过统一的格式,把“这次提交做了什么”“影响了哪个模块”“是否存在破坏性变更”等信息明确表达出来,让提交历史既对人友好,也对工具友好。

什么是约定式提交

约定式提交的核心思想很简单:为提交信息加上结构化前缀。

它的基本格式如下:

1
2
3
4
5
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

可以理解为:

1
2
3
4
5
<类型>[可选范围]: <简短描述>

[可选正文]

[可选脚注]

其中最关键的部分是第一行标题,它决定了这次提交的性质。

为什么要使用约定式提交

相较于随意填写的提交说明,约定式提交有几个非常明显的好处:

  • 提交历史更清晰,团队成员一眼就能看懂每次变更的大致类型
  • 便于自动生成变更日志(CHANGELOG)
  • 可以结合工具自动推导语义化版本号
  • 有助于规范团队协作,减少“这次改动到底干了什么”的沟通成本
  • 为 CI/CD、发布流程和代码审查提供结构化输入

如果你的项目已经在使用语义化版本(SemVer),那么约定式提交的价值会更明显,因为它天然能和版本升级规则对应起来。

约定式提交的核心结构

一条标准的提交信息通常由三部分组成:标题、正文、脚注。

1. 标题

标题是最核心的部分,格式通常为:

1
type(scope): description

例如:

1
2
3
feat(auth): support login with GitHub
fix(api): handle empty query parameters
docs: update installation guide

这里包含了三个关键元素:

  • type:提交类型,表示这次提交的性质
  • scope:可选范围,表示影响的模块或子系统
  • description:简短描述,概括本次变更内容

2. 正文

正文是可选的,用来补充说明本次修改的背景、原因和实现细节。通常在以下场景很有价值:

  • 修改逻辑比较复杂
  • 修复的问题不容易从标题看出来
  • 需要解释为何采用某种实现方案

示例:

1
2
3
4
fix: prevent duplicated order creation

Add idempotency check before persisting order records.
This avoids duplicate data when the client retries requests.

3. 脚注

脚注也是可选的,常用于记录额外信息,例如关联 issue、评审人、破坏性变更等。

例如:

1
2
Refs: #123
Reviewed-by: Alice

如果涉及不兼容变更,还可以使用:

1
BREAKING CHANGE: remove legacy token authentication

常见的提交类型

规范中明确强调了两个最重要的类型:

  • feat:新增功能
  • fix:修复缺陷

这两个类型之所以重要,是因为它们通常会和语义化版本建立直接映射关系:

  • fix 对应 PATCH
  • feat 对应 MINOR
  • 包含破坏性变更的提交对应 MAJOR

除了这两个核心类型,社区中还普遍使用以下类型:

  • docs:文档修改
  • style:代码格式调整,不影响逻辑
  • refactor:重构代码,不新增功能也不修复缺陷
  • perf:性能优化
  • test:测试相关修改
  • build:构建系统或依赖变更
  • ci:持续集成配置修改
  • chore:杂项维护工作

这些类型不是规范强制限定的全部,但它们已经成为大多数团队的默认约定。

scope 有什么用

scope 是可选字段,用来标识本次变更影响的范围。它通常是一个模块名、功能域或者子系统名称。

例如:

1
2
3
feat(payment): add Alipay refund support
fix(parser): handle unexpected whitespace
docs(readme): add Docker startup example

它的好处在于:

  • 让提交信息更精确
  • 便于按模块过滤变更历史
  • 在大型项目中更容易快速定位影响范围

不过 scope 不是越多越好。命名应该稳定、统一、易理解,否则反而会让日志变得混乱。

什么是破坏性变更

破坏性变更(Breaking Change)指的是会影响现有使用方式、导致旧版本调用失败或行为改变的修改,比如:

  • 删除旧接口
  • 修改函数签名
  • 改变接口返回结构
  • 移除对旧运行环境的支持

在约定式提交中,破坏性变更可以通过两种方式标记。

方式一:在类型后加 !

1
feat!: remove deprecated config format

或者带范围:

1
feat(api)!: change response schema for user endpoint

方式二:在脚注中写 BREAKING CHANGE

1
2
3
feat: support new configuration loader

BREAKING CHANGE: config files must now use the "sources" field instead of "extends"

如果使用 !,通常也建议在正文或脚注里说明具体哪里不兼容,方便他人理解影响范围。

约定式提交与 SemVer 的关系

约定式提交最实用的一个价值,就是能和语义化版本规则自动衔接。

对应关系通常如下:

提交类型 含义 对应版本
fix 修复 bug PATCH
feat 新增功能 MINOR
BREAKING CHANGE! 不兼容修改 MAJOR

例如:

  • fix: correct cache invalidation logic -> 适合发布补丁版本
  • feat: add export to CSV -> 适合发布次版本
  • feat!: redesign public authentication API -> 适合发布主版本

这也是很多自动发布工具能够根据提交记录直接决定版本号的原因。

一些规范要求

根据约定式提交规范,下面这些点值得特别注意:

  • 每个提交都应该以类型开头
  • 类型后面可以接范围,范围要放在圆括号中
  • 类型和描述之间必须使用英文冒号加空格分隔
  • 描述应该紧跟标题,不要空行
  • 正文如果存在,应该与标题之间空一行
  • 脚注如果存在,应该与正文之间空一行
  • BREAKING CHANGE 必须使用大写

看起来规则不少,但实际上只要掌握几次就会形成习惯。

示例

下面给出几个常见示例。

简单功能提交

1
feat: add dark mode support

带范围的缺陷修复

1
fix(login): prevent redirect loop after session timeout

仅修改文档

1
docs: update API usage examples

带正文的提交

1
2
3
4
fix: prevent racing of requests

Introduce a request id and keep only the latest response.
Older responses are ignored to avoid state corruption.

带脚注的提交

1
2
3
4
refactor(cache): simplify invalidation flow

Refs: #248
Reviewed-by: Bob

标记破坏性变更

1
feat(api)!: remove v1 session endpoint

或者:

1
2
3
feat(api): replace legacy session endpoint

BREAKING CHANGE: clients must migrate from /v1/session to /v2/session

团队落地时的实践建议

理解规范只是第一步,真正重要的是如何在团队中稳定执行。

1. 先统一最小集合

不要一上来设计太多类型。对于大多数团队,先统一这些就够了:

  • feat
  • fix
  • docs
  • refactor
  • test
  • chore

等大家习惯之后,再逐步细化 cibuildperf 等类型。

2. 描述尽量简洁明确

好的描述应该说明“做了什么”,而不是“我改了点东西”。

不推荐:

1
fix: fix bug

推荐:

1
fix(cart): handle coupon expiration during checkout

3. 一个提交只表达一件事

如果一次提交既改了接口,又顺手调整了测试,又更新了 README,后续查看历史时就很难判断这次提交真正的主旨是什么。约定式提交的价值,建立在“单次提交职责清晰”的基础上。

4. 借助工具强制执行

如果希望长期保持一致性,建议搭配工具使用,例如:

  • commitlint:校验提交信息格式
  • husky:在 Git hook 中拦截不合规提交
  • commitizen:通过交互式方式生成提交信息
  • semantic-release:基于提交自动计算版本和发布

规范只靠口头约定很容易失效,而工具可以把“约定”真正变成“流程”。

常见问题

提交类型必须全部小写吗

规范实现通常要求解析时不区分大小写,但实际团队中最好统一风格。最常见的做法是全部使用小写,例如 featfixdocs

一次修改同时属于多个类型怎么办

最好的处理方式通常不是“选一个最接近的类型”,而是回退一步思考:这次修改是否可以拆成多个提交。如果能拆分,提交历史通常会更清晰。

每个人都必须手写规范格式吗

不一定。很多团队会通过 PR 合并时整理提交记录,或者使用 squash merge 时统一编辑最终提交信息。也可以通过交互式工具降低使用门槛。

初期项目也有必要使用吗

有必要。即使项目还在快速迭代阶段,清晰的提交记录依然能帮助团队理解变更、追踪问题,并为后续自动化打基础。

总结

约定式提交并不是为了增加流程负担,而是为了让提交历史更有表达力。它用一套很小的规则,换来了更清晰的协作、更顺畅的发布流程,以及更容易自动化的工程实践。

如果你所在的团队还没有统一提交规范,完全可以从最简单的约定开始:

  • feat 表示新功能
  • fix 表示缺陷修复
  • docs 表示文档更新
  • 对重大不兼容改动明确标记 BREAKING CHANGE

当提交信息开始变得结构化之后,你会发现 Git 历史不再只是“存档记录”,而会逐渐成为项目演进过程中最重要的一份工程文档。