最后更新:
Git 提交消息的写法 - 字数标准与最佳实践
字数标准的约束力到什么程度
"标题行 50 字符、正文 72 字符换行"这一惯例的起源可以追溯到电子邮件的时代。Git 的开发者 Linus Torvalds 把 Linux 内核开发中通过邮件收发补丁的习惯带进了 Git。邮件客户端的标准显示宽度是 80 列,考虑到引用符号和缩进,正文取 72 字符是最合适的。
提交消息的长度之所以被反复讨论,是因为它同时要面向两类读者:在终端里快速浏览历史的人,以及几个月后追查某个变更缘由的人。前者需要一眼能读完的短标题,后者需要正文里完整的背景说明。标题行和正文的字数标准,正是为了同时照顾这两种用法而形成的分工:标题行负责检索,正文负责解释。
需要先分清一点:Git 自身并不限制提交消息的长度。标题行 50 字符、正文 72 字符都不是程序强制的上限,而是为了在终端和各类工具中都便于阅读而形成的运用参考值,超出并不会导致提交失败,只是可读性会打折。另外采用 Conventional Commits 时,前缀 (feat: 或 fix:) 本身就会占掉 5-10 字符,留给说明部分的字数预算相应缩水,落笔时需要把前缀一起算进去。
提交消息的标准格式
被广泛接受的 Git 提交消息格式由三部分组成:
标题行 (最多 50 字符)
正文 (每行 72 字符换行)
脚注 (可选)
标题行和正文之间必须有一个空行。这是 Git 工具链正确解析提交消息的前提。这一结构对应着 git log --oneline 和 GitHub 提交列表中只显示标题行的机制。
各平台在哪里给标题加省略号并没有公开的规格,而且提交列表、仓库首页、拉取请求页面的可用宽度各不相同,还会随界面改版而变化。因此以某个具体位数为目标去微调标题,投入的功夫往往得不到回报。把标题写得足够短、让开头就能读出变更要点,才是更稳妥的做法。
| 要素 | 字数标准 | 理由 |
|---|---|---|
| 标题行 (subject) | 50 字符以内 (英文) | Git 官方手册 (git commit 的 DISCUSSION) 推荐的长度 |
| 标题行 (实用上的上限) | 72 字符以内 | 80 列终端下 git log --oneline 不换行就能容纳的宽度 |
| 正文每行 | 72 字符换行 | 终端标准宽度 (80 列) 扣除缩进后的数值 |
| 正文整体 | 无限制 | 可以按需要写入详细说明 |
全角与半角的字数差异也需要注意。包含中日文等全角字符的提交消息,标题行以 25 字符左右为标准,就不容易在 GitHub 上被截断。
为什么是 50 字符和 72 字符
| 限制 | 字符数 | 技术原因 |
|---|---|---|
| 标题行 | 50 字符 (实用上限 72) | Git 官方手册推荐 50 字符以内;72 字符是 80 列终端下 git log --oneline 的可用宽度 |
| 正文换行 | 72 字符 | git log 默认缩进 4 字符,终端标准宽度 80 字符,80 - 4 - 4 = 72 |
50 字符这个数值有明确的出处:Git 官方手册 (git commit 的 DISCUSSION 一节) 推荐把标题行控制在 50 字符以内。另一个常被提到的 72 字符,则来自 80 列终端下 git log --oneline 留给标题的可用宽度。两者都不是 Git 会强制检查的限制。
72 字符的正文换行规则源于终端的标准宽度 (80 列)。git log 默认在左侧添加 4 个字符的缩进,右侧也预留 4 个字符的边距,因此正文的有效宽度为 72 字符。
这里容易误解的是 git log 的默认 (medium) 格式:提交哈希值单独占一行,标题行和正文统一缩进 4 个空格,标题并不会被哈希值挤掉宽度。真正与 72 字符相关的是 git log --oneline:缩短哈希值 (7 字符) 加一个空格先消耗 8 列,在 80 列终端下留给标题的正好是 72 列。50 字符这一推荐值,就是相对于这个 72 列上限留出余地的"软限制"。
正文 72 字符换行同样有明确的依据。git format-patch 生成的补丁邮件在邮件列表里被引用时,行首会加上 > (2 字符)。两级引用 (> > ) 为 4 字符,再加上 4 字符缩进,要装进 80 列的显示宽度,上限正好是 72 字符。这个计算作为 72 字符规则的说明被广泛共享,不过 Git 的手册本身并没有规定正文的换行宽度。
Conventional Commits 规范
Conventional Commits 是一种结构化的提交消息格式,被越来越多的项目采用。在消息开头加上前缀 (类型),变更的种类就一目了然,还能与 CHANGELOG 的自动生成和语义化版本号联动:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
| 类型 (type) | 含义 | 示例 |
|---|---|---|
| feat | 新功能 | feat(auth): add OAuth2 login |
| fix | 修复 bug | fix(api): handle null response |
| docs | 文档变更 | docs: update API reference |
| style | 代码格式 (不影响逻辑) | style: fix indentation |
| refactor | 重构 (不改变功能) | refactor: extract helper function |
| test | 测试相关 | test: add unit tests for parser |
| chore | 构建/工具变更 | chore: update dependencies |
| perf | 性能优化 | perf(search): add index for full-text query |
| ci | CI/CD 配置变更 | ci: add GitHub Actions workflow |
这一规范的设计思想在于与语义化版本 (SemVer) 的自动联动。feat 对应 MINOR 版本的更新,fix 对应 PATCH 版本的更新,包含 BREAKING CHANGE 脚注的提交则表示 MAJOR 版本的更新。有了这层对应关系,semantic-release、standard-version 等工具就能从提交历史自动确定版本号。
基本格式是 <type>(<scope>): <description>。scope 可以省略,用于标明变更所涉及的模块或组件。
Conventional Commits 的优势在于可以自动生成 CHANGELOG、自动确定语义化版本号,以及让提交历史更加结构化和可搜索。使用前缀后,只要浏览 git log --oneline 的输出就能把握变更的全貌。团队开发时,把前缀的种类和用法写成文档,运用起来会更顺畅。
为什么推荐使用祈使语气 (imperative mood)
英文提交消息推荐祈使语气,理由植根于 Git 自身的设计。Git 自动生成的消息 (Merge branch 'feature'、Revert "Add login form") 都使用祈使语气。用户写的消息与之保持一致,git log 的整体输出就会形成统一的文体。
祈使语气的消息可以读作"应用这个提交之后会发生什么"的句子。Add user authentication 意为"添加用户认证",简洁地表达了提交的效果;而 Added user authentication (过去式) 是"添加了用户认证"的报告,记录的是作业过程而不是提交的效果。
判断不清时,可以把消息填进 "If applied, this commit will ___" 的空格里,看读起来是否自然。If applied, this commit will add user authentication 很自然,而 If applied, this commit will added user authentication 在语法上就不通顺。
好的提交消息 vs 坏的提交消息
提交消息的质量直接关系到项目的可维护性。下面把实务中常见的坏例子与改进后的好例子对照列出。
| ❌ 坏的示例 | ✅ 好的示例 | 改善点 |
|---|---|---|
| fix bug | fix(auth): prevent session timeout on idle | 明确了修复的内容和范围 |
| update | feat(dashboard): add real-time chart updates | 说明了具体的变更内容 |
| WIP | refactor(api): extract validation middleware | 描述了重构的具体操作 |
| asdfgh | docs: add deployment guide for production | 提供了有意义的描述 |
| Fixed the thing that was broken in the last commit because it was not working properly | fix(auth): restore session after token refresh | 冗长且大幅超过 50 字符,改为简洁表述 |
- 标题行用祈使语气:用 "Add" 而不是 "Added",用 "Fix" 而不是 "Fixed",与 Git 自身生成的消息 (Merge branch...) 文体统一
- 标题行末尾不加句号:从节省空间和惯例两方面都推荐这样做
- 把"为什么"写进正文:标题行写"做了什么",正文写"为什么需要这个变更"
- 一个提交 = 一个逻辑变更:不要把多个互不相关的变更塞进一个提交
标题行的写作规则
- 使用祈使语气:写"Add feature"而不是"Added feature"或"Adding feature"。这与 Git 自动生成的消息 (如 "Merge branch") 保持一致。
- 首字母大写:标题行的第一个字母大写 (使用 Conventional Commits 时,type 小写,description 首字母大写)。
- 不加句号:标题行末尾不加句号。这是节省字符的惯例。
- 说明"做了什么"而非"怎么做的":"Fix login error"比"Change auth.js line 42"更有价值。
正文的写作指南
- 变更的动机和背景
- 与之前实现方式的对比
- 可能的副作用或注意事项
- 相关的 Issue 或 PR 编号
中文提交消息的特殊考虑
虽然英文是 Git 提交消息的主流语言,但在中文团队中使用中文提交消息也是合理的选择。此时字符数与字节数的区别会带来实际问题:Git 内部以 UTF-8 处理字符串,一个全角字符要消耗 3 字节。而 GitHub 的标题截断并非按字节数、而是按显示宽度 (列数) 判定,全角字符占 2 列。也就是说,能装进 50 列显示宽度的中文最多是 25 字。需要注意:
- 中文字符在等宽字体中通常占 2 个字符宽度,因此 50 字符的标题行实际上只能容纳约 25 个中文字符
- 72 字符的换行规则对应约 36 个中文字符
- 混合中英文时,注意字符宽度的计算
- Conventional Commits 的 type 和 scope 建议保持英文,description 可以使用中文
在终端里查看 git log 时,多字节字符的宽度计算也会成为问题。多数终端模拟器依据 East Asian Width 属性把全角字符按 2 列宽显示,但在部分环境 (尤其是旧版 Windows 命令提示符) 中宽度计算不正确,显示就会错乱。如果 git log --oneline 的输出列对不齐,请检查终端的字符宽度设置。
团队采用中文提交消息时,"前缀用英文、说明用中文"的混合方式很实用。写成 fix(auth): 修复登录时的会话恢复 这样,既能让 git log --oneline --grep="^fix" 的按类型过滤继续生效,也让中文母语者读起来更轻松。
squash merge 与 revert 的消息设计
使用 squash merge 时,多个提交会被合并成一个,消息的设计因此与普通提交不同。GitHub 的 squash merge 默认把拉取请求的标题作为标题行、把各个提交消息以列表形式插入正文 (这一默认值可以在仓库设置中更改)。直接沿用这段自动生成的消息,正文往往会变得冗长。squash merge 时,用拉取请求的说明文作为正文,简洁归纳变更的目的和影响范围更为有效。
revert 提交的消息,惯例是直接沿用 Git 自动生成的 Revert "<original subject>" 格式。正文中除了原提交的哈希值 (This reverts commit <hash>.) 之外,务必写明 revert 的理由。缺少理由,日后追溯历史时就无法弄清"为什么要撤销"。如果一次性 revert 多个提交,像 revert: undo feature X (commits abc..def) 这样明确标出范围,历史追踪会更容易。
团队开发中的提交消息运用规则
- 引入 commitlint:自动检查是否符合 Conventional Commits 规范的工具。集成到 CI 后,可以在合并前发现违反规范的提交
- 活用 Git hooks (husky):用
commit-msg钩子在本地环境也验证消息的格式 - 设置模板:用
git config commit.template设定团队通用模板,防止漏写 - 代码审查时也检查消息:在拉取请求的审查中,把提交消息的质量也纳入检查对象
下面给出 commitlint 与 husky 组合的配置示例。用 npm install --save-dev @commitlint/cli @commitlint/config-conventional husky 安装后,在项目根目录创建 commitlint.config.js,写入 module.exports = { extends: ['@commitlint/config-conventional'] };。接着用 npx husky init 初始化 husky,在 .husky/commit-msg 文件中写入 npx --no -- commitlint --edit $1,提交时就会自动运行消息验证。
与命名规范与长度指南一样,提交消息的规则也应配合团队的规模和文化分阶段引入才现实。先从统一前缀开始,习惯之后再引入 commitlint,这种循序渐进的做法更为有效。
AI 生成提交消息的活用与注意事项
GitHub Copilot 及各类编辑器扩展提供的提交消息自动生成功能,可以大幅减少撰写消息的工夫。它们会解析差异 (diff) 来概括变更内容,因此在"改了什么"的描述精度上往往较高。
不过自动生成也有局限。最大的弱点是无法推测"为什么需要这个变更"。从代码差异中读不出变更的动机和业务背景,因此应写进正文的"Why"部分需要由人来补全。此外自动生成的消息容易冗长,超过 50 字符标题限制的情况也不少。不要直接照用生成结果,务必由人来审查、删去多余信息并简洁归纳,这个习惯很重要。
Git 工具中的字符限制
| 工具/平台 | 标题显示 | 截断行为 |
|---|---|---|
| git log --oneline | 完整显示 | 终端宽度截断 |
| GitHub 提交列表 | 依页面宽度而定 | 过长时加省略号 (位置未公开) |
| GitHub PR 合并消息 | 完整显示 | 不截断 |
| GitLab 提交列表 | 依页面宽度而定 | 过长时加省略号 (位置未公开) |
| VS Code 源代码管理 | 完整显示 | 50 字符处显示警告 |
总结
Git 提交消息的字数控制不是形式主义,而是确保团队协作效率和项目可维护性的实践。标题行 50 字符以内、正文 72 字符换行是经过时间验证的最佳实践,这些数值源自终端 80 列的宽度和邮件引用的惯例,但都属于运用上的参考值,Git 自身并不限制消息的长度。采用 Conventional Commits 规范可以让变更种类一目了然,还能对接 CHANGELOG 的自动生成。用中文写消息时要注意全角字符的显示宽度,标题行以 25 字符左右为标准。好的消息会简洁地传达"改了什么""为什么改",坏的消息则含糊且信息量不足。写提交消息前使用字符计数器确认字数,养成良好的提交习惯。