Git 提交规范指南
在团队协作的过程中,我发现自己的 Git 仓库历史逐渐变得混乱不堪。搜索后才了解到,Git 提交其实有一套成熟的规范——约定式提交(Conventional Commits)。
这套轻量级的提交信息规范,通过结构化的提交历史支持自动化工具,并与语义化版本(SemVer)形成对应关系。
目录
什么是约定式提交
约定式提交规范是一种基于提交信息的轻量级约定。它提供了一组简单规则来创建清晰的提交历史,这更有利于编写自动化工具。通过在提交信息中描述功能、修复和破坏性变更,使这种惯例与 SemVer 相互对应。
简单来说,它让每一次提交都有明确的「类型标签」,使得:
- 代码审查更容易,可以一眼看出提交意图(然而在中文的情况下,有些时候确实反应不过来)
- 自动生成 CHANGELOG 成为可能
- 版本号可以基于提交历史自动确定
- 团队协作更加高效(吧?)
提交信息格式
每条提交信息应遵循以下结构:
<类型>[可选 范围]: <描述>
[可选 正文]
[可选 脚注]提交说明包含了下面的结构化元素,以向类库使用者表明其意图:
| 元素 | 说明 | 是否必填 |
|---|---|---|
| 类型 | 提交的类型,如 feat、fix |
必填 |
| 范围 | 描述代码某部分的名词,用圆括号包围 | 可选 |
| ! | 破坏性变更标记,放在 : 前 |
可选 |
| 描述 | 对代码变更的简短总结 | 必填 |
| 正文 | 较长的提交正文,提供额外上下文 | 可选 |
| 脚注 | 包含元数据,如 BREAKING CHANGE |
可选 |
核心提交类型
强制类型
规范强制要求的类型只有两个,对应 SemVer 版本:
| 类型 | 说明 | SemVer 对应 |
|---|---|---|
fix |
在代码库中修复了一个 bug | PATCH 版本升级 |
feat |
在代码库中新增了一个功能 | MINOR 版本升级 |
BREAKING CHANGE |
引入了破坏性 API 变更 | MAJOR 版本升级 |
SemVer(语义化版本) 是一套版本号命名规范,格式为
主版本号.次版本号.修订号(如2.1.3),用来向使用者清晰传达每次发布的变更程度。
SemVer 的三段含义
以版本号 MAJOR.MINOR.PATCH 为例:
- MAJOR(主版本号):当你做了不兼容的 API 破坏性变更时递增,例如
1.x.x → 2.0.0 - MINOR(次版本号):当你新增了向下兼容的功能时递增,例如
1.1.x → 1.2.0 - PATCH(修订号):当你做了向下兼容的 bug 修复时递增,例如
1.1.1 → 1.1.2
这套规则来自 semver.org,被 npm、Cargo、Maven 等几乎所有主流包管理器广泛采用。
扩展类型
Angular 约定中常用的扩展类型包括:
| 类型 | 说明 |
|---|---|
build |
用于修改项目构建系统,例如修改依赖库、外部接口或者升级 Node 版本等 |
chore |
用于对非业务性代码进行修改,例如修改构建流程或者工具配置等 |
ci |
用于修改持续集成流程,例如修改 Travis、Jenkins 等工作流配置 |
docs |
用于修改文档,例如修改 README 文件、API 文档等 |
style |
用于修改代码的样式,例如调整缩进、空格、空行等(不影响代码逻辑) |
refactor |
用于重构代码,例如修改代码结构、变量名、函数名等但不修改功能逻辑 |
perf |
用于优化性能,例如提升代码的性能、减少内存占用等 |
test |
用于修改测试用例,例如添加、删除、修改代码的测试用例等 |
⚠️ 其他提交类型在约定式提交规范中并没有强制限制,并且在语义化版本中没有隐式影响(除非它们包含 BREAKING CHANGE)。
破坏性变更的标注方式
破坏性变更的提交表示引入了不兼容的 API 变更,对应 SemVer 的 MAJOR 版本升级。
方式一:类型后加感叹号** **`!`
feat!: send an email to the customer when a product is shipped配合范围的写法:
feat(api)!: send an email to the customer when a product is shipped方式二:脚注声明 BREAKING CHANGE
feat: allow provided config object to extend other configs
BREAKING CHANGE: `extends` key in config file is now used for extending other config files同时使用两种方式
feat!: drop support for Node 6
BREAKING CHANGE: use JavaScript features not available in Node 6.💡 使用
!方式时,脚注中的BREAKING CHANGE:前缀可以省略。
关于范围(Scope)
范围是一个可选的字段,用于为提交类型提供额外的上下文信息,描述代码的某个部分:
feat(parser): adds ability to parse arraysfix(lang): correct Chinese translation error详细规范条文
本文中的关键词 「必须(MUST)」、「禁止(MUST NOT)」、「必要(REQUIRED)」、「应当(SHALL)」、「不应当(SHALL NOT)」、「应该(SHOULD)」、「不应该(SHOULD NOT)」、「推荐(RECOMMENDED)」、「可以(MAY)」 和 「可选(OPTIONAL)」,其相关解释参考 RFC 2119。
规范条文详解(复制粘贴来的)
| 编号 | 规范内容 |
|---|---|
| 01 | 每个提交都 必须 使用类型字段前缀,它由一个名词构成,诸如 feat 或 fix,其后接 可选的 范围字段,可选的 !,以及 必要的 冒号(英文半角)和空格 |
| 02 | 当一个提交为应用或类库实现了新功能时,必须 使用 feat 类型 |
| 03 | 当一个提交为应用修复了 bug 时,必须 使用 fix 类型 |
| 04 | 范围字段 可以 跟随在类型字段后面。范围 必须 是一个描述某部分代码的名词,并用圆括号包围,例如:fix(parser): |
| 05 | 描述字段 必须 直接跟在 <类型>(范围) 前缀的冒号和空格之后。描述指的是对代码变更的简短总结 |
| 06 | 在简短描述之后,可以 编写较长的提交正文,为代码变更提供额外的上下文信息。正文 必须 起始于描述字段结束的一个空行后 |
| 07 | 提交的正文内容自由编写,并 可以 使用空行分隔不同段落 |
| 08 | 在正文结束的一个空行之后,可以 编写一行或多行脚注。每行脚注都 必须 包含一个令牌(token),后面紧跟 :<space> 或 <space># 作为分隔符,后面再紧跟令牌的值(受 git trailer convention 启发) |
| 09 | 脚注的令牌 必须 使用 - 作为连字符,比如 Acked-by(这样有助于区分脚注和多行正文)。有一种例外情况就是 BREAKING CHANGE,它 可以 被认为是一个令牌 |
| 10 | 脚注的值 可以 包含空格和换行,值的解析过程 必须 直到下一个脚注的令牌/分隔符出现为止 |
| 11 | 破坏性变更 必须 在提交信息中标记出来,要么在 <类型>(范围) 前缀中标记,要么作为脚注的一项 |
| 12 | 包含在脚注中时,破坏性变更 必须 包含大写的文本 BREAKING CHANGE,后面紧跟着冒号、空格,然后是描述 |
| 13 | 包含在 <类型>(范围) 前缀时,破坏性变更 必须 通过把 ! 直接放在 : 前面标记出来。如果使用了 !,那么脚注中 可以 不写 BREAKING CHANGE: |
| 14 | 在提交说明中,可以 使用 feat 和 fix 之外的类型 |
| 15 | 工具的实现 必须 不区分大小写地解析构成约定式提交的信息单元,只有 BREAKING CHANGE 必须 是大写的 |
| 16 | BREAKING-CHANGE 作为脚注的令牌时 必须 是 BREAKING CHANGE 的同义词 |
完整示例参考
示例 1:包含描述和脚注中破坏性变更的提交
feat: allow provided config object to extend other configs
BREAKING CHANGE: `extends` key in config file is now used for extending other config files示例 2:使用** **`!`** **标注破坏性变更
feat!: send an email to the customer when a product is shipped示例 3:包含范围和破坏性变更** **`!`
feat(api)!: send an email to the customer when a product is shipped示例 4:同时使用** **`!`** **和 BREAKING CHANGE 脚注
feat!: drop support for Node 6
BREAKING CHANGE: use JavaScript features not available in Node 6.示例 5:不包含正文的提交
docs: correct spelling of CHANGELOG示例 6:包含范围的提交
feat(lang): add Polish language support示例 7:包含多行正文和多行脚注
fix: prevent racing of requests
Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
Remove timeouts which were used to mitigate the racing issue but are
obsolete now.
Reviewed-by: Z
Refs: #123示例 8:修复 bug 带关闭 Issue 引用
fix: prevent racing on requests to accept user-collaborators
Introduce a request id and reference to latest request. Dismiss
incompletely received responses.
Refs: #123示例 9:还原(revert)提交
revert: let us never again speak of the noodle incident
Refs: 676104e, a215868💡 约定式提交不能明确定义还原行为,建议工具开发者基于类型和脚注的灵活性来实现自己的还原处理逻辑。
为什么使用约定式提交
采用约定式提交规范,能为项目带来以下好处:
自动化优势
| 好处 | 说明 |
|---|---|
| 自动生成 CHANGELOG | 基于提交类型自动生成版本更新日志,无需手动维护 |
| 自动版本决策 | 基于提交的类型,自动决定语义化的版本变更(PATCH/MINOR/MAJOR) |
| 触发构建流程 | 可以根据提交类型自动触发不同的 CI/CD 流程 |
团队协作优势
| 好处 | 说明 |
|---|---|
| 清晰传达变化 | 向同事、公众与其他利益相关者传达变化的性质 |
| 结构化提交历史 | 让人们探索一个更加结构化的提交历史,降低对项目做出贡献的难度 |
| 代码审查更高效 | 审查者可以快速了解每次提交的意图和影响范围 |
版本控制优势
fix类型提交 → 自动对应到PATCH版本feat类型提交 → 自动对应到MINOR版本- 带
BREAKING CHANGE的提交 → 自动对应到MAJOR版本
除此之外
在ai的时代,这些已经约定俗成的规矩是很重要的
不了解这些东西可能会导致你在vibe coding的时候寸步难行
一般来说为了安全,我不会让ai直接修改我的git库,
这也是为什么需要学习git的提交规范
在ai横行的情况下,
常见问题 FAQ
Q1: 在初始开发阶段我该如何处理提交说明?
我们建议按照假设你已发布了产品那样来处理。因为通常 有人 在使用你的软件,即便那只是你软件开发的同事们。他们会希望知道诸如修复了什么、哪里不兼容等信息。
Q2: 提交标题中的类型是大写还是小写?
大小写都可以,但最好保持一致。规范要求工具实现 不区分 大小写地解析信息单元,只有 BREAKING CHANGE 必须是全大写的。
Q3: 如果提交符合多种类型我该如何操作?
回退并尽可能创建多次提交。约定式提交的好处之一是能够促使我们做出更有组织的提交和 PR。
Q4: 这不会阻碍快速开发和迭代吗?
它阻碍的是以杂乱无章的方式快速前进。它能帮助你在横跨多个项目以及和多个贡献者协作时长期地快速演进。
Q5: 约定式提交会让开发者受限于提交的类型吗?
约定式提交鼓励我们更多地使用某些类型的提交,比如 fixes。除此之外,约定式提交的灵活性也允许你的团队使用自己的类型,并随着时间的推移更改这些类型。
Q6: 这和 SemVer 有什么关联?
| 提交类型 | SemVer 版本 |
|---|---|
fix |
PATCH |
feat |
MINOR |
带 BREAKING CHANGE 的任何类型 |
MAJOR |
Q7: 如果我不小心使用了错误的提交类型,该怎么办?
使用了规范中但错误的类型(如将 `feat` 写成了 `fix`)
在合并或发布之前,建议使用 git rebase -i 来编辑提交历史。发布之后,根据你使用的工具和流程不同,会有不同的清理方案。
使用了不在规范中的类型(如将 `feat` 写成了 `feet`)
在最坏的场景下,即便提交没有满足约定式提交的规范,也不会是世界末日。这只意味着这个提交会被基于规范的工具错过而已。
Q8: 所有的贡献者都需要使用约定式提交规范吗?
并不!如果你使用基于 squash 的 Git 工作流,主管维护者可以在合并时清理提交信息——这不会对普通提交者产生额外的负担。
一种常见的工作流是让 git 系统自动从 pull request 中 squash 出提交,并向主管维护者提供一份表单,用以在合并时输入合适的 git 提交信息。
Q9: 约定式提交规范中如何处理还原(revert)提交?
还原提交比较复杂:你还原的是多个提交吗?如果你还原了一个功能模块,下次发布的应该是补丁吗?
约定式提交不能明确定义还原行为,所以把这个问题留给工具开发者,基于类型和脚注的灵活性来开发他们自己的还原处理逻辑。
一种建议是使用 revert 类型,和一个指向被还原提交摘要的脚注:
revert: let us never again speak of the noodle incident
Refs: 676104e, a215868Q10: 如何版本化管理对规范的扩展?
如果你对约定式提交做了形如 @jameswomack/conventional-commit-spec 的扩展,我们推荐使用 SemVer 来发布你对于这个规范的扩展(并鼓励你创建这些扩展!)