这篇文章我翻译自How to Write a Git Commit Message,由于文章篇幅太多,全部翻译下来太浪费时间了,因此我把一些重要的地方全部翻译完了,如果英语好的请读原文,英语水平不算太好地同学,可以参考我的翻译来理解原文。如果大家能把我翻译的部分看完,相信大家就可以写出一个好的commit信息了。这篇文章主要介绍的是写好Git的commit信息我们需要遵守的一些规则并说明为什么好的commit信息是如此重要,作者都给出了很好地解释。
文章作者的Github主页为:https://github.com/cbeams,从他的主页可以看出他是spring-framework和gradle的contributor.
如果你去随便找几个Git仓库的日志,你会发现这些仓库的commit信息或多或少地有些混乱。举个例子,大家来看一看我前些日子提交到Spring代码的commit日志:
$ 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
呀!比较一下同一个仓库中最近刚刚提交的commit信息吧。
$ 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
你更喜欢看哪个?前者在长度和形式上都有很大地不同; 而后者是简洁和一致的。前者是通常情况下都会出现的状况; 而后者绝不会偶然发生。然而许多仓库地日志都像前者,当然也有例外地,比如Git项目本身就是一个很好地例子。看看Spring Boot,或者是Tim Pope管理地仓库。
这些仓库地贡献者都知道一个构思好地commit信息是与开发同伴或未来的自己交流改动的最好方式。diff
命令会告诉你改动了什么,但是只有commit信息会告诉你为什么那样去改动。Peter Hutterer很好地解释了这点:
重新建立一段代码的背景情况是会浪费很多地时间。我们不能完全避免它,所以我们应该尽量去减少它。commit信息正好可以做到这点,因此,commit信息也可以表明一个开发者是否是个好地合作者。
If you haven’t given much thought to what makes a great Git commit message, it may be the case that you haven’t spent much time using git log and related tools. 这里有个恶性循环: because the commit history is unstructured and inconsistent, one doesn’t spend much time using or taking care of it. And because it doesn’t get used or taken care of, it remains unstructured and inconsistent.
But a well-cared for log is a beautiful and useful thing. git blame
, revert
, rebase
, log
, shortlog
and other subcommands come to life. Reviewing others’ commits and pull requests becomes something worth doing, and suddenly can be done independently. Understanding why something happened months or years ago becomes not only possible but efficient.
A project’s long-term success rests (among other things) on its maintainability, and a maintainer has few tools more powerful than his project’s log. It’s worth taking the time to learn how to care for one properly. What may be a hassle at first soon becomes habit, and eventually a source of pride and productivity for all involved.
In this post, I am addressing just the most basic element of keeping a healthy commit history: how to write an individual commit message. There are other important practices like commit squashing that I am not addressing here. Perhaps I’ll do that in a subsequent post.
Most programming languages have well-established conventions as to what constitutes idiomatic style, i.e. naming, formatting and so on. There are variations on these conventions, of course, but most developers agree that picking one and sticking to it is far better than the chaos that ensues when everybody does their own thing.
一个团队对于commit日志地方法应该相同。为了创建更有用地版本历史,一个团队首先应该统一定义commit信息地规范。这需要至少定义下面3件事情:
Style. 标记语法, 行间距, 语法, 大写字母开头, 标点符号. 阐明这些事情, 不要去臆测,让一切尽可能地简单。最终将会使日志出奇地
一致,这不仅仅会使大家很乐意去读,而且会使大家经常性地去读日志。
Content. commit信息的主体(如果有的话)应该包含什么样的信息?它不应该包含什么?
Metadata. How should issue tracking IDs, pull request numbers, etc. be referenced?
Fortunately, there are well-established conventions as to what makes an idiomatic Git commit message. Indeed, many of them are assumed in the way certain Git commands function. There’s nothing you need to re-invent. 遵循下面的7条原则,你的committing方式会像一个专家一样。
For example:
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 consequences 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
上面例子中的文字详细地解释了很多原则,大家仔细看一下。
对于一个commit信息来说,从开始到第一个空行被认为是commit头,这个头在Git中被广泛地使用。比如:Git-format-patch(1) 把commit信息转换成Email,主题行用作Email的标题,正文用作Email的body。
如果有些改动非常简单,那么这样的commit信息并不需要正文,只给一个主题行就行。比如下面的例子:
Fix typo in introduction to user guide
如果读者想要看看是怎样地改动的,他只需查看改动本身就可以的,比如用git show
或者 git diff
或者 git log -p
等命令。像这样不需要正文的commit,我们只需要用下面简单的方式commit就行:
$ git commit -m "Fix typo in introduction to user guide"
当一个改动比较复杂时,我们的commit就需要一些正文来解释我们做了什么和为什么这样去做?正文不要包含是怎样做的解释,因为代码本身就可以解释清楚,如果代码本身也不是很清楚,那么我们就应该对这段代码加上注释。比如下面的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.
一旦你日后重新浏览这些提交的信息时,你会发现这样严格要求格式是非常值得的。比如我们用下面的命令来浏览日志:
$ 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)
and turns it back into a chess game.
$ git log --oneline // 只显示主题行的内容
42e769 Derezz the master control program
$ 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中还有很多情况都会用到区分主题与正文,因此我们严格要求自己的commit信息格式是非常值得的。
50个字符并不是强制要求的,这仅仅是个经验法则。这个规则可以确保其可读性,并且会强制作者仔细思考一下,用一种更简明的方式来描述到底发生了什么?
Tip: 如果你很难用50个字符总结出到底发生了什么,那么你有可能一次提交了太多的变化。Strive for atomic commits .
Github的UI也完全意识到了这些规范,如果你的主题行超过了50个字符它会警告你:
Github会用省略符号截断超过72个字符地主题行:
因此最好达到50个字符的限制,但是把72个字符当作是严格的限制。
这个要求很简单。就是主题行要以大写字母开头。比如用下面的格式:
Accelerate to 88 miles per hour
下面的格式是不正确的:
accelerate to 88 miles per hour
主题行的末尾不要加句号。这是因为我们要保持主题行要少于50个字符,因此主题行的空间是很宝贵的。比如用下面的格式:
Open the pod bay doors
下面的格式是不正确的:
Open the pod bay doors.
主题行要用虚拟语气。虚拟语气意味着“口头上或者书面上就好像是给出一个命令或指令一样”。比如:
本屌丝的英语语法真的是很烂,因此去查了一下各种语气的表达方式,大家参考一下吧。
一、 陈述语气
陈述语气用来陈述事实或询问事情,用于陈述句,疑问句或感叹句中。
比如下面的语句:
二、祈使语气
祈使语气表示劝告、命令、希望或禁止等,用原形动词开始,主语常被省略。
比如下面的语句:
三、虚拟语气
虚拟语气用来表示说话人的主观愿望或假想,而不表示客观存在的事实,所说的是一个条件,不一定是事实,或与事实相反。
比如下面的语句:
祈使语气感觉可能有点粗鲁的,因此我们平常不怎么去使用它。但是,对于Git commit的主题行来说,它再适合不过了。而且,Git本身也用祈使语气为我们创建commit. 比如,当我们用git merge
时,它默认给我们创建的commit信息格式如下:
Merge branch 'myfeature'
当用git revert
命令时:
Revert "Add the thing with the stuff"
This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d.
或者当我们点击Github上pull request上的Merge按钮时:
Merge pull request #123 from someuser/somebranch
大家从上面的例子中可以看到,所有的信息都以原形动词开始,因此当你写自己的commit信息时,你应该遵循Git内置的规范,比如下面的例子:
起初以这种方式去表达可能会有点尴尬的。我们表达的方式主要是陈述语气,它是关于陈述事实的。这就是为什么commit信息通常出现下面这样方式的原因:
有时会把commit信息主题行写成它们内容的描述:
这里我们有一个简单的规则可以使我们的commit信息不再混乱。一个格式良好地commit主题行应该总是会补全下面的句子:
If applied, this commit will your subject line here
比如下面的例子就满足了上面的规则:
而对于非祈使形式的commit信息,就不适用上面的规则:
注意:仅仅在主题行用祈使语气是重要的,但是当你写commit的正文时,你可以不用遵循这样的规则。
Git从不会自动地为文本换行。当你写commit信息的正文时,你必须注意它的右间距,手动地对它进行换行。推荐地换行字符数是72个字符,这样Git就会有大量地空间来缩进文本,从而保证整体上每行不会超过80个字符。如果你不想时刻注意着每行的文本数,那么你可以把你的Git提交的默认编辑器改为Vim,你可以安装插件来帮你做这件事情。
下面Bitcoin Core的commit就是一个非常好的例子来解释了what changed and why:
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.
大多数情况下,在你的commit的正文中不需要解释你是怎样做出改变的。因为代码本身会解释这一点,如果代码是非常复杂的,你可以为你的源码加注释来方便日后理解它。因此,我们只需要关注你为什么做出这样地变化,即改变之前事情是怎么工作的,它有什么地主是不对的,现在事情是怎样工作的,为什么你这样做去解决事情的工作方式?
Learn to love the command line. Leave the IDE behind. 要学着去爱上命令行,抛弃IDE.
Git是极其强大的,IDE也是,但是它们强大的地方不同。我每天都会用IDE(IntelliJ IDEA)和其它一些广泛使用的(Eclipse),但是,我从来没见过哪个IDE把Git集成的像它的命令行那样简洁和强大。当然了,在IDE中集成的Git命令是非常棒的,比如当你删除一个文件就像调用git rm
,当你重命名一个文件时就像用Git一样可以把事情做对。但是,当你试着去commit,merge,rebase,或者通过IDE去做一些复杂的历史分析时,一切将变得非常困难或者不可行。如果你想要发挥出git的全部力量,你要一直使用命令行。