在 git 的使用中,一种最佳实践是使用格式化的 commit 信息,这样方便自动化工具进行处理,可以快速生成 Release Notes,甚至可以直接对接 CI 工具进行更进一步的规范化发布流程。那么如何规范化 git commit 信息呢?本文将重点讨论这个。
2020-05-21 更新:为了更加轻量化,使用git-cz替换掉原来主要推荐的
commitlizen
部分,在最后添加了一个小结部分概览,方便快速在本地开发环境速查命令及使用方式。
Git-Commit-Best-Practices这个项目总结了一个最基本的 git commit 实践:
而针对其中的Formatting Rules部分,我们将详细讲解下 Angular 的 git commit 规范格式。
首先我们先了解一下Angular项目如何规范化自己的 commit 信息的吧。
Angular 项目可以说是业界最广为流传的 git commit 最佳实践的项目。Angular 的贡献要求必须 git commit 符合自己定义的模板。先来看看 Angular 的 commit 记录长什么样子吧: https://github.com/angular/angular/commits/master
这样的话 Angular 项目组可以很方便的生成Release Notes
完整的 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 中间有空白行分割。
Header 部分只有一行,包括三个字段:type
(必需)、scope
(可选)和subject
(必需)。
type
用于说明 commit 的类别,只允许使用下面 7 个标识:
feat
:新功能(feature)fix
:修补 bugdocs
:文档(documentation)style
: 格式(不影响代码运行的变动)refactor
:重构(即不是新增功能,也不是修改 bug 的代码变动)test
:增加测试chore
:构建过程或辅助工具的变动通常feat
和fix
会被放入 changelog 中,其他(docs
、chore
、style
、refactor
、test
)通常不会放入 changelog 中。
scope
用于说明 commit 影响的范围,可选值。通常是文件、路径、功能等。对于 Angular 项目已经定死了 scope: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#scope
subject
是 commit 目的的简短描述,不超过 50 个字符。如用英语表述:
change
,而不是changed
或changes
.
)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
手写上述的 commit 格式很明显并不怎么方便,而且还有出错的可能性(尽管 git 支持template功能,但是实际使用的时候仍然不是特别方便),为什么不交给自动化工具完成呢?
下面就是重点要介绍的规范化 git commit 消息的工具git-cz。
git-cz是一个简化版的commitizen
+cz-conventional-changelog
组合,提供了开箱即用的功能,默认使用Angular
规范,默认模板不填写scope
部分内容。
NOTE: Windows 环境下你需要使用
cmd
或powershell
运行交互式终端,在cygwin
/mingw32
/msys2
等模拟 posix 运行环境下无法正常执行交互式终端菜单。NOTE: git-cz非常轻量,只提供 Angular 规范的支持。如果你想使用其他规范的 commit,请使用commitizen配合对应的Adapter
对于 NodeJS 项目,commitizen 可以将自己的一些脚本添加到package.json
中,方便npm script
生命周期管理。
快速将一个项目初始化为 commitizen 友好的项目(需要确保当前工程中必须存在package.json
文件):
# 将git-cz命令行安装到全局
npm install -g git-cz
# 也可以使用npx命令直接运行
npx git-cz
此时git cz
或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)
按照之前的 Angular commit 规范格式交互式输入信息即可,是不是比手写方便了许多?
以后,只要是需要git commit
的地方,通通替换为git cz
或git-cz
即可这样交互式的输入符合 Angular commit 规范的 git log 了。其余 commit 的参数也兼容,比如-a
, --amend
等等。
尽管我们可以在 CI 测试阶段检测 commit 是否符合这个规范,但是能在本地就有反馈不是更快吗?因此我们可以考虑在git commit
的hook
中加入commitlint检测,不符合 commit 规范的提交在本地就无法提交进去。
对于 NodeJS 项目来说,有个非常简单的使用 git hook 的项目husky,使用者无需手工定义繁琐的 git hook 脚本,直接在package.json
中定义要执行的 hook 命令即可。
在项目依赖中添加husky
:
npm i -D husky
然后在package.json
配置中添加husky
的 git hook 配置:
{
"husky": {
"hooks": {
"commit-msg": "commitlint -x @commitlint/config-conventional -E HUSKY_GIT_PARAMS"
}
}
}
然后安装@commitlint/cli
和@commitlint/config-conventional
这两个包(建议安装到全局,这样所有项目都可以用):
npm install -g @commitlint/cli @commitlint/config-conventional
这样如果使用普通的 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 项目,可以将commitlint
安装为全局的命令行工具独立使用。
npm install -g @commitlint/config-conventional @commitlint/cli
每次 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。
在conventional-changelog的官方文档中,官方更鼓励使用更上层的工具standard-version来产生CHANGELOG.md
。conventional-changelog
可以帮助我们快速检查要生成的 CHANGELOG.md 的格式是否符合期望,而standard-version
可以自动帮助我们做以下几件事情:
package.json
,composer.json
等等)package.json
(如果有) 和 CHANGELOG.md
首先安装 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
直接跟空参运行的行为我们已经看到了,一些基本参数介绍下:
--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
直接结合semantic-release,参考我们的文章。
在 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-commitizen插件,使用ctrl+shift+p
或command+shift+p
使用conventional commit
提交代码。
这里快速总结一下本文的工具部分的安装和使用
# 安装git-cz包
npm install -g git-cz
# 以后所有使用git commit的地方都用git-cz或git cz命令提交代码
git cz [-a] [--amend] [...]
# 安装commitlint命令行和验证使用的规则config-conventional
npm install -g @commitlint/config-conventional @commitlint/cli
# 添加全局默认配置,以后commitlint命令都不用加 -x "@commitlint/config-conventional"
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > ~/commitlint.config.js
# 验证最新一条提交记录(必须添加上述配置,否则需要加上 -x "@commitlint/config-conventional")
commitlint -e
NodeJS 项目直接使用 husky:
npm install -D husky
然后在package.json
添加 husky 配置:
{
"husky": {
"hooks": {
"commit-msg": "commitlint -x @commitlint/config-conventional -E HUSKY_GIT_PARAMS"
}
}
}
其他项目可以手工添加 git hook。
觉得有帮助的话,不妨考虑购买付费文章来支持我们 🙂 :
付费文章