规范化git commit信息

原文链接

前言

在git的使用中,一种最佳实践是使用格式化的commit信息,这样方便自动化工具进行处理,可以快速生成Release Notes,甚至可以直接对接CI工具进行更进一步的规范化发布流程。那么如何规范化git commit信息呢?本文将重点讨论这个。

一个最基本的git commit最佳实践

Git-Commit-Best-Practices这个项目总结了一个最基本的git commit实践:

  • Commit Related Changes
  • Commit Often
  • Don't Commit Half-Done Work
  • Test Your Code Before You Commit
  • Write Good Commit Messages
  • Use Branches
  • Agree on A Workflow

而针对其中的Formatting Rules部分,我们将详细讲解下Angular的git commit规范格式。

Angular项目的git commit规范

首先我们先了解一下Angular项目如何规范化自己的commit信息的吧。

Angular项目可以说是业界最广为流传的git commit最佳实践的项目。Angular的贡献要求必须git commit符合自己定义的模板。先来看看Angular的commit记录长什么样子吧: https://github.com/angular/angular/commits/master

image.png

这样的话Angular项目组可以很方便的生成Release Notes

image.png

Angular的git commit实践

完整的Angular的commit教程参见的CONTRIBUTING: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines

Angular定义的commit基本格式如下:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

除了第一行的Header部分必填外,其余均可选。注意Header, Body, Footer中间有空白行分割。

image.png

Header部分只有一行,包括三个字段:type(必需)、scope(可选)和subject(必需)。

type用于说明 commit 的类别,只允许使用下面7个标识:

  • feat:新功能(feature)
  • fix:修补bug
  • docs:文档(documentation)
  • style: 格式(不影响代码运行的变动)
  • refactor:重构(即不是新增功能,也不是修改bug的代码变动)
  • test:增加测试
  • chore:构建过程或辅助工具的变动

通常featfix会被放入changelog中,其他(docschorestylerefactortest)通常不会放入changelog中。

scope用于说明 commit 影响的范围,可选值。通常是文件、路径、功能等。对于Angular项目已经定死了scope: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#scope

subject是 commit 目的的简短描述,不超过50个字符。如用英语表述:

  • 以动词开头,使用第一人称现在时,比如change,而不是changedchanges
  • 第一个字母小写
  • 结尾不加句号(.

Body部分是对本次 commit 的详细描述,可以分成多行。可选项。范例:

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 部分只用于两种情况:

  • Break Changes
  • Closes

Break Changes范例:

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 should be no code using it.

Closes范例:

Closes #123, #245, #992

自动化工具处理git commit

手写上述的commit格式很明显并不怎么方便,而且还有出错的可能性(尽管git支持template功能,但是实际使用的时候仍然不是特别方便),为什么不交给自动化工具完成呢?

下面就是重点要介绍的规范化git commit消息的工具commitizen

commitizen本质上是一个通用的规范化git commit的框架,通过各种Adapter实现格式化成对应格式。比如希望格式化成Angular的commit格式,那么需要额外安装cz-conventional-changelog这个adapter。目前commitizen实现的Adapter有Angular, ESlint, mapbox, jira等等,本篇我们主要使用cz-conventional-changelog实现Angular的commit格式。

由于commitizen及其生态都是使用NodeJS开发的工具,天然对NodeJS项目友好,并且附加全局命令行工具可供其他项目使用。因此本篇文档我们将分开探讨,NodeJS项目和非NodeJS项目如何使用commitizen。

使用commitizen系列工具前,请先确保系统当前安装有NodeJS的LTS版本(当前最新LTS为v10.15.3)。

NOTE: Windows环境下你需要使用cmdpowershell运行交互式终端,在cygwin/mingw32/msys2等模拟posix运行环境下无法正常执行交互式终端菜单。

NOTE: 你可能会注意到commitizen使用的快速使用的范例为npx git-cz。特别注意git-cz和commitizen是两个独立包,并且是不同开发者维护。可以将git-cz这个包看做是简化版的commitizen+cz-conventional-changelog。这个包产生的git-cz/git cz命令开箱即用,依赖轻,安装速度快,默认即使用Angular格式,默认自动忽略scope,无需配置Adapter。而使用commitizen+cz-conventional-changelog需要配置Adapter,相对依赖较重,但是对NodeJS项目比较友好,自动化支持度高。由于commitizen安装也会同时释放两个命令commitizengit-cz,注意不要和git-cz带的这个命令混淆了。

NodeJS项目

对于NodeJS项目,commitizen可以将自己的一些脚本添加到package.json中,方便npm script生命周期管理。

快速将一个项目初始化为commitizen友好的项目(需要确保当前工程中必须存在package.json文件):

 # 将commitizen命令行安装到全局(也可以用npx替代,或安装到项目的Dev依赖)
npm install commitizen -g

# 使用commitizen命令初始化当前工程为commitizen友好的工程
commitizen init cz-conventional-changelog --save-dev

执行完毕后可以发现在package.jsondevDependencies配置多了cz-conventional-changelog依赖,同时,也增加了以下配置段:

  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }

此时git cz将出现类似于下面的交互式终端:

cz-cli@3.1.1, cz-conventional-changelog@2.1.0


Line 1 will be cropped at 100 characters. All other lines will be wrapped after 100 characters.

? Select the type of change that you're committing: (Use arrow keys)
❯ feat:     A new feature
  fix:      A bug fix
  docs:     Documentation only changes
  style:    Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  refactor: A code change that neither fixes a bug nor adds a feature
  perf:     A code change that improves performance
  test:     Adding missing tests or correcting existing tests
(Move up and down to reveal more choices)
image

按照之前的Angular commit规范格式交互式输入信息即可,是不是比手写方便了许多?

以后,只要是需要git commit的地方,通通替换为git czgit-cz即可这样交互式的输入符合Angular commit规范的git log了。其余commit的参数也兼容,比如-a, --amend等等。

自动检测commit是否符合规范

尽管我们可以在CI测试阶段检测commit是否符合这个规范,但是能在本地就有反馈不是更快吗?因此我们可以考虑在git commithook中加入commitlint检测,不符合commit规范的提交在本地就无法提交进去。

对于NodeJS项目来说,有个非常简单的使用git hook的项目husky,使用者无需手工定义繁琐的git hook脚本,直接在package.json中定义要执行的hook命令即可。

在项目依赖中添加husky, commitlint相关依赖:

npm i -D husky @commitlint/config-conventional @commitlint/cli

然后在package.json配置中添加husky的git hook配置:

{
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -x @commitlint/config-conventional -E HUSKY_GIT_PARAMS"
    }
  }
}

这样如果使用普通的git commit提交了不符合commit规范的消息,就会被直接打回:

 git commit -a -m '添加husky和commitlint依赖'
husky > commit-msg (node v10.15.3)

