约定式提交指南:写出清晰、可自动化的 Git 提交信息
约定式提交指南:写出清晰、可自动化的 Git 提交信息
在多人协作和持续交付越来越普遍的今天,提交信息早已不只是“给自己留个备注”。一条清晰的提交记录,既能帮助团队快速理解变更内容,也能为自动生成 CHANGELOG、自动版本发布、代码审查和回溯问题提供基础。
约定式提交(Conventional Commits)就是一套围绕 Git 提交信息建立的轻量规范。它通过统一的格式,把“这次提交做了什么”“影响了哪个模块”“是否存在破坏性变更”等信息明确表达出来,让提交历史既对人友好,也对工具友好。
什么是约定式提交
约定式提交的核心思想很简单:为提交信息加上结构化前缀。
它的基本格式如下:
1 |
|
可以理解为:
1 |
|
其中最关键的部分是第一行标题,它决定了这次提交的性质。
为什么要使用约定式提交
相较于随意填写的提交说明,约定式提交有几个非常明显的好处:
- 提交历史更清晰,团队成员一眼就能看懂每次变更的大致类型
- 便于自动生成变更日志(CHANGELOG)
- 可以结合工具自动推导语义化版本号
- 有助于规范团队协作,减少“这次改动到底干了什么”的沟通成本
- 为 CI/CD、发布流程和代码审查提供结构化输入
如果你的项目已经在使用语义化版本(SemVer),那么约定式提交的价值会更明显,因为它天然能和版本升级规则对应起来。
约定式提交的核心结构
一条标准的提交信息通常由三部分组成:标题、正文、脚注。
1. 标题
标题是最核心的部分,格式通常为:
1 |
|
例如:
1 |
|
这里包含了三个关键元素:
type:提交类型,表示这次提交的性质scope:可选范围,表示影响的模块或子系统description:简短描述,概括本次变更内容
2. 正文
正文是可选的,用来补充说明本次修改的背景、原因和实现细节。通常在以下场景很有价值:
- 修改逻辑比较复杂
- 修复的问题不容易从标题看出来
- 需要解释为何采用某种实现方案
示例:
1 |
|
3. 脚注
脚注也是可选的,常用于记录额外信息,例如关联 issue、评审人、破坏性变更等。
例如:
1 |
|
如果涉及不兼容变更,还可以使用:
1 |
|
常见的提交类型
规范中明确强调了两个最重要的类型:
feat:新增功能fix:修复缺陷
这两个类型之所以重要,是因为它们通常会和语义化版本建立直接映射关系:
fix对应PATCHfeat对应MINOR- 包含破坏性变更的提交对应
MAJOR
除了这两个核心类型,社区中还普遍使用以下类型:
docs:文档修改style:代码格式调整,不影响逻辑refactor:重构代码,不新增功能也不修复缺陷perf:性能优化test:测试相关修改build:构建系统或依赖变更ci:持续集成配置修改chore:杂项维护工作
这些类型不是规范强制限定的全部,但它们已经成为大多数团队的默认约定。
scope 有什么用
scope 是可选字段,用来标识本次变更影响的范围。它通常是一个模块名、功能域或者子系统名称。
例如:
1 |
|
它的好处在于:
- 让提交信息更精确
- 便于按模块过滤变更历史
- 在大型项目中更容易快速定位影响范围
不过 scope 不是越多越好。命名应该稳定、统一、易理解,否则反而会让日志变得混乱。
什么是破坏性变更
破坏性变更(Breaking Change)指的是会影响现有使用方式、导致旧版本调用失败或行为改变的修改,比如:
- 删除旧接口
- 修改函数签名
- 改变接口返回结构
- 移除对旧运行环境的支持
在约定式提交中,破坏性变更可以通过两种方式标记。
方式一:在类型后加 !
1 |
|
或者带范围:
1 |
|
方式二:在脚注中写 BREAKING CHANGE
1 |
|
如果使用 !,通常也建议在正文或脚注里说明具体哪里不兼容,方便他人理解影响范围。
约定式提交与 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 |
|
带范围的缺陷修复
1 |
|
仅修改文档
1 |
|
带正文的提交
1 |
|
带脚注的提交
1 |
|
标记破坏性变更
1 |
|
或者:
1 |
|
团队落地时的实践建议
理解规范只是第一步,真正重要的是如何在团队中稳定执行。
1. 先统一最小集合
不要一上来设计太多类型。对于大多数团队,先统一这些就够了:
featfixdocsrefactortestchore
等大家习惯之后,再逐步细化 ci、build、perf 等类型。
2. 描述尽量简洁明确
好的描述应该说明“做了什么”,而不是“我改了点东西”。
不推荐:
1 |
|
推荐:
1 |
|
3. 一个提交只表达一件事
如果一次提交既改了接口,又顺手调整了测试,又更新了 README,后续查看历史时就很难判断这次提交真正的主旨是什么。约定式提交的价值,建立在“单次提交职责清晰”的基础上。
4. 借助工具强制执行
如果希望长期保持一致性,建议搭配工具使用,例如:
commitlint:校验提交信息格式husky:在 Git hook 中拦截不合规提交commitizen:通过交互式方式生成提交信息semantic-release:基于提交自动计算版本和发布
规范只靠口头约定很容易失效,而工具可以把“约定”真正变成“流程”。
常见问题
提交类型必须全部小写吗
规范实现通常要求解析时不区分大小写,但实际团队中最好统一风格。最常见的做法是全部使用小写,例如 feat、fix、docs。
一次修改同时属于多个类型怎么办
最好的处理方式通常不是“选一个最接近的类型”,而是回退一步思考:这次修改是否可以拆成多个提交。如果能拆分,提交历史通常会更清晰。
每个人都必须手写规范格式吗
不一定。很多团队会通过 PR 合并时整理提交记录,或者使用 squash merge 时统一编辑最终提交信息。也可以通过交互式工具降低使用门槛。
初期项目也有必要使用吗
有必要。即使项目还在快速迭代阶段,清晰的提交记录依然能帮助团队理解变更、追踪问题,并为后续自动化打基础。
总结
约定式提交并不是为了增加流程负担,而是为了让提交历史更有表达力。它用一套很小的规则,换来了更清晰的协作、更顺畅的发布流程,以及更容易自动化的工程实践。
如果你所在的团队还没有统一提交规范,完全可以从最简单的约定开始:
- 用
feat表示新功能 - 用
fix表示缺陷修复 - 用
docs表示文档更新 - 对重大不兼容改动明确标记
BREAKING CHANGE
当提交信息开始变得结构化之后,你会发现 Git 历史不再只是“存档记录”,而会逐渐成为项目演进过程中最重要的一份工程文档。