Skip to content Skip to main navigation Skip to footer

写好 Git Commit 信息的 7 个建议

写好 Git Commit 信息的 7 个建议
写好 Git Commit 信息的 7 个建议

介绍: 为什么好的提交信息如此重要

当你随意浏览任一 git 仓库的日志,你很可能会发现其中的提交信息或多或少有点乱。举个例子,瞧一瞧我早先提交到 Spring 上的这些宝贝

$ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009"
e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build.
2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests)
147709f Tweaks to package-info.java files
22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils
7f96f57 polishing

狂吐吧!和最近提交到同一个仓库的信息比较一下:

$ git log --oneline -5 --author pwebb --before "Sat Aug 30 2014"
5ba3db6 Fix failing CompositePropertySourceTests
84564a0 Rework @PropertySource early parsing logic
e142fd1 Add tests for ImportSelector meta-data
887815f Update docbook dependency and generate epub
ac8326d Polish mockito usage

你更喜欢阅读哪一个?

前者长度和形式截然不同,后者则简洁一致。前者像是随意为之,而后者才是精心构思的。

虽然很多仓库的日志像前者一样,但也有例外。Linux 内核 和 Git 自己都是很好的例子。Tim Pope 维护的仓库,以及 Spring Boot 也都不错。

这些仓库的贡献者知道,和后续开发者(事实上未来就是他们自己)沟通一个改动的上下文context,最好方法就是通过一个好的 git 提交信息。代码差异diff可以告知改动的内容,但只有提交信息才能正确地告诉你为什么。Peter Hutterer 很好地指出了这一点:

重建一段代码的上下文是一种浪费。我们不能完全避免,我们只能努力尽最大可能去减少它。提交的信息就可以做到这一点,以至于一个提交信息可以表明一个开发者是不是一个好的合作者。

如果你对如何写好 git 提交信息没有仔细想过,那你很可能没有怎么使用过 git log 和相关工具。这里有一个恶性循环:因为提交的历史信息组织混乱而且前后矛盾,那后面的人也就不愿意花时间去使用和维护它。 又因为没有人去使用和维护它,提交的信息就会一直组织混乱和前后矛盾。

但一个好的日志是一个优美和有用的东西,一旦日志处理的好,那么 git blamerevertrebaselogshortlog 和其它子命令都将发挥它们的作用。查看别人的提交和 pull 请求是值得的,而且可以随时独立完成。理解几个月或者几年前发生的代码变动不仅变得可能,而且高效。

一个项目的长期成功依赖于(除了其它方面)它的可维护性,一个维护者最有力的工具就是项目的日志。所以非常值得花时间学习如何正确地维护它。刚开始可能很麻烦,但很快会成为习惯,最终会成为人们自豪和生产力的源泉。

我在这篇文章只会讨论如何保持一个健康的提交历史的最基本要素:即如何写好个人的提交信息。其它重要的实践,比如 commit squashing,我在这里不会涉及。我可能会在后续文章里讨论这些。

对于什么是惯用的约定,即命名和格式等,大多数编程语言都已经形成惯例。这些约定千差万别,但是大多数开发者都同意选择一种并坚持下去,这比让每个人自行其事导致混乱要好得多。

一个团队提交日志的方法应该一致 。为了创建一个有用的修改历史,团队应该首先对提交信息的约定形成共识,至少明确以下三件事情:

风格。包含句语、自动换行间距、文法、大小写、拼写。把这些讲清楚,不要让大家猜,并尽可能保持简单。最终的结果就是一份相当一致的日志,不仅让人读起来很爽,而且可以定期阅读。

内容。哪些信息应该包含在提交信息(如果有)的正文中?哪些不用?

元数据。如何引用问题跟踪 ID,pull 请求编号等?

幸运地是,如何生成一个地道的 git 提交信息已经有完善的约定。事实上,大部分都集成在 git 命令中,你不需要重新发明什么。你只需要遵循如下七条规则,就可以像老手一样提交日志。

七条很棒的 git 提交信息规则

记住:这都是之前都说过的。

  1. 用一个空行隔开标题和正文
  2. 限制标题字数在 50 个字符内
  3. 用大写字母写标题行
  4. 不要用句号结束标题行
  5. 在标题行使用祈使语气
  6. 正文在 72 个字符处换行
  7. 使用正文解释是什么和为什么,而不是如何做

例如:

