为什么说 commit message 非常重要

许多团队不注重 commit message 的维护,导致项目的提交历史非常混乱,最后必须诉诸于人工手段来解决一些代码提交问题,但这些不必要的事情其实都可以通过一定的机制去避免的。

好的 commit message 可以做到:

  • 让提交记录更加有价值,开发者无须通过阅读代码就可以通过自然语言来获取更抽象的提交描述
  • 规范化的提交记录有助于自动化分析,比如自动生成 ChangeLog,自动提取 Bugfix 记录等等
  • 帮助开发者整理自己的提交记录,不要出现臃肿的 commit(比如一个 commit 中包含多个关联性较差的改动);
  • 让 Reviewer 能更好地理解 commit 的意图

等等。

遗憾的是,如果项目一开始不注重 commit message 的维护,主要开发人员形成了一定的惰性,要想强制让大家遵守规范就相对比较困难,需要采用一些手段去纠正,不过最好的手段还是采用自动化的规范检测,不符合规定的 commit 就拒绝进入主干,从而非人为地强制大家去遵守开发规范。

几种不同的风格

AngularJS 社区风格

Angular规范 (更详细的文档可见 “参考资料”)使用相对较广,也有相应的自动化工具进行支持,比如

由 AngularJS 衍生出了 Conventional Commits,基本上大同小异。

AngularJS 社区风格遵循以下格式

1
2
3
4
5
<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>

每一条 commit message 由 headerbodyfooter 组成。subject 后的空行有助于让 git 认为这是一个 summary。

header 是必须有,scope 和 footer 是可选项。

每一行不要超过 100 个字符,便于阅读

其中:

  • header

    • type 用于说明 commit 类别只允许有以下几种标识

      • build:引起 build system 或者外部依赖项改变;
      • ci:CI 配置的改变;
      • docs:只是文档的改变;
      • feat:新的 feature;
      • fix:Bugfix;
      • perf:为提升性能而作出的代码改变;
      • refractor:重构(不是 fix 也不是 feat);
      • style:只是需改代码的 format;
      • test:总结测试代码或者修改测试逻辑;

      如果 type 是 feat 或者 fix,commit 肯定会出现在 ChangeLog 中。

    • scope 用于说明 commit 影响的范围

    • subject 是 commit 目的的简短描述,要做到:

      • 动词开头,使用第一人称现在时
      • 第一个字母小写
      • 结尾不加 .
  • body

    body 是对本次 commit 的详细描述,可以分成多行,比如:

    1
    2
    3
    4
    5
    6
    7
    
    More detailed explanatory text, if necessary.  Wrap it to 
    about 72 characters or so. 
    
    Further paragraphs come after blank lines.
    
    - Bullet points are okay, too
    - Use a hanging indent
    

    要注意:

    • 使用第一人称现在时
    • 说明代码变动的动机以及与以前行为的对比
  • footer

    footer 只用于两种情况:

    • 不兼容改动,以 BREAKING CHANGE: 开头,后面是对变动的描述,以及变动理由和迁移方法,比如:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      
      BREAKING CHANGE: isolate scope bindings definition has changed.
      
      To migrate the code follow the example below:
      
      Before:
      
      scope: {
          myAttr: 'attribute',
      }
      
      After:
      
      scope: {
          myAttr: '@',
      }
      
      The removed `inject` wasn't generaly useful for directives so there 
      
    • 关闭 issue

      如果当前 commit 针对某个 issue,可在 footer 部分关闭这个 issue:

      1
      
      Closes #234
      

      也可同时关闭多个:

      1
      
      Closes #123, #245, #992
      
  • revert

    如果当前 commit 用于撤销以前的 commit,则必须以 revert: 开头,后面紧跟被撤销 commit 的 header:

    1
    2
    3
    
    revert: feat(pencil): add 'graphiteWidth' option
    
    This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
    

    body 部分是固定的,必须写成:This reverts commit <hash>. 其中 hash 是被撤销 commit 的 SHA。

    如果当前 commit 和被撤销的 commit 在同一个 release 中,那么它们都不会出现在 ChangeLog 里。如果二者在不同的发布中,那么当前 commit 会出现在 ChangeLog 的 Reverts 小标题下。

PingCAP 社区风格

PingCAP 社区指导文档中有提及好的 commit message 要有以下几个元素:

  • 你的改动是什么

    可以是 Bugfix,Feature,Improving peformance 等等;

  • 为什么需要进行这些改动

    如果是很显而易见的 Patch,这部分可以省略,但是最好还是有一个简短清晰的说明;

  • 改动将会带来什么影响

PingCAP 社区风格遵循以下格式

1
2
3
4
5
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>(optional)

其中:

  • 对于第一行:
    • 不要超过 70 个字符;
    • 如果改动影响两个子系统,使用逗号隔开,比如 util/codec, util/types
    • 如果改动影响超过三个或更多子系统,使用 *,比如 *:
    • <what changed> 使用小写且不用句号,比如:media: **update** the DM architecture image
  • 第二行保持空行
  • why 这一部分如果对改动没有特别的理由,可以使用一些通用理由,比如 Improve performanceImprove test coverage 等;
  • 其他行不要超过 80 个字符;

好的习惯

  • 概括地介绍 Commit 作出的改变;
  • 清晰描述,不要使用含糊的描述;
  • 描述现有代码的局限性;
  • 不要加句号 .
  • 不要假设代码是显而易见的;
  • 不要假设 reviewer 了解原始问题;
  • 首行使用简单的动词(比如 Add);
  • 使用准确和标准的语法;
  • 使用相对较短的句子;
  • 不要使用较长的复合词;
  • 不要使用不常见的缩写;

Git 技巧

  • 尽量不要用 git commit -m,而是输入 git commit 后使用设置的编辑器进行 Commit Message 的编写;

  • git log 查看提交历史的技巧

    • git log 默认不加任何参数的话,会按照提交时间列出所有的更新。这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间等;

    • git log -p 用来显示每次提交的内容差异,加上 -2 仅显示最近两次提交;

    • git log --stat 在每次提交的下面列出一些统计信息:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      commit a11bef06a3f659402fe7563abf99ad00de2209e6
      Author: Scott Chacon <schacon@gee-mail.com>
      Date:   Sat Mar 15 10:31:28 2008 -0700
      
          first commit
      
       README           |  6 ++++++
       Rakefile         | 23 +++++++++++++++++++++++
       lib/simplegit.rb | 25 +++++++++++++++++++++++++
       3 files changed, 54 insertions(+)
      
    • git log --pretty=oneline 可以使用不同与默认格式的方式展示提交历史,比如 oneline 是将每个提交放在一行显示,另外还有 shortfullfuller

      可以使用 git log --pretty=<format> 来定制要显示的记录格式:

      1
      
      $ git log --pretty=format:"%h - %an, %ar : %s"
      

      具体的现实可参考文档

    • --graph 添加一些 ASCII 字符串来形象地展示分支、合并历史:

      1
      
      $ git log --pretty=format:"%h %s" --graph
      

      --pretty 结合使用比较有效;

    • git shortlog

      返回每个用户进行 commit 的次数,以及每个 commit message。

      -s :忽略 commit message,只输出一个简单的统计;

      -n:按照 commit 数量从多到少排序;

总结

随手看了好几个知名项目的 commit message 的规范,基本都是 AngularJS 模式的衍生(header+body+footer),只是细节约束上每个项目都有不同的规则,此处就不一一列举。

参考资料