| name | chinese-commit |
| description | 写 git commit 时使用。生成规范的 Conventional Commits(英文 type + 中文主题),主题精炼。 |
| category | china |
| tags | ["commit","git","规范"] |
中文 commit 规范
何时用
- 准备执行
git commit 前,需要撰写 commit message 时。
- 对已有 commit message 做 review 或修改时。
- 在 PR 描述中引用 commit 列表,需要判断某条消息是否清晰时。
- 给团队新成员讲解项目提交规范时。
核心规则
1. 格式 = type(scope): 中文主题
规则: type 必须用英文关键字(feat / fix / docs / refactor / test / chore / perf),冒号后的主题用中文,且不超过 50 个字。
为什么: AI 最常见的错误有两种:一是整行全写中文(新增功能:用户登录),导致 CI 的 commit-lint 规则直接报错;二是 type 用中文近义词(特性、修复)或随意缩写(f、upd),让 git log --oneline 的过滤脚本无法识别。格式不统一还会导致自动生成 CHANGELOG 时分类错误,把 bug 修复误放进"新功能"节。
怎么做:
- type 只从以下七个中选一个:
feat(新功能)、fix(缺陷修复)、docs(文档)、refactor(重构,不改行为)、test(测试)、chore(构建/工具链)、perf(性能优化)。
- scope 写在圆括号内,可省略,但存在时必须用真实模块名(见规则 5)。
- 冒号后面跟一个空格,然后是中文主题,不加句号。
2. 主题写"做了什么",用祈使句
规则: 主题用一句祈使句描述本次变更的核心动作,不写流水账,不写"修改了一些文件"之类的废话。
为什么: AI 极容易生成这种主题:更新了登录模块的相关代码——这句话在任何 commit 上都成立,完全没有信息量。另一类错误是记流水账:修改了 auth.py,删除了多余的注释,调整了变量名,顺便加了一个空行。主题不是 diff 摘要,是对"本次提交解决了什么问题"的一句话答案。读 git log --oneline 时,好的主题应当让人一眼知道"要不要点进这个 commit 看细节"。
怎么做:
- 问自己:「这个 commit 的目的是什么?」把答案压缩成一句话。
- 动词放句首,例如:
修复、新增、删除、提取、替换、禁用。
- 不带末尾句号;不用被动句(不写"被修复了")。
- 超过 50 字说明你在一次 commit 里做了多件事,应当拆分(见规则 4)。
3. 正文只在"为什么"不显然时写
规则: commit 正文(body)用来解释动机与权衡,而不是复述 diff 的内容。若改动理由一眼即明,正文可省略。
为什么: AI 倾向于把 diff 内容逐行翻译成正文,例如:将 token_expiry < now 改为 token_expiry <= now——这完全没有价值,读者直接看 diff 就能得到这个信息。真正有用的正文是:旧逻辑在 token 恰好等于当前时间时不视为过期,导致极少数请求绕过鉴权;改为 <= 后临界情况被正确拦截。 这类信息只存在于作者脑子里,不写下来就永久丢失。
怎么做:
- 主题行与正文之间空一行(git 规范要求)。
- 正文用自然段落,每行不超过 72 字,方便
git log 展示。
- 只写"为什么这样改"和"考虑过哪些替代方案、为何放弃",不复述 diff。
- 如果有关联的 issue 或 PR,在正文末尾用
Closes #123 / Refs #456 标注。
4. 一次只提一件事
规则: 一个 commit 只做一件逻辑上内聚的事;功能、修复、格式整理混在一起时,必须拆成多个 commit。
为什么: AI 在帮用户完成任务时容易"顺手"把格式清理、变量重命名、无关 bug 修复一并提交。这类混合 commit 带来三个具体问题:① git bisect 时无法精确定位引入 bug 的节点;② cherry-pick 到其他分支时会带入不需要的副作用;③ code review 时 reviewer 不知道应该关注功能正确性还是格式合规,两件事互相干扰。
怎么做:
- 在提交前用
git diff --staged 扫描暂存区:确认每一处修改都服务于同一个目的。
- 发现夹带了无关改动(例如顺手修了 typo),用
git add -p 把它们拆到单独的 commit。
- 格式化改动(
chore: 统一缩进风格)单独提交,绝不与功能 commit 混在一起。
5. scope 用真实模块名
规则: scope 必须对应项目中真实存在的目录名、模块名或服务名;不编造模糊范围,不用 misc、various、global 之类的占位词。
为什么: AI 在不确定影响范围时会编造一个听起来合理的 scope,例如 fix(backend): …——但项目里根本没有叫 backend 的目录,实际改的是 api/auth 模块。这让基于 scope 过滤 CHANGELOG 的脚本输出混乱,也让后续维护者无法通过 git log --grep 快速锁定某模块的历史变更。
怎么做:
- 打开项目根目录,用真实的顶层目录名或模块名作为 scope,例如
auth、user、payment、db、cli。
- 若改动跨多个模块且无法归为某一个,省略 scope,不要编造一个"最近似"的假名。
- monorepo 中 scope 通常是包名,例如
@app/core,直接用包的短名:core。
正例 / 反例
第一组:主题无信息量 vs. 直击要害
# 反例 — 读者毫无所知,在任何 repo 的任何 commit 上都能贴
update code
# 反例 — 流水账,不说明做了什么,也不说明为什么
改了一堆东西,调整了登录页,还顺便修了一个 bug
# 正例 — 一眼知道改了哪个模块、解决了什么问题
fix(auth): 修复 token 过期判断用 < 导致临界失效
# 正例 — 新功能,主题完整交代了做什么、作用在哪里
feat(payment): 新增支付宝扫码支付入口
第二组:type 错误 vs. 准确选型
# 反例 — type 用中文,CI lint 直接挂
新增: 用户头像上传功能
# 反例 — type 含糊,无法区分功能还是修复
update(profile): 更新了头像上传的逻辑
# 正例 — type 精准,读者立即知道这是新功能
feat(profile): 支持上传 WebP 格式头像并自动压缩至 200 KB 以内
# 正例 — 重构不改行为,用 refactor 区分于 feat/fix
refactor(db): 将裸 SQL 查询提取为 Repository 层统一管理
第三组:带正文的完整 commit(解释 why)
fix(session): 修复并发登录时 session 互相覆盖的问题
旧实现使用用户 ID 作为 session key,同一账号在两台设备同时登录时,
后登录的设备会覆盖前一个设备的 session,导致前者被强制下线但无任何提示。
改为在 session key 中加入设备指纹(device_fingerprint),使每台设备
持有独立的 session。考虑过改用 JWT 无状态方案,但当前需要服务端主动
撤销能力,暂不切换。
Closes #412
自查清单