Git提交规范笔记:feat和fix到底该怎么写
Git提交规范笔记:feat和fix到底该怎么写
省流:
这套规范叫 Conventional Commits
常用格式:
type(scope): description
feat = 新功能
fix = 修 bug
scope 可选:
feat: add login
feat(auth): add login
不要写:
feat(): add login
破坏性变更:
feat(api)!: change user endpoint
好家伙,
今天整理 Git 提交信息规范。
这套写法来自 Conventional Commits 1.0.0 官方规范,不是 Git 强制语法,而是一套社区约定。
它的价值很简单:
让人一眼看懂这次提交做了什么。
也让工具能根据提交信息生成 changelog 或判断版本号。
Conventional Commits 官方规范也说明,它很大程度上受 Angular Commit Guidelines 启发。
0.为什么提交信息也要有规范
先说一个很现实的问题。
如果一个项目的提交记录长这样:
update
fix
修改
再改一下
临时提交
bug fix
过两周再回头看,基本等于没写。
你只能一个个点开 diff 猜:
这次到底改了什么?
是加功能?
是修 bug?
是改文档?
还是只是格式化?
提交信息规范要解决的就是这个问题。
它不是为了装专业。
它是为了让提交记录能被人看懂,也能被工具识别。
比如:
feat
fix
docs
refactor
test
chore
这些词放在提交信息最前面,相当于给每次提交打了一个类型标签。
以后看日志时,就能快速知道:
哪些提交是新功能
哪些提交是修 bug
哪些提交只是文档或测试
哪些提交可能影响版本号
1.先看基本格式
Conventional Commits 的基本格式可以先记成这样:
<type>(<scope>): <description>
更完整一点是:
<type>[optional scope][!]: <description>
[optional body]
[optional footer(s)]
先别被完整格式吓到。
日常最常用的就是第一行。
比如:
feat(blog): add draft import command
fix(publish): avoid duplicate image upload
docs(readme): add setup guide
可以看这张图:

里面最关键的是四块:
type: 这次改动是什么类型
scope: 这次影响哪个模块,可选
!: 是否是破坏性变更,可选
description: 一句话说明做了什么
所以不要把它理解成复杂语法。
它本质上就是:
用固定格式说清楚这次提交的性质。
2.feat是什么
feat 表示:
新增功能。
比如你给项目加了一个之前没有的能力:
feat(auth): add password login
feat(blog): support markdown import
feat(api): add user profile endpoint
这些都属于 feat。
我现在的判断方法是:
用户或调用方能感知到一个新能力,大概率就是 feat。
比如在这个博客项目里:
支持导入 Markdown 文件
支持上传图片到博客园
支持生成发布预览
这些都算功能。
所以可以写:
feat(draft): import markdown file
feat(publish): upload local images
feat(preview): generate cnblogs publish preview
3.fix是什么
fix 表示:
修复 bug。
比如之前功能已经存在,但是有问题:
fix(publish): handle png upload failure
fix(parser): keep code block indentation
fix(cli): show readable error on missing token
我现在的判断方法是:
原来应该能正常工作,但实际不正常,这次把它修好了,就是 fix。
比如:
图片路径没有被替换成远程地址
发布时重复上传同一张图片
Markdown 表格渲染错位
命令报错信息不清楚
这些都更适合写 fix。
4.scope是什么
scope 就是括号里的部分。
比如:
feat(auth): add password login
fix(publish): retry image upload
docs(readme): add install steps
这里的:
auth
publish
readme
就是 scope。
它表示这次提交主要影响哪个范围。
scope 不是必须的。
如果不用 scope,就直接写:
feat: add markdown import
fix: avoid empty title
注意这里有一个容易写错的地方:
feat(): add markdown import
这种写法不推荐。
因为 scope 是可选的。
如果没有 scope,就把括号整个省掉:
feat: add markdown import
如果有 scope,再写括号:
feat(draft): add markdown import
也就是说:
有范围 -> feat(scope): xxx
没范围 -> feat: xxx
不要写空括号。
5.除了feat和fix,还有哪些常见type
一开始不用背太多。
先把这些常见的用准就行:

