目录

Git | Git Commit 规范

1. Commit Message 的重要性

  • 可以使自己或者其他开发人员能够清晰地知道每个 commit 的变更内容,方便快速浏览变更历史,比如可以直接略过文档类型或者格式化类型的代码变更。
  • 可以基于这些 Commit Message 进行过滤查找,比如只查找某个版本新增的功能:git log –oneline –grep “^feat|^fix|^perf”。
  • 可以基于规范化的 Commit Message 生成 Change Log。
  • 可以依据某些类型的 Commit Message 触发构建或者发布流程,比如当 type 类型为 feat、fix 时我们才触发 CI 流程。
  • 确定语义化版本的版本号。比如 fix 类型可以映射为 PATCH 版本,feat 类型可以映射为 MINOR 版本。带有 BREAKING CHANGE 的 commit,可以映射为 MAJOR 版本。

总结来说,一个好的 Commit Message 规范可以使 Commit Message 的可读性更好,并且可以实现自动化。

2. 规范

2.1. 应该有的样子

社区已有多种 Commit Message 的规范了,例如 JQuey、Angular 等,这些规范的关系如下所示

https://img.dawnguo.cn/Git/20210703121142.png

而不管什么样的规范,个人认为一个好的 Commit Message 从结构上应该由以下三个部分组成:

  • 主题

    主题应该只有一行,是对你 commit 的总结。这个句子应该用祈使句的语气,以大写字母开头,不以句号为结束符,并且字数应该少于等于 50 。

    这个主题句子可以很好地填充 “This commit will ……” 这个句子,比如 “add new neural network model to back-end”。

  • 正文

    正文应该包含了 commit 的要点,通过正文可以了解更改的详细信息。在正文中你应该详细地阐述 commit 所做的改变,以及阐述你这么做的上下文,比如解释为什么做出这样的改变,为什么选择使用这种方式去实现你的改变,以及其他可以理解这个 commit 的背后思考过程。同时没有必要重复阐述那些通过 diff 命令就可以看出来的改变,也没有必要一行一行的进行解释。一定要专注于更高级的细节,这些细节是通过读代码不好发现的。总的来说,正文内容的目标是提供这么改变的上下文,这个上下文主要包括修改的东西和目标。

    另外对于一些很小的 commits 来说,比如 “fixing a typo” 这种的,那么就可以不需要正文了,因为主题句中已经提供了足够多的信息了。

  • 结束语

    结束语应该是 commit message 的最后一行。这一行中你可以放一些关于 commit 的有用的 meta-data。比如 Github issue numbers,co-author names 和额外的链接。这些可以帮助你找到跟这个 commit 有关的重要信息。

2.2. Angular 规范

Angular 规范在功能上能够满足开发者 commit 需求,在格式上清晰易读,目前也是用得最多的。Angular 规范是一种语义化的提交规范(Semantic Commit Messages),语义化的提交规范包含两方面:

  • 语义化:Commit Message 都会被归为一个有意义的类型,用来说明本次的 commit 类型。
  • 规范化:Commit Message 遵循预先定义好的规范,比如 Commit Message 格式固定、都属于某个类型,这些规范不仅可被开发者识别也可以被工具识别。

下面是 Angular 规范的一个例子:

https://img.dawnguo.cn/Git/20210703122003.png

Angular 规范的 Commit Message 包含了三部分:Header、Body、Footer,格式如下所示。其中 Header 是必需的,Body 和 Footer 都是可以省略的。其中 Header、Body、Footer 之间必须要有空行。

1
2
3
4
5
<type>[optional scope]: <description>
// 空行
[optional body]
// 空行
[optional footer(s)]

2.2.1. Header