⧗   input: 添加husky和commitlint依赖
✖   subject may not be empty [subject-empty]
✖   type may not be empty [type-empty]
✖   found 2 problems, 0 warnings
    (Need help? -> https://github.com/conventional-changelog/commitlint#what-is-commitlint )


husky > commit-msg hook failed (add --no-verify to bypass)

我看可以看到git commit触发了husky的hook,告诉用户这条commit记录不符合Angular规范。

再次使用git cz提交一次:

 git cz -a
cz-cli@3.1.1, cz-conventional-changelog@2.1.0


Line 1 will be cropped at 100 characters. All other lines will be wrapped after 100 characters.

? Select the type of change that you're committing: build:    Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
? What is the scope of this change (e.g. component or file name)? (press enter to skip)
 package.json
? Write a short, imperative tense description of the change:
 添加husky和commitlint提交前检测
? Provide a longer description of the change: (press enter to skip)

? Are there any breaking changes? No
? Does this change affect any open issues? No

husky > commit-msg (node v10.15.3)

⧗   input: build(package.json): 添加husky和commitlint提交前检测
✔   found 0 problems, 0 warnings
    (Need help? -> https://github.com/conventional-changelog/commitlint#what-is-commitlint )


[master bd79828] build(package.json): 添加husky和commitlint提交前检测
 2 files changed, 1357 insertions(+), 7 deletions(-)

符合commit规范的提交可以成功提交到版本库中。

NOTE: 巧妙合理利用husky的hook功能可以大大规范化开发。比如在pre-commit
这个hook中加入各种lint(eslink, tslink, jslint等)检测,大大提高代码规范化效率,减少BUG产生。

非NodeJS项目

对于非NodeJS项目,可以将commitizen, commitlint等这些工具安装为全局的命令行工具分开使用。

安装commitizen为全局:

npm install -g commitizen cz-conventional-changelog

# 注: powershell使用下面命令产生的文件是UTF16格式,需要手工转为UTF8保存
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc

NOTE: 前面提到过,如果使用git-cz,则无需安装commitizen和相关Adapter,也无需配置~/.czrc,但是交互式commit的时候不会提示scope,和直接使用npx git-cz运行效果等效。

然后像git commit那样直接使用git czgit-cz就可以弹出交互式提示了:

 git add new.md
 git cz
cz-cli@3.1.1, cz-conventional-changelog@2.1.0


Line 1 will be cropped at 100 characters. All other lines will be wrapped after 100 characters.

? Select the type of change that you're committing: (Use arrow keys)
❯ feat:     A new feature
  fix:      A bug fix
  docs:     Documentation only changes
  style:    Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  refactor: A code change that neither fixes a bug nor adds a feature
  perf:     A code change that improves performance
  test:     Adding missing tests or correcting existing tests
(Move up and down to reveal more choices)

检测commit是否符合规范

同样可以将commitlint安装到全局,以命令行方式在本地手工检测运行。

npm install -g @commitlint/cli @commitlint/config-conventional

每次commit之后可以使用下述命令检测:

commitlint -x '@commitlint/config-conventional' -e

-e/--edit参数代表读取git commit最后一条记录。如果希望检测最近几条commit记录,可以用:

commitlint -x '@commitlint/config-conventional' -f HEAD~1

-f/--from参数可以指明从哪一条commit记录开始检测,还可以配合-t/--to参数检测一个commit区间段。

NOTE: 如果不介意非NodeJS项目下多一堆NodeJS项目相关的配置文件,也可以在npm init初始化成nodejs项目之后走上述NodeJS项目的配置流程。

版本发布

重头戏来了。规范化commit记录的作用就是为了方便我们知道每次发布之后到底改了什么内容。利用conventional-changelog这个工具可以很方便的帮我们产生changelog。

npm install -g conventional-changelog-cli

如果之前每次commit都使用规范化的commit提交,那么使用:

conventional-changelog -p angular

应该看到这样的markdown:

#  (2019-04-25)


### Bug Fixes

* **third.md:** 添加新的third.md文件,添加一个新功能 719c542


### Features

* **new.md:** 添加新的功能项 43d6584
* **README.md:** 初始化项目工程 69c6c9f


### BREAKING CHANGES

* **new.md:** 这个功能打破了一个函数。before: old, new: new

这就是一个基本的CHANGELOG.md雏形,你可以自己复制到CHANGELOG.md并进行相应的修改。也可以直接输出到CHANGELOG.md文件中:

conventional-changelog -i CHANGELOG.md -s -p angular

终端中看到的内容将输出到CHANGELOG.md文件。再次使用上述命令可以将新的change log追加到文件中。可以追加-r 0参数代表将commit记录从头至尾全部生成changelog。

更自动化的发布方式standard-version

conventional-changelog的官方文档中,官方更鼓励使用更上层的工具standard-version来产生CHANGELOG.mdconventional-changelog可以帮助我们快速检查要生成的CHANGELOG.md的格式是否符合期望,而standard-version可以自动帮助我们做以下几件事情:

  • 升级元数据中的版本号(如package.json,composer.json等等)
  • 使用conventional-changelog更新 CHANGELOG.md
  • 提交package.json (如果有) 和 CHANGELOG.md
  • 给新版本打一个tag

首先安装standard-version到全局命令行:

npm i -g standard-version

执行下standard-version,将看到类似于下面这样的输出:

 standard-version
✔ created CHANGELOG.md
✔ outputting changes to CHANGELOG.md
✔ committing CHANGELOG.md
✔ tagging release v2.0.0
ℹ Run `git push --follow-tags origin master && npm publish` to publish

可以非常清楚的从终端上看到standard-version做了哪些事情。检查git log可以看到新增了一条commit记录:

commit cac4b5cda4f0c2a78928d8306c5c2eab8c590f02 (HEAD -> master, tag: v2.0.0)
Author: Your Name <you@example.com>
Date:   Thu Apr 25 17:15:56 2019 +0800

    chore(release): 2.0.0

项目中也生成了一个CHANGELOG.md文件:

# Change Log

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

#  (2019-04-25)


### Bug Fixes

* **third.md:** 添加新的third.md文件,添加一个新功能 719c542


### Features

* **new.md:** 添加新的功能项 43d6584
* **other.md:** 新增一个other.md文件,记录了新的内容 1a204d9
* **README.md:** 初始化项目工程 69c6c9f


### BREAKING CHANGES

* **new.md:** 这个功能打破了一个函数。before: old, new: new

standard-version一些基本用法

直接跟空参运行的行为我们已经看到了,一些基本参数介绍下:

  • --dry-run: 强烈建议正式运行之前先加这个参数,打印一下要做的事情,并不真正执行
  • --first-release/-f: 首次发布,加上这个参数代表不会升级版本号。仅第一次使用需要
  • --commit-all/-a: 等效于git commit -a,建议自己决定要提交哪些文件,都add没问题之后再执行standard-version
  • --release-as/-r: 手工指定下一个版本号(格式<major|minor|patch>)。这个参数可能会经常使用。standard-version默认规则是从major/minor/patch中最后一个非零的字段开始累加,如v1.0.0 -> v2.0.0, v1.1.0 -> v1.2.0, v1.1.1 -> v1.1.2,指定这个参数可以规避这个规则,从自定义的版本号开始重新按上述规则升级
  • --prerelease/-p: 预发布版本,默认产生的tag像这样: v1.0.1-0。可以跟上一个可选的tag id。如-p alpha,将产生这样的tag: v1.0.1-alpha.0

项目实战Workflow[1]

对于实际的项目,可以采用这一套流水线规范化代码提交与发布流程。需要初始化项目。首先安装全局依赖,仅首次需要:

# 等standard-version的BUG修复了考虑将conventional-changelog替换为standard-version
npm install -g commitizen

NodeJS项目

对于NodeJS项目需要初始化为commitizen友好的项目,以及添加husky的hook检查:

commitizen init cz-conventional-changelog --save-dev
npm i -D husky @commitlint/config-conventional @commitlint/cli

然后在package.json配置中添加husky的git hook配置:

{
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -x @commitlint/config-conventional -E HUSKY_GIT_PARAMS"
    }
  }
}

