前段日子在 GitHub 上翻译了一篇文章,讲的是如何编写更好的 commit 消息。
合并的过程也颇为艰辛,原本以为一上午翻译后差不多就能提交,后来有几位大神在 p/r 的评论区疯狂校对,我就不停修改文档,期间还去了趟重庆,想我在火车上拿着手机在网页更改的样子也很有趣。
经过两个多星期、80多条讨论后,终于将简体中文
版合并到了主仓库,也算是为上过 trending 榜首的 repo 贡献过的人了。
下面是中文版翻译,原仓库为:commit-messages-guide
Commit messages guide
一个了解 commit 信息重要性和如何更好地编写它的指南。
它可以帮助你了解什么是 commit、为什么编写好的信息很重要、最好的实践案例以及一些技巧来计划和(重新)编写良好的 commit 历史。
什么是“commit”?
简单来讲,commit 就是在本地存储库中编写的文件的 快照。与印象中不同的是,git 不仅存储不同版本文件之间的差异,还存储了所有文件的完整版本。对于两个 commit 之间没有被修改的文件,git 只存储指向前一个完全相同的文件的链接。
下面的图片展示了 git 如何随着时间存储数据,其中每个 “Version” 都是一个 commit:
为什么 commit 信息很重要?
- 加快和简化代码审查(code reviews)
- 帮助理解一个更改
- 解释不能只由代码描述的“为什么”
- 帮助未来的维护人员弄清楚为什么以及如何产生的更改,从而使故障排查和调试更容易
为了最大化这些结果,我们可以使用下一节中描述的一些好的实践和标准。
好的实践
这些是从我的经验、互联网文章和其他指南中整理的一些实践经验。如果你有更多的经验(或持不同意见),请随时提交 Pull Request 提供帮助。
使用祈使句
1 | # Good |
1 | # Bad |
不过为什么要使用祈使句呢?
commit 信息描述的是引用的变更部分实际上做了什么,它的效果,而不是因此被做了什么。
Chris Beams 的这篇优秀的文章为我们提供了一些简单的句子,可以帮助我们用祈使句编写更好的 commit 信息:
1 | If applied, this commit will <commit message> |
例子:
1 | # Good |
1 | # Bad |
首字母大写
1 | # Good |
1 | # Bad |
首字母大写的原因是遵守英文句子开头使用大写字母的语法规则。
这种做法可能因人而异、因团队而异、甚至因语言而异。不管是否大写,重要的是要制定一个标准并遵守它。
尽量做到只看注释便可明白而无需查看变更内容
1 | # Good |
1 | # Bad |
1 | # Good |
1 | # Bad |
它在许多场景中(例如多次 commit、多个更改和重构)非常有用,可以帮助审查人员理解提交者的想法。
使用信息本身来解释“原因”、“目的”、“手段”和其他的细节
1 | # Good |
1 | # Good |
1 | # Good |
信息的主题和正文之间用空行隔开。其他空行被视为信息正文的一部分。
像“-”、“*”和“\”这样的字符可以提高可读性。
避免使用无上下文的信息
1 | # Bad |
限制每行字数
这里建议主题最多使用50个字符,正文最多使用72个字符。
保持语言的一致性
对于项目所有者而言:选择一种语言并使用该语言编写所有的 commit 信息。理想情况下,它应与代码注释、默认翻译区域(用于本地化项目)等相匹配。
对于贡献者而言:使用与现有 commit 历史相同的语言编写 commit 信息。
1 | # Good |
1 | # Good (Portuguese example) |
1 | # Bad (mixes English and Portuguese) |
模板
下面是参考模板,最初由 Tim Pope 编写,出现在 Pro Git Book 中。
1 | 用 50 左右或更少的字符描述更改 |
Rebase vs. Merge
这部分是 Atlassian 的优秀教程(TL;DR)——“Merging vs. Rebasing” 的精华。
Rebase
TL;DR: 将你的分支逐个应用于基本分支,生成新树。
Merge
TL;DR: 创建一个新的 commit,称为 merge commit(合并提交),其具有两个分支之间的差异。
为什么一些人更喜欢 rebase 而非 merge?
我特别喜欢 rebase 而不是 merge。原因有以下几点:
- 它的历史信息很”干净”,没有无用的合并 commit。
- 所见即所得,即在代码审查中,所有的更改都能在特定的、有标题的 commit 中找到,避免了隐藏在合并 commit 中的修改。
- 通常 merge 是由提交者实行的,并会为每个转换成 commit 的 merge 书写准确的信息。
- 通常我们不会深挖和复查 merge commit,因此尽量避免使用 merge commit,并确保个变化点都有它们所属的 commit 。
什么时候 squash
“Squashing” 是将一系列 commit 压缩成一个的过程。
它在某些情况下很有用,例如:
- 减少那些很少甚至没有上下文(拼写错误、格式化、缺失内容)的 commit
- 将单独的更改连接在一起使它们更通俗易懂
- 重写 work in progress 的 commit
什么时候避免 rebase 或 squash
避免在多人共同开发的公共 commit 或共享分支上使用 rebase 和 squash。rebase 和 squash 会改写历史记录并覆盖当前 commit,在共享分支的 commit(即推送到远程仓库或来自其他分支的 commit)上执行这些操作可能会引起混乱,由于分支产生分歧及冲突,合作者可能会因此失去他们(本地和远程)的更改。
有用的 git 命令
rebase -i
使用它来压缩提交(squash commits)、 编写信息、 重写/删除/重新编排 commit 等。
1 | pick 002a7cc Improve description and update document title |
fixup
使用它可以轻松清理 commit,而不需要复杂的 rebase。这篇文章提供了很好的示例,说明了如何以及何时进行此操作。
cherry-pick
它在你 commit 到了错误的分支而不需要重新编码时非常有用。
例子:
1 | $ git cherry-pick 790ab21 |
add/checkout/reset [—patch | -p]
假设我们有以下冲突:
1 | diff --git a/README.md b/README.md |
我们可以使用 git add -p
只添加我们想要的补丁,而无需更改已有代码。
它在将一个大的更改分解为小的 commit 或 reset/checkout 特定的更改时很有用。
1 | Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? s |
hunk 1
1 |
|
hunk 2
1 |
|
hunk 3
1 | @@ -202,3 +205,4 @@ Any kind of help would be appreciated. Example of topics that you can help me wi |
其他有趣的内容
喜欢它吗?
贡献
感谢任何形式的帮助。例如:
- 语法和拼写的纠正
- 翻译成其他语言
- 原引用的改进
- 不正确或不完整的信息
灵感、来源以及扩展阅读
- 如何编写 Git Commit Message
- Pro Git Book - Commit 指南
- 关于 Git Commit Messages 的说明
- 合并与变基
- Pro Git Book - 改写历史