前面我们已经统一代码规范,并且在提交代码时进行强约束来保证仓库代码质量。多人协作的项目中,在提交代码这个环节,也存在一种情况:不能保证每个人对提交信息的准确描述,因此会出现提交信息紊乱、风格不一致的情况。
如果 git commit 的描述信息精准,在后期维护和 Bug 处理时会变得有据可查,项目开发周期内还可以根据规范的提交信息快速生成开发日志,从而方便我们追踪项目和把控进度。
这里,我们使用社区最流行、最知名、最受认可的 Angular 团队提交规范。
先看看 Angular 项目的提交记录:
如上图,可以看出这些提交信息都是有固定格式的,下面我们来学习 Angular 规范的 commit message 格式。
commit message 格式规范
commit message 由 Header、Body、Footer 组成。
<Header> <Body> <Footer>
Header
Header 部分包括三个字段 type(必需)、scope(可选)和 subject(必需)。
<type>(<scope>): <subject>
type
type 用于说明 commit 的提交类型(必须是以下几种之一)。
值 | 描述 |
---|---|
feat | 新增一个功能 |
fix | 修复一个 Bug |
docs | 文档变更 |
style | 代码格式(不影响功能,例如空格、分号等格式修正) |
refactor | 代码重构 |
perf | 改善性能 |
test | 测试 |
build | 变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等) |
ci | 更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等 |
chore | 变更构建流程或辅助工具 |
revert | 代码回退 |
scope
scope 用于指定本次 commit 影响的范围。scope 依据项目而定,例如在业务项目中可以依据菜单或者功能模块划分,如果是组件库开发,则可以依据组件划分。(scope 可省略)
subject
subject 是本次 commit 的简洁描述,长度约定在 50 个字符以内,通常遵循以下几个规范:
- 用动词开头,第一人称现在时表述,例如:change 代替 changed 或 changes
- 第一个字母小写
- 结尾不加句号(.)
Body
body 是对本次 commit 的详细描述,可以分成多行。(body 可省略)
跟 subject 类似,用动词开头,body 应该说明修改的原因和更改前后的行为对比。
Footer
如果本次提交的代码是突破性的变更或关闭缺陷,则 Footer 必需,否则可以省略。
- 突破性的变更当前代码与上一个版本有突破性改变,则 Footer 以 BREAKING CHANGE 开头,后面是对变动的描述、以及变动的理由。
- 关闭缺陷如果当前提交是针对特定的 issue,那么可以在 Footer 部分填写需要关闭的单个 issue 或一系列 issues。
参考例子
featfeat(browser): onUrlChange event (popstate/hashchange/polling) Added new event to browser: - forward popstate event if available - forward hashchange event if popstate not available - do polling when neither popstate nor hashchange available Breaks $browser.onHashChange, which was removed (use onUrlChange instead)
fixfix(compile): couple of unit tests for IE9 Older IEs serialize html uppercased, but IE9 does not... Would be better to expect case insensitive, unfortunately jasmine does not allow to user regexps for throw expectations. Closes #392 Breaks foo.bar api, foo.baz should be used instead
stylestyle(location): add couple of missing semi colons
chorechore(release): v3.4.2
featfeat(browser): onUrlChange event (popstate/hashchange/polling) Added new event to browser: - forward popstate event if available - forward hashchange event if popstate not available - do polling when neither popstate nor hashchange available Breaks $browser.onHashChange, which was removed (use onUrlChange instead)
fixfix(compile): couple of unit tests for IE9 Older IEs serialize html uppercased, but IE9 does not... Would be better to expect case insensitive, unfortunately jasmine does not allow to user regexps for throw expectations. Closes #392 Breaks foo.bar api, foo.baz should be used instead
stylestyle(location): add couple of missing semi colons
chorechore(release): v3.4.2
规范 commit message 的好处
- 首行就是简洁实用的关键信息,方便在 git history 中快速浏览。
- 具有更加详细的 body 和 footer,可以清晰的看出某次提交的目的和影响。
- 可以通过 type 过滤出想要查找的信息,也可以通过关键字快速查找相关提交。
- 可以直接从 commit 生成 change log。
集成 Commitizen 实现规范提交
上面介绍了 Angular 规范提交的格式,初次接触的同学咋一看可能会觉得复杂,其实不然,如果让大家在 git commit 的时候严格按照上面的格式来写,肯定是有压力的,首先得记住不同的类型到底是用来定义什么,subject 怎么写,body 怎么写,footer 要不要写等等问题,懒才是程序员第一生产力,为此我们使用 Commitizen 工具来帮助我们自动生成 commit message 格式,从而实现规范提交。
Commitizen 是一个帮助撰写规范 commit message 的工具。它有一个命令行工具 cz-cli。
安装 Commitizen
npm install commitizen -D
npm install commitizen -D
初始化项目
成功安装 Commitizen 后,我们用 cz-conventional-changelog 适配器来初始化项目:
npx commitizen init cz-conventional-changelog --save-dev --save-exact
npx commitizen init cz-conventional-changelog --save-dev --save-exact
这行命令做了两件事:
- 安装 cz-conventional-changelog 到开发依赖(devDependencies)
- 在 package.json 中增加了 config.commitizen
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
使用 Commitizen
以前我们提交代码都是 git commit -m "xxx",现在改为 git cz,然后按照终端操作提示,逐步填入信息,就能自动生成规范的 commit message。
最后,在 Git 提交历史中就能看到刚刚规范的提交记录了:
自定义配置提交说明
从上面的截图可以看到,git cz 终端操作提示都是英文的,如果想改成中文的或者自定义这些配置选项,我们使用 cz-customizable 适配器。
cz-customizable 初始化项目
运行如下命令使用 cz-customizable 初始化项目,注意之前已经初始化过一次,这次再初始化,需要加 --force 覆盖。
npx commitizen init cz-customizable --save-dev --save-exact --force
npx commitizen init cz-customizable --save-dev --save-exact --force
这行命令做了两件事:
- 安装 cz-customizable 到开发依赖(devDependencies)
"devDependencies": { ... "cz-customizable": "^6.3.0", ... },
"devDependencies": { ... "cz-customizable": "^6.3.0", ... },
- 修改 package.json 中的 config.commitizen 字段为:
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
}
}
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
}
}
使用 cz-customizable
在项目根目录下创建 .cz-config.js 文件,然后按照官方提供的示例来配置。
在本项目中我们修改成中文:
module.exports = {
// type 类型(定义之后,可通过上下键选择)
types: [
{value: 'feat', name: 'feat: 新增功能'},
{value: 'fix', name: 'fix: 修复 bug'},
{value: 'docs', name: 'docs: 文档变更'},
{
value: 'style',
name: 'style: 代码格式(不影响功能,例如空格、分号等格式修正)',
},
{
value: 'refactor',
name: 'refactor: 代码重构(不包括 bug 修复、功能新增)',
},
{value: 'perf', name: 'perf: 性能优化'},
{value: 'test', name: 'test: 添加、修改测试用例'},
{
value: 'build',
name: 'build: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)',
},
{value: 'ci', name: 'ci: 修改 CI 配置、脚本'},
{
value: 'chore',
name: 'chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)',
},
{value: 'revert', name: 'revert: 回滚 commit'},
],
// scope 类型(定义之后,可通过上下键选择)
scopes: [
['components', '组件相关'],
['hooks', 'hook 相关'],
['utils', 'utils 相关'],
['element-ui', '对 element-ui 的调整'],
['styles', '样式相关'],
['deps', '项目依赖'],
['auth', '对 auth 修改'],
['other', '其他修改'],
// 如果选择 custom,后面会让你再输入一个自定义的 scope。也可以不设置此项,把后面的 allowCustomScopes 设置为 true
['custom', '以上都不是?我要自定义'],
].map(([value, description]) => {
return {
value,
name: `${value.padEnd(30)} (${description})`,
}
}),
// 是否允许自定义填写 scope,在 scope 选择的时候,会有 empty 和 custom 可以选择。
// allowCustomScopes: true,
// allowTicketNumber: false,
// isTicketNumberRequired: false,
// ticketNumberPrefix: 'TICKET-',
// ticketNumberRegExp: '\\d{1,5}',
// 针对每一个 type 去定义对应的 scopes,例如 fix
/*
scopeOverrides: {
fix: [
{ name: 'merge' },
{ name: 'style' },
{ name: 'e2eTest' },
{ name: 'unitTest' }
]
},
*/
// 交互提示信息
messages: {
type: '确保本次提交遵循 Angular 规范!\n选择你要提交的类型:',
scope: '\n选择一个 scope(可选):',
// 选择 scope: custom 时会出下面的提示
customScope: '请输入自定义的 scope:',
subject: `请以云效任务id结尾填写简短精炼的提交描述,例如:"还原首页设计稿 [#task_id]":\n`,
body: '填写更加详细的变更描述(可选)。使用 "|" 换行:\n',
breaking: '列举非兼容性重大的变更(可选):\n',
footer: '列举出所有变更的 ISSUES CLOSED(可选)。 例如: #31, #34:\n',
confirmCommit: '确认提交?',
},
// 设置只有 type 选择了 feat 或 fix,才询问 breaking message
allowBreakingChanges: ['feat', 'fix'],
// 跳过要询问的步骤
skipQuestions: ['body', 'footer'],
// subject 限制长度
subjectLimit: 100,
breaklineChar: '|', // 支持 body 和 footer
// footerPrefix : 'ISSUES CLOSED:'
// askForBreakingChangeFirst : true,
}
module.exports = {
// type 类型(定义之后,可通过上下键选择)
types: [
{value: 'feat', name: 'feat: 新增功能'},
{value: 'fix', name: 'fix: 修复 bug'},
{value: 'docs', name: 'docs: 文档变更'},
{
value: 'style',
name: 'style: 代码格式(不影响功能,例如空格、分号等格式修正)',
},
{
value: 'refactor',
name: 'refactor: 代码重构(不包括 bug 修复、功能新增)',
},
{value: 'perf', name: 'perf: 性能优化'},
{value: 'test', name: 'test: 添加、修改测试用例'},
{
value: 'build',
name: 'build: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)',
},
{value: 'ci', name: 'ci: 修改 CI 配置、脚本'},
{
value: 'chore',
name: 'chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)',
},
{value: 'revert', name: 'revert: 回滚 commit'},
],
// scope 类型(定义之后,可通过上下键选择)
scopes: [
['components', '组件相关'],
['hooks', 'hook 相关'],
['utils', 'utils 相关'],
['element-ui', '对 element-ui 的调整'],
['styles', '样式相关'],
['deps', '项目依赖'],
['auth', '对 auth 修改'],
['other', '其他修改'],
// 如果选择 custom,后面会让你再输入一个自定义的 scope。也可以不设置此项,把后面的 allowCustomScopes 设置为 true
['custom', '以上都不是?我要自定义'],
].map(([value, description]) => {
return {
value,
name: `${value.padEnd(30)} (${description})`,
}
}),
// 是否允许自定义填写 scope,在 scope 选择的时候,会有 empty 和 custom 可以选择。
// allowCustomScopes: true,
// allowTicketNumber: false,
// isTicketNumberRequired: false,
// ticketNumberPrefix: 'TICKET-',
// ticketNumberRegExp: '\\d{1,5}',
// 针对每一个 type 去定义对应的 scopes,例如 fix
/*
scopeOverrides: {
fix: [
{ name: 'merge' },
{ name: 'style' },
{ name: 'e2eTest' },
{ name: 'unitTest' }
]
},
*/
// 交互提示信息
messages: {
type: '确保本次提交遵循 Angular 规范!\n选择你要提交的类型:',
scope: '\n选择一个 scope(可选):',
// 选择 scope: custom 时会出下面的提示
customScope: '请输入自定义的 scope:',
subject: `请以云效任务id结尾填写简短精炼的提交描述,例如:"还原首页设计稿 [#task_id]":\n`,
body: '填写更加详细的变更描述(可选)。使用 "|" 换行:\n',
breaking: '列举非兼容性重大的变更(可选):\n',
footer: '列举出所有变更的 ISSUES CLOSED(可选)。 例如: #31, #34:\n',
confirmCommit: '确认提交?',
},
// 设置只有 type 选择了 feat 或 fix,才询问 breaking message
allowBreakingChanges: ['feat', 'fix'],
// 跳过要询问的步骤
skipQuestions: ['body', 'footer'],
// subject 限制长度
subjectLimit: 100,
breaklineChar: '|', // 支持 body 和 footer
// footerPrefix : 'ISSUES CLOSED:'
// askForBreakingChangeFirst : true,
}
建议大家结合项目实际情况来自定义配置提交规则,例如很多时候我们不需要写长描述,公司内部的代码仓库也不需要管理 issue,那么可以把询问 body 和 footer 的步骤跳过(在 .cz-config.js 中修改成 skipQuestions: ['body', 'footer'])。
集成 commitlint 验证提交规范
在“代码规范”章节,我们已经讲到过,尽管制定了规范,但在多人协作的项目中,总有些人依旧我行我素,因此提交代码这个环节,我们也增加一个限制:只让符合 Angular 规范的 commit message 通过,我们借助 @commitlint/config-conventional 和 @commitlint/cli 来实现。
安装 commitlint
安装 @commitlint/config-conventional 和 @commitlint/cli
npm i @commitlint/config-conventional @commitlint/cli -D
npm i @commitlint/config-conventional @commitlint/cli -D
配置 commitlint
- 创建 commitlint.config.js 文件 在项目根目录下创建 commitlint.config.js 文件,并填入以下内容:
module.exports = {extends: ['@commitlint/config-conventional']}
module.exports = {extends: ['@commitlint/config-conventional']}
- 或直接使用快捷命令:
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
- 使用 husky 的 commit-msg hook 触发验证提交信息的命令
我们使用 husky 命令在 .husky 目录下创建 commit-msg 文件,并在此执行 commit message 的验证命令。
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
commitlint 验证
- 不符合规范的提交信息
如下图,提交信息 test commitlint 不符合规范,提交失败。 - 符合规范的提交信息
如下图,提交信息 test: commitlint test 符合规范,成功提交到仓库。
因为已在项目中集成 commitizen,建议大家用 git cz 来代替 git commit 提交代码,可以保证提交信息规范。