前言
在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
这样的话Angular项目组可以很方便的生成Release Notes
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中间有空白行分割。
Header部分只有一行,包括三个字段:type
(必需)、scope
(可选)和subject
(必需)。
type
用于说明 commit 的类别,只允许使用下面7个标识:
-
feat
:新功能(feature) -
fix
:修补bug -
docs
:文档(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
自动化工具处理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环境下你需要使用
cmd
或powershell
运行交互式终端,在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
安装也会同时释放两个命令commitizen
和git-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.json
的devDependencies
配置多了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)
按照之前的Angular commit规范格式交互式输入信息即可,是不是比手写方便了许多?
以后,只要是需要git commit
的地方,通通替换为git cz
或git-cz
即可这样交互式的输入符合Angular commit规范的git log了。其余commit的参数也兼容,比如-a
, --amend
等等。
自动检测commit是否符合规范
尽管我们可以在CI测试阶段检测commit是否符合这个规范,但是能在本地就有反馈不是更快吗?因此我们可以考虑在git commit
的hook
中加入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 cz
或git-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.md
。conventional-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+p
或command+shift+p
使用conventional commit
提交代码。
参考资料:
- Git Commit Best Practices
- AngularJS的git-commit规范推广
- Commit message 和 Change log 编写指南
- The way to fully automated releases in open source projects
-
项目实战Workflow ↩