如有需要也可以在pre-commit这个hook中添加各种lint检测。

今后使用git-cz/git cz代替git commit提交代码。

非NodeJS项目

cz-conventional-changelog,commitlint添加到全局:

npm install -g cz-conventional-changelog @commitlint/config-conventional @commitlint/cli

默认commitizen的Adapter为cz-conventional-changelog:

# 注: powershell使用下面命令产生的文件是UTF16格式,需要手工转为UTF8保存
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc

使用git-cz/git cz代替git commit提交代码

使用commitlint -x @commitlint/config-conventional -e检测最新一条commit记录是否符合规范。

NOTE: 不在意非NodeJS项目下多一堆nodejs项目相关的文件,也可以在npm init初始化成nodejs项目工程之后走上述NodeJS项目方案

版本发布

安装standard-version到全局

npm install -g standard-version

使用以下命令生成CHANGELOG.md,并自动打tag:

standard-version --dry-run  # 加上--dry-run检查是否符合预期
standard-version

默认规则自动升级小版本号,如需手工指定版本号,可以追加-r x.y.z这样的参数,第一次发布也可以追加-f参数,表示不升级当前版本。更详细的standard-version用法可以参考之前更自动化的发布方式standard-version小节的内容。

CI检测

在gitlab ci中运行以下命令检测当前提交是否符合conventional-changelog规范:

npx -p "@commitlint/cli" -p "@commitlint/config-conventional" -p "commitlint-format-junit" commitlint -x @commitlint/config-conventional -o commitlint-format-junit -f ${CI_COMMIT_BEFORE_SHA} > commitlint_result.xml

将lint result输出为JUnit格式,方便Gitlab在merge request的时候展示lint失败的结果。

vscode用户

可以安装vscode-commitizen插件,使用ctrl+shift+pcommand+shift+p使用conventional commit提交代码。

image.png

image.png

参考资料:


  1. 项目实战Workflow