Summarize changes in around 50 characters or less
More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as the
subject of the commit and the rest of the text as the body. The
blank line separating the summary from the body is critical (unless
you omit the body entirely); various tools like `log`, `shortlog`
and `rebase` can get confused if you run the two together.
Explain the problem that this commit is solving. Focus on why you
are making this change as opposed to how (the code explains that).
Are there side effects or other unintuitive consequenses of this
change? Here's the place to explain them.
Further paragraphs come after blank lines.
 - Bullet points are okay, too
 - Typically a hyphen or asterisk is used for the bullet, preceded
   by a single space, with blank lines in between, but conventions
   vary here
If you use an issue tracker, put references to them at the bottom,
like this:
Resolves: #123
See also: #456, #789

1. 用一个空行隔开标题和正文

来自 git commit 帮助页:

虽然不是必须的,在提交的信息中先用一行简短的文字(少于50个字符)总结改动是好的想法,后面空一行就是更为详细的描述。提交信息中第一个空行前的文字可以作为题目,这个题目会在整个 Git 用到。比如,git-format-patch 把一个提交转化成 email,它就使用这个题目作为邮件的标题,提交中余下的部分作为邮件正文。

首先,不是每一个提交都需要标题和正文。有时简单一行也可以,特别是当这个改动很简单将来也不会变化。比如:

Fix typo in introduction to user guide

不需要再多说什么了。如果读者想知道拼写错误具体是什么,她通过 git showgit diff 或者 git log -p 命令,就可以很容易地看到这个改动。

如果提交的信息像下面命令行那样简单,你可以在 git commit 时使用 -m 开关。

$ git commit -m"Fix typo in introduction to user guide"

但如果一个 commit 需要多解释一点,你就要写一下正文。比如:

Derezz the master control program
MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.

这种情况下使用 -m 开关就不太方便。你真的需要一个合适的编辑器。如果你还没有设定好和 git 命令行一起使用的编辑器,可以参考 Pro Git 的这个章节

无论如何,在浏览日志时,你会发现把标题和正文分开是值得的。下面是完整的日志:

$ git log
commit 42e769bdf4894310333942ffc5a15151222a87be
Author: Kevin Flynn 
Date:   Fri Jan 01 00:00:00 1982 -0200
 Derezz the master control program
 MCP turned out to be evil and had become intent on world domination.
 This commit throws Tron's disc into MCP (causing its deresolution)

现在执行 git log –online,就只印出主题这行:

$ git log --oneline
42e769 Derezz the master control program

或者,git shortlog,按用户来归类提交信息,为了简洁一样也只显示标题。

$ git shortlog
Kevin Flynn (1):
      Derezz the master control program
Alan Bradley (1):
      Introduce security program "Tron"
Ed Dillinger (3):
      Rename chess program to "MCP"
      Modify chess program
      Upgrade chess program
Walter Gibbs (1):
      Introduce protoype chess program

git 中标题和正文之间的差别还体现在许多其他的功能上,但前提是标题和正文之间要有空行,这才能正常工作。

2. 限制标题字数在50个字符内

50个字符不是一个硬性规定,只是一个经验之谈。把标题行限制在这个长度,既保证它们容易阅读,也强迫作者去思考如何用最精简的方式解释发生了什么。

小技巧:如果你发现很难总结,那你可能一次提交太多改动了。 争取作原子的提交(一个题目对应一个单独的提交)

GitHub 的界面充分考虑到这些约定。如果超过50个字符的限制,它会警告你:

写好 Git Commit 信息的 7 个建议
写好 Git Commit 信息的 7 个建议

标题行中超过69字符的部分都被会截断,显示为省略号:

写好 Git Commit 信息的 7 个建议
写好 Git Commit 信息的 7 个建议

所以目标是50个字符,但一定不要超过69个字符。

3. 用大写字母写标题行

这个很简单。标题行的首字母大写。

例如:

Accelerate to 88 miles per hour

而不是:

accelerate to 88 miles per hour

4. 不要用句号结束标题行

标题行不需要考虑标点符号。而且如果希望保持在 50 个字符以内,慎用空格。

例子:

Open the pod bay doors

而不是:

Open the pod bay doors.

5. 在标题行使用祈使语气

祈使语气就是像给一个命令或指示一样叙述或写作。一些例子如下:

  • 打扫你的房间
  • 关门
  • 倒垃圾

你现在读到七个原则中的每一个都是用祈使语气书写的。(“正文在 72 个字符处换行”等)

祈使语气听起来有点粗鲁;所以我们通常不使用它。但对 git 提交信息的标题行而言,它确实很棒。一个原因是当 git 代表你创建一个提交时,它自己就是使用祈使语气。

例如,使用git merge 后的默认输出信息读起来就像:

Merge branch 'myfeature'

当你使用 git revert 时:

Revert "Add the thing with the stuff"
This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d.

或者当在一个 GitHub  pull请求上点击“Merge”按钮时:

Merge pull request #123 from someuser/somebranch

当你用祈使语气编写你的提交信息时,你遵守了 git 内在的约定。例如:

  • 重构 X 子系统以增加可读性
  • 更新新手入门的文档
  • 删除弃用的方法
  • 发布1.0.0版本

一开始这样写会有点尴尬。同样是用来描述事实,我们更习惯用陈述方式去讲话。这也就是为什么提交信息常常读起来都是这样:

  • 用 Y 修正了问题
  • X的改变行为

有时编写的提交信息像是一个内容的描述:

  • 对损坏事物的更多修正
  • 新酷的 API 方法

有一个简单的规则,每次把它做正确就可以减少误解。

一个格式正确的 git 标题行应该类似下面的句子:

如果被采用,这个 commit 将把你的标题放在这里

例如:

  • 如果被采用,这个 commit 将会重构 X 子系统以增加可读性
  • 如果被采用,这个 commit 将会更新新手入门的文档
  • 如果被采用,这个 commit 将移出弃用的方法
  • 如果被采用,这个 commit 将发布 1.0.0 版本
  • 如果被采用,这个 commit 将从 user/branch 合并 #123 pull 请求

注意下面这些非祈使格式不能工作:

  • 如果被采用,这个 commit 将用 Y 修正了问题
  • 如果被采用,这个 commit 将 X 的改变行为
  • 如果被采用,这个 commit 将对损坏事物的更多修正
  • 如果被采用,这个 commit 将新酷的 API 方法

记住:使用祈使语气只在标题上是重要的。你在编写正文的时候,可以放宽这一限制。

6. 正文在72个字符处换行

Git 从来不自动换行。当你编写一个提交信息的正文时,你必须考虑到正确的间距和手动换行。

推荐在72个字符处换行,这样 git 有足够的空间来缩进文本,还可以保持整体都在80个字符以内。

一个好的编辑器在这里可以有所帮助。例如当你编写一个 git 提交时,可以很容易在 Vim 上设定 72 个字符换行。但是通常,IDE 提供文本换行的智能支持都很糟糕(虽然在最近版本中,IntelliJ IDEA终于有所改善)。

7. 使用正文解释是什么和为什么,而不是如何做

这个提交是一个来自 Bittcoin Core 的好例子,解释了什么被改动了和为什么:

commit eb0b56b19017ab5c16c745e6da39c53126924ed6
Author: Pieter Wuille 
Date:   Fri Aug 1 22:57:55 2014 +0200
   Simplify serialize.h's exception handling
   Remove the 'state' and 'exceptmask' from serialize.h's stream
   implementations, as well as related methods.
   As exceptmask always included 'failbit', and setstate was always
   called with bits = failbit, all it did was immediately raise an
   exception. Get rid of those variables, and replace the setstate
   with direct exception throwing (which also removes some dead
   code).
   As a result, good() is never reached after a failure (there are
   only 2 calls, one of which is in tests), and can just be replaced
   by !eof().
   fail(), clear(n) and exceptions() are just never called. Delete
   them.

看一下完整的 diff,可以想象作者当下花了多少时间提供这些上下文信息,这大大节省了同伴和未来提交者的时间。如果他不这么做,这些信息可能就永远消失了。

大多数情况下,你可以省去改动的具体实现细节。 在这点上,代码通常是不需加以说明的(如果代码太复杂需要文字解释,可以使用代码注释)。首先把你做这个改动的原因交待清楚 —— 改动前是如何工作的(和出了什么问题),现在是如何工作的,以及为什么你要采用这种方法解决问题。

未来的维护者会感谢你,这个人可能就是你自己!

小技巧

学着去热爱命令行,抛弃 IDE

拥抱命令行是明智的,理由就像 git 子命令那样多。Git 是强大的,IDE 也是,但是表现的方式不同。我每天都使用 IDE (IntelliJ IDEA),也用过不少其它的IDE(Eclipse),但我看到集成 git 的IDE ,都无法企及命令行的方便和强大(一旦你了解它)。

某些 git 相关的 IDE 功能是很有用的,比如通过调用git rm 删除一个文件,用 git 重新命名一个文件。但一旦你开始使用 commitmergerebase 这些命令,或者进行复杂的历史分析,IDE 就无能为力了。

要发挥 git 的全部火力,那就是命令行。

记住无论你使用 Bash 还是 Z shell,都可以使用 Tab 补齐脚本来减轻记住子命令和开关的痛苦。

阅读 Pro Git

Pro Git 这本书可以在网上免费得到了 ,太棒了。好好利用吧!

0 Comments

There are no comments yet

Leave a comment

Your email address will not be published.