为什么好的提交信息很重要?
如果你随便浏览一下 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内核就是很好的例子。看看Spring Boot,或者Tim Pope管理的任何仓库,这些仓库的贡献者都知道,精心设计的 Git 提交信息是向其他开发者(甚至是未来的自己)传达变化的最好方式。差异会告诉你有什么变化,但只有提交信息才能正确地告诉你原因。Peter Hutterer很好地说明了这一点。
重新建立一段代码的上下文是浪费的。我们不能完全避免它,所以我们的努力应该去尽可能地减少它提交信息正好可以做到这一点,因此,提交信息可以显示出一个开发者是否是一个好的合作者。
如果你没有考虑过怎样才能写出一条好的 Git 提交信息,那可能是你没有花太多时间使用 git log 和相关工具。这里有一个恶性循环:因为提交历史是无序的、不一致的,所以人们不会花很多时间去使用或照顾它。又因为它没有被使用或照顾,所以它仍然是非结构化的和不一致的。
git blame、revert、rebase、log、shortlog和其他子命令都是如此。审查别人的提交和拉动请求成为值得做的事情,而且突然可以独立完成。了解几个月或几年前发生的事情的原因不仅是可能的,而且是有效的。
一个项目的长期成功取决于它的可维护性,而维护者没有什么工具比他的项目日志更强大。值得花时间去学习如何正确地维护它。一开始可能会很麻烦,但很快就会成为习惯,并最终成为所有参与者的骄傲和生产力的来源。
在这篇文章中,我只讨论了保持健康的提交历史的最基本要素:如何编写个人提交信息。还有其他一些重要的做法,比如提交压制,我在这里就不谈了。也许我会在以后的文章中讨论这个问题。
大多数编程语言对于什么是习惯性风格,即命名、格式等等,都有既定的约定。当然,这些惯例也有变化,但大多数开发者都同意,选择一个惯例并坚持下去,要比每个人都做自己的事情时产生的混乱好得多。
一个团队对其提交日志的处理方式也应该如此。为了创建一个有用的修订历史,团队应该首先商定一个提交信息的惯例,至少要定义以下三点。
风格,标记语法、包边、语法、大小写、标点符号。把这些东西写出来,消除猜测,并使之尽可能的简单。最终的结果将是一个非常一致的日志,它不仅是一个阅读的乐趣,而且实际上经常被阅读。
内容,提交信息的正文(如果有的话)应该包含什么样的信息?不应该包含哪些内容?
元数据。应该如何引用问题追踪ID、拉动请求编号等?
幸运的是,对于怎样才能成为一条规范的 Git 提交信息,已经有了成熟的约定。事实上,其中很多都是在某些Git命令的运作方式中假定的。你不需要重新发明什么。只要按照下面的方法,你就能像专家一样提交了。
优秀的Git提交信息的七个规则
请牢记
用大约50个字符或更少的文字来概括变化
如有必要,提供更详细的解释文字。把它包起来,大约72个字符左右。在某些情况下,第一行被视为
的主题,其余部分作为正文。摘要和正文之间的摘要和正文之间的空行是非常重要的(除非你完全省略正文)。
除非你完全省略正文);各种工具,如log
、shortlog
、rebase
等,都会在提交过程中出现问题。
和 "rebase "等各种工具,如果你把这两者放在一起运行,就会产生混淆。
解释这次提交所要解决的问题。重点是为什么
而不是如何改变(代码会解释)。
这个改动是否有副作用或其他不直观的后果?
的副作用或其他不直观的后果?这里就可以解释了。
空行之后是进一步的段落。
- 子弹头也是可以的
- 典型的做法是用连字符或星号来表示项目,前面是一个空格。
前面有一个空格,中间有空行,但惯例是
在此有所不同
如果你使用一个问题跟踪器,把对它们的引用放在底部。
像这样:
Resolves: #123
See also: #456, #789
用空行将主体与正文分开
虽然不是必须的,但在提交信息的开头,最好用一行简短的(少于50个字符)来概述该修改,然后是空白行,再来一个更详尽的描述。提交信息中第一个空行之前的文字被视为提交标题,该标题将在整个 Git 中使用。例如,Git-format-patch(1)把提交变成了电子邮件,它在主题行使用标题,在正文中使用提交的其他内容。
首先,不是每个提交都需要主题和正文。有时只需一行就可以了,特别是当变化非常简单,不需要进一步的上下文。比如说:
修正用户指南介绍中的错别字
没有什么好说的了;如果读者想知道是什么错字,她可以直接看一下修改本身,即用git show或git diff或git log -p。
如果您在命令行上提交这样的东西,很容易使用git commit的-m选项。
git commit -m"F修正用户指南介绍中的错别字"
然而,当一个提交需要解释和说明时,你需要写一个正文。比如说。
主控制程序Derezz
这个承诺将Tron的光盘扔进了MCP(导致其解体)。
并把它重新变成一个棋局。
用-m选项写带正文的提交信息并不那么容易。你最好用一个合适的文本编辑器来写信息。如果你还没有在命令行上为 Git 设置编辑器,请阅读 Pro Git的这一部分。
无论如何,在浏览日志时,将主题和正文分开是有好处的。下面是完整的日志条目。
$ git log
commit 42e769bdf4894310333942ffc5a15151222a87be
Author: Kevin Flynn
Date: Fri Jan 01 00:00:00 1982 -0200
主控制程序Derezz
这个承诺将Tron的光盘扔进了MCP(导致其解体)。
并把它重新变成一个棋局。
而现在git log –oneline,只打印出主题行。
$ git log --oneline
42e769 Derezz the master control program
42e769 Derezz的主控程序
或者,git shortlog,它按用户分组提交,同样只显示主题行,以达到简洁的效果。
$ git shortlog
Kevin Flynn (1):
Derezz的主控程序
Alan Bradley (1):
引入安全程序 "Tron"
Ed Dillinger (3):
将国际象棋程序更名为 "MCP"
修改国际象棋程序
升级国际象棋程序
Walter Gibbs (1):
介绍原版的国际象棋程序
在Git中,还有许多其他情况下,主题行和正文之间的区别开始发挥作用,但如果没有中间的空行,它们都不能正常工作。
将主题行限制在50个字符以内
50个字符不是一个硬性限制,只是一个经验法则。将主题行保持在这个长度,可以确保它们的可读性,并迫使作者思考一下用最简洁的方式来解释发生了什么事。
提示:如果你很难进行总结,你可能一次提交了太多的修改。争取实现原子提交(这是一个单独的帖子的主题)。
GitHub的用户界面完全了解这些惯例。如果你超过了50个字符的限制,它就会警告你。
并会用省略号截断任何超过72个字符的主题行。
因此,争取控制在50个字符以内,但要考虑到72个是硬性限制。
在主题栏中使用大写字母
这和它听起来一样简单。所有主题行都以大写字母开头。
比如说。
Accelerate to 88 miles per hour
Instead of:
而不是:
accelerate to 88 miles per hour
不要以句号结束主题行
在主题行中,尾部标点符号是不必要的。此外,当你试图把它们保持在50个字符或更少时,空间是很宝贵的。
例子。
Open the pod bay doors
Instead of:
而不是:
Open the pod bay doors.
在主题行中使用祈使语气
祈使语气只是意味着 “像发出命令或指令一样说或写”。举几个例子。
打扫你的房间
关好门
*把垃圾倒掉
你现在读到的七条规则中的每一条都是用命令式写的(”在72个字符处包住身体”,等等)。
命令式听起来有点粗鲁;这就是为什么我们不经常使用它。但它很适合用于Git提交的主题行。其中一个原因是,当Git代表你创建一个提交时,它本身就会使用该命令。
例如,使用git merge时创建的默认信息是这样的
Merge branch 'myfeature'
而在使用git revert时。
Revert "Add the thing with the stuff"
This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d.
或者在点击GitHub拉动请求的 “合并 “按钮时。
Merge pull request #123 from someuser/somebranch
因此,当你在命令式中写你的提交信息时,你是在遵循Git自己的内置惯例。比如说
重构子系统X的可读性
更新入门文档
删除废弃的方法
发布1.0.0版本
这样写一开始可能会有点尴尬。我们更习惯于用指示性语气说话,那是关于报告事实的。这就是为什么提交的信息最后往往是这样读的。
Fixed bug with Y
Changing behavior of X
而有时提交的信息会被写成对其内容的描述。
More fixes for broken stuff
Sweet new API methods
为了消除任何疑惑,这里有一个简单的规则,可以让你每次都做得很好。
一个正确的 Git 提交主题行应该总是能够完成以下句子。
If applied, this commit will your subject line here
比如说:
如果应用,此提交将重构子系统X以提高可读性。
如果应用,此提交将更新入门文档
如果应用,此提交将删除废弃的方法
如果应用,此提交将发布 1.0.0 版本。
如果应用,此提交将合并来自用户/分支的 #123 拉动请求。
请注意,这对其他非imperative形式是不起作用的。
如果应用,此提交将修复Y的错误。
如果应用的话,此提交将改变 X 的行为。
如果应用,此提交将修复更多破损的东西
如果应用的话,此提交将提供新的API方法。
请记住。祈使句的使用只在主题行中很重要。在写正文时,你可以放松这一限制。
在72个字符处包住文本
Git从不自动包装文本。当你写提交信息的正文时,你必须注意它的右边距,并手动包装文本。
建议在72个字符时进行,这样Git就有足够的空间来缩进文本,同时还能将所有内容保持在80个字符以内。
一个好的文本编辑器可以帮到你。例如,配置 Vim 很容易,当你写一个 Git 提交时,它可以将文本包在 72 个字符内。然而,传统上,IDE在为提交信息提供智能支持方面一直很糟糕(尽管在最近的版本中,IntelliJ IDEA在这方面终于变得更好。
使用正文来解释什么和为什么与如何。
这个来自Bitcoin Core的提交是一个很好的例子,解释了什么变化和原因。
c`
ommit eb0b56b19017ab5c16c745e6da39c53126924ed6
Author: Pieter Wuille mailto:[email protected]
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.
看看完整的差异,想想作者花时间在这里提供这个背景,为同伴和未来的提交者节省了多少时间。如果他不这样做,可能就会永远消失。
在大多数情况下,你可以不写关于如何进行修改的细节。代码在这方面通常是不言自明的(如果代码非常复杂,需要用散文来解释,那就是源代码注释的作用)。你只需专注于清楚地说明你首先做出修改的原因—修改前的工作方式(以及其中的问题),现在的工作方式,以及你为什么决定以这样的方式来解决它。
未来感谢你的维护者可能就是你自己。
小贴士
学会热爱命令行。把IDE抛在脑后
就像Git的子命令一样多,拥抱命令行是明智的。Git的功能非常强大;IDE也是如此,但各自的方式不同。我每天都在使用IDE(IntelliJ IDEA),也曾广泛使用其他IDE(Eclipse),但我从未见过IDE对Git的整合能与命令行的便捷和强大相提并论(一旦你了解它)。
某些与Git相关的IDE功能是非常宝贵的,比如当你删除一个文件时调用git rm,当你重命名一个文件时用git做正确的事情。当你开始尝试通过IDE提交、合并、重定位或做复杂的历史分析时,一切都会崩溃。
要想充分发挥 Git 的威力,就得用命令行。