Header 部分只有一行,包含了三个字段:type(必选)、scope(可选,有的话需要用 () 括起来)和 description(subject,必选)。<type>[optional scope] 后必须紧跟冒号,冒号后面必须紧跟空格。

  • type 字段,这个字段用来说明 commit 的类型。

    Angular 规范中常见的 type 如下图所示,其中这些 type 大致可以归为两大类:

    • Production:这类修改会影响最终的用户和生产环境的代码。所以对于这种改动,我们一定要慎重,并在提交前做好充分的测试。比如,在做 code review 的时候,如果遇到 Production 类型的代码,一定要认真 Review,因为这种类型,会影响到现网用户的使用和现网应用的功能。
    • Development:这类修改一般是项目管理类的变更,不会影响最终用户和生产环境的代码,比如 CI 流程、构建方式等的修改。遇到这类修改,通常也意味着可以免测发布。

    如何确定一个 commit 所属的 type 呢?可以通过下面这张图来确定。

    • 如果我们变更了应用代码,比如某个 Go 函数代码,那这次修改属于代码类。在代码类中,有 4 种具有明确变更意图的类型:feat、fix、perf 和 style;如果我们的代码变更不属于这 4 类,那就全都归为 refactor 类,也就是优化代码。

      有冲突后的 commit 可以使用 refactor。

    • 如果我们变更了非应用代码,例如更改了文档,那它属于非代码类。在非代码类中,有 3 种具有明确变更意图的类型:test、ci、docs;如果我们的非代码变更不属于这 3 类,那就全部归入到 chore 类。

    Angular 的 Commit Message 规范提供了大部分的 type,在实际开发中,我们可以使用部分 type,或者扩展添加我们自己的 type。但无论选择哪种方式,我们一定要保证一个项目中的 type 类型一致。

  • scope 字段,scope 是用来说明 commit 的影响范围的,它必须是名词。

    显然,不同项目会有不同的 scope。在项目初期,我们可以设置一些粒度比较大的 scope,比如可以按组件名或者功能来设置 scope;后续,如果项目有变动或者有新功能,我们可以再用追加的方式添加新的 scope。

    这里想强调的是,scope 不适合设置太具体的值。太具体的话,一方面会导致项目有太多的 scope,难以维护。另一方面,开发者也难以确定 commit 属于哪个具体的 scope,导致错放 scope,反而会使 scope 失去了分类的意义。

    在指定 scope 时需要遵循我们预先规划的 scope。除此之外,我们要将 scope 文档化,放在类似 devel 这类文档中。比如下面这样

    https://img.dawnguo.cn/Git/image-20210924162040200.png

    参考:https://github.com/marmotedu/iam/blob/master/docs/devel/zh-CN/scope.md

  • subject 字段,即 description 部分。

    subject 是 commit 的简短描述,必须以动词开头、使用现在时(比如,我们可以用 change,却不能用 changed 或 changes)。这个动词的第一个字母必须是小写,通过这个动词,我们可以明确地知道 commit 所执行的操作。此外,subject 的结尾不能加英文句号

2.2.2. Body

Body 部分是对本次 commit 的更详细描述,是可选的

Body 部分可以分成多行,而且格式也比较自由。Body 中需要包含修改的动机,以及跟上一版本相比的改动点。

Footer 部分用来说明本次 commit 导致的后果,是可选的

在实际应用中,Footer 通常用来说明不兼容的改动和关闭的 Issue 列表,格式如下所示:

1
2
3
4
5
6
BREAKING CHANGE: <breaking change summary>
// 空行
<breaking change description + migration instructions>
// 空行
// 空行
Fixes #<issue number>
  • 不兼容的改动:如果当前代码跟上一个版本不兼容,需要在 Footer 部分,以 BREAKING CHANG: 开头。后面跟上不兼容改动的摘要。之后空一行,来说明变动的描述、变动的理由和迁移方法。示例如下所示:

     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 and
        the inject option for the directive controller injection was removed.
      
        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 should be no code using it.
    
  • 关闭的 Issue 列表:关闭的 Bug 需要在 Footer 部分新建一行,并以 Closes 开头列出,例如:Closes #123。如果关闭了多个 Issue,可以这样列出:Closes #123, #432, #886。

    1
    2
    3
    
    Change pause version value to a constant for image
          
    Closes #1137
    

2.2.4. Revert Commit

除了 Header、Body 和 Footer 这 3 个部分,Commit Message 还有一种特殊情况:如果当前 commit 还原了先前的 commit,则应以 revert: 开头,后跟还原的 commit 的 Header。在 Body 中必须写成 This reverts commit ,其中 hash 是要还原的 commit 的 SHA 标识。例如,

1
2
3
revert: feat(iam-apiserver): add 'Host' option

This reverts commit 079360c7cfc830ea8a6e13f4c8b8114febc9b48a.