我现在会这样记:
| type | 什么时候用 | | --- | --- | | feat | 新增功能 | | fix | 修复 bug | | docs | 只改文档 | | style | 只改格式,不影响逻辑 | | refactor | 重构代码,行为不变 | | test | 新增或修改测试 | | chore | 杂项维护 | | perf | 性能优化 | | build | 构建系统相关 | | ci | CI/CD 流水线相关 |
比如:
docs(api): add publish command example
style(markdown): format heading spacing
refactor(storage): split json store helpers
test(publish): cover cnblogs image upload
chore(deps): update dev dependencies
perf(search): reduce repeated file scan
ci(test): run unit tests on pull request
这里有个判断技巧:
看这次提交给项目带来的主要变化是什么。
如果同时改了很多东西,说明这次提交可能太大了。
最好拆成多次提交。
6.破坏性变更怎么写
如果这次提交会破坏原来的使用方式,就要标出来。
比如一个接口原来是:
GET /api/user?id=1
现在改成:
GET /api/users/1
老代码会挂。
这就是破坏性变更。
Conventional Commits 里可以这样写:
feat(api)!: change user endpoint path
或者在 footer 里写:
feat(api): change user endpoint path
BREAKING CHANGE: /api/user?id=1 was replaced by /api/users/:id
这里的 ! 很醒目。
它告诉别人:
这次提交不是普通 feat,可能需要改调用代码。
如果项目做版本管理,破坏性变更通常会对应 major 版本升级。
7.提交正文和footer什么时候写
大多数日常提交,一行就够。
比如:
fix(publish): handle empty image alt text
但如果这次改动比较复杂,可以加正文:
fix(publish): handle empty image alt text
Some markdown images may not provide alt text.
Use an empty string instead of failing the publish flow.
正文主要解释:
为什么这么改
有什么背景
有什么影响
footer 通常放这些东西:
BREAKING CHANGE
Closes #123
Refs #456
比如:
fix(publish): retry cnblogs image upload
Closes #18
这个对开源项目和团队协作更有用。
个人项目可以先不复杂化。
8.我现在会怎么写
如果只是我自己的项目,我会先定一个简单版本:
feat: 新功能
fix: 修 bug
docs: 文档
refactor: 重构
test: 测试
chore: 杂项
scope 可以用,但不要滥用。
比如这个项目可以有:
draft
publish
style
cli
storage
cnblogs
提交时就写:
feat(draft): import markdown article
fix(cnblogs): upload png images before publish
docs(flow): add publish checklist
refactor(cli): split publish commands
test(storage): cover publication record update
chore: clean generated cache
我会避免这些提交信息:
update
fix bug
wip
改一下
临时提交
不是说这些绝对不能用。
但如果一个项目全是这种提交,后面回头看日志会很痛苦。
9.容易踩的坑
第一个坑:
把所有提交都写成 fix。
加功能不是 fix。
改文档也不是 fix。
重构也不是 fix。
第二个坑:
feat(): xxx
如果没有 scope,不要写空括号。
写:
feat: xxx
第三个坑:
一次提交塞太多东西。
比如一个提交里同时:
加功能
修 bug
改文档
格式化全项目
这种提交很难写清楚 type。
更好的做法是拆开。
第四个坑:
description 写得太虚。
比如:
feat: update code
fix: fix problem
这跟没写差不多。
更好一点:
feat(publish): add cnblogs preview command
fix(markdown): preserve fenced code indentation
10.总结
这篇先记住一句话:
Conventional Commits 不是让提交信息变复杂,而是让提交历史变清楚。
最常用格式:
type(scope): description
最常用两个 type:
feat: 新功能
fix: 修 bug
scope 是可选的:
feat(draft): import markdown file
feat: import markdown file
不要写:
feat(): import markdown file
如果一个提交写不清楚 type,通常说明:
这次提交太大了,应该拆。
下一次写提交信息时,我会先问自己:
这次提交给项目带来的主要变化是什么?
然后再选 type。
参考资料
这是 Conventional Commits 的官方规范。这里主要参考它对 feat、fix、scope、破坏性变更和提交格式的定义。
Conventional Commits 官方规范提到它受 Angular 提交规范启发。这里引用它,是为了说明 feat、fix 这类写法在前端和开源社区里很早就有实践来源。

浙公网安备 33010602011771号