2.2.5. 其他

  • 为了使 Commit Message 在 GitHub 或者其他 Git 工具上更加易读,往往会限制每行 message 的长度。根据需要,可以限制为 50/72/100 个字符,这里我将长度限制在 72 个字符以内(也有一些开发者会将长度限制为 100,你可根据需要自行选择)。
  • 为了更好地遵循 Angular 规范,建议你在提交代码时养成不用 git commit -m,即不用 -m 选项的习惯,而是直接用 git commit 或者 git commit -a 进入交互界面编辑 Commit Message。这样可以更好地格式化 Commit Message。

3. Commit Message 规范自动化

有一定的规范之后,我们可以使用一些工具,来自动化地做以下这些事情:

  • Commit Message 生成和检查功能:生成符合 Angular 规范的 Commit Message、Commit Message 提交前检查、历史 Commit Message 检查。
  • 基于 Commit Message 自动生成 CHANGELOG 和 SemVer 的工具。

可以通过以下几个工具自动完成上面的功能:

  • commitizen-go(https://github.com/lintingzhen/commitizen-go):使你进入交互模式,并根据提示生成 Commit Message,然后提交。
  • commit-msg:githooks,在 commit-msg 中,指定检查的规则,commit-msg 是个脚本,可以根据需要自己写脚本实现。这门课的 commit-msg 调用了 go-gitlint 来进行检查。
  • go-gitlint(https://github.com/llorllale/go-gitlint):检查历史提交的 Commit Message 是否符合 Angular 规范,可以将该工具添加在 CI 流程中,确保 Commit Message 都是符合规范的。
  • gsemver(https://github.com/arnaud-deprez/gsemver):语义化版本自动生成工具。
  • git-chglog(https://github.com/git-chglog/git-chglog):根据 Commit Message 生成 CHANGELOG。

4. 规范推荐网址

https://www.conventionalcommits.org/en/v1.0.0-beta.4/

5. Commit 相关的 3 个重要内容

除了 Commit Message 规范之外,在代码提交时,我们还需要关注 3 个重点内容:提交频率、合并提交和 Commit Message 修改

5.1. 提交频率

在实际项目开发中,如果是个人项目,随意 commit 可能影响不大,但如果是多人开发的项目,随意 commit 不仅会让 Commit Message 变得难以理解,还会让其他研发同事觉得你不专业。因此,我们要规定 commit 的提交频率。

进行 commit 的时候一般分为两种情况:

  • 只要我对项目进行了修改,一通过测试就立即 commit。比如修复完一个 bug、开发完一个小功能,或者开发完一个完整的功能,测试通过后就提交。
  • 我们规定一个时间,定期提交。这里我建议代码下班前固定提交一次,并且要确保本地未提交的代码,延期不超过 1 天。这样,如果本地代码丢失,可以尽可能减少丢失的代码量。

5.2. 合并提交

上述两种方式提交代码,可能会导致代码 commit 比较多,看起来比较随意。另外,有时候想等一个功能开发完成之后,将所有 commit 放在一个 commit 中。这个时候可以在最后合并代码或者提交 pull request 前,可以使用 git rebase -i <commit ID> 将若干个 commit 合并为一个 commit。

如果有太多的 commit 需要合并的话,可以尝试撤销过去的 commit,然后重新提交一个新的 commit。但是需要说明的一点是,除了 commit 实在太多的时候,一般情况不采用这种方法,比较粗暴,而且之前提交的 commit message 都得重新提交一遍。

1
2
3
$ git reset HEAD~3
$ git add .
$ git commit -am "feat(user): add user resource"

合并提交的建议:在把新的 commit 合并到主干的时候,最好只保留 2-3 个 commit 记录。

5.3. 修改 Commit Message

有时候我们希望修改某次 commit message,那么有以下两种方式:

  • git commit –amend:修改最近一次 commit 的 message;
  • git rebase -i:修改某次 commit 的 message。

需要注意的是:

  • Commit Message 是 commit 数据结构中的一个属性,如果 Commit Message 有变更,则 commit ID 一定会变,git commit –amend 只会变更最近一次的 commit ID,但是 git rebase -i 会变更父 commit ID 之后所有提交的 commit ID。

  • 如果当前分支有未 commit 的代码,需要先执行 git stash 将工作状态进行暂存,当修改完成后再执行 git stash pop 恢复之前的工作状态。