前端工程化之 git commit 工具链

1,445 阅读6分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

前言

最近在做 monorepo 项目的前端工程化,其中有一个部分与 git commit 有关,因此参考了几个项目,总结出了相关的工具链,并写成文章。

整个工具链,都是围绕着 git commit message 的生成、校验,到最后的生成 changelog 的过程

下面是我调研的几个库在该过程所使用到的一些工具:

tdesign-vue-next-starterant-design-vuevuevite
commit 规范angular 规范angular 规范基于 angular 规范的扩展规范基于 angular 规范的扩展规范
生成 commit messagecommitizen×××
校验 commit messagecommitlintcommitlint使用自定义脚本校验使用自定义脚本校验
生成 changelog×自定义脚本conventional-changelogconventional-changelog

除了使用项目中自定义的一些工具外,主要使用到的开源工具为以下几个:

  • commitizen:生成 commit message
  • commitlint:校验 commit message
  • conventional-changelog:生成 changelog

在介绍这几个工具前,先介绍一下目前用得比较多的 commit 规范

angular commit 规范

网上就有很多文章,大家应该也是非常熟悉的,不了解的可以看看阮一峰的文章。该小节的内容都来源于该文章,这里我把一些重点的内容列一下

Commit message 包括三个部分:Header,Body 和 Footer。

<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>

其中,Header 是必需的,Body 和 Footer 可以省略。

Header 中 type 的类别,有以下 7 种:

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

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

Footer 部分只用于两种情况:

  • 不兼容变动,以 BREAKING CHANGE 开头,后面是对变动的描述、以及变动理由和迁移方法
  • 关闭 Issue

另外,如果是回滚撤销 git commit,则 commit message 必须要以 revert: 开头,如:

revert: feat(pencil): add 'graphiteWidth' option

This reverts commit 667ecc1654a317a13331b17617d973392f415f02.

commit 工具链

  • commitizen:生成 commit message
  • commitlint:校验 commit message
  • conventional-changelog:生成 changelog

这些工具,作用不同,那么要如何把这些工具的流程串起来?我们先来看看 git commit 的周期钩子

image-20220216225204985

下面是简单的 git 钩子介绍,详情请查看官方文档

  • pre-commit 钩子:在键入提交信息前运行,可以用于 eslint 等 linter 的代码校验和修复
  • prepare-commit-msg 钩子:在启动提交 commit message 编辑器之前运行。可以在该阶段生成 commit message(commitizen 在该阶段运行),这样就不会打开编辑器输入 commit message 了
  • commit-msg 钩子:填写 commit message 之后运行,如果该钩子的脚本以非零值退出,则 Git 放弃提交。可用于校验 commit message 是否符合规范。(commitlint 在该阶段运行)
  • post-commit 钩子:Git commit 提交过程完成后执行

changelog 的生成并没有在 git 钩子中,因为不是每次 commit 都需要生成 changelog,只需要再发布新的版本前生成即可

commitizen

通过命令行交互的方式,生成 git commit message

在使用 commitizen 前,首先得告诉 commitizen 我们用的是哪个 commit 规范,因此我们可以做以下配置:

  1. 安装依赖,假设我们使用 cz-conventional-changelog 作为配置,更多配置可以查看这里
npm install commitizen cz-conventional-changelog --save
  1. 在 package.json 中,加入 commitizen 的配置
{
  "script":{
      "cz": "cz"
  }
  "config": {
    "commitizen": {
      "path": "cz-conventional-changelog"
    }
  }
}

这样 commitizen 就知道我们所使用的的规范了

  1. 生成 commit message 并提交 commit:
npx cz

## 运行结果如下:
? Select the type of change that you are committing: feat:     A new feature
? What is the scope of this change (e.g. component or file name): (press enter to skip)

? Write a short, imperative tense description of the change (max 94 chars): test
? 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

## commit message: feat: test

命令行交互的内容,是来源于配置 cz-conventional-changelog ,因为不同的 commit 规范,有不同的命令行交互

命令行交互效果图如下:

如何与 git 钩子配合使用?

配置 prepare-commit-msg 的 git 钩子

# .git/hook/prepare-commit-msg
exec < /dev/tty && node_modules/.bin/cz --hook || true

这样 git-commit 的时候,就会执行 cz,进行命令行交互式地生成 commit 信息

  • exec < /dev/tty,默认情况下,git钩子不是交互式的。这个命令允许用户在钩子期间使用他们的终端与commizen交互。

  • --hook 是告诉 commitizen,这是在 git 钩子中运行的(直接命令行执行 cz 会生成 commit message 并提交 commit,如果通过 git 钩子触发,则是只负责生成 commit message,传给 git commit 命令)

commitlint

类似 eslint,commitlint 则是对 commit message 进行规范校验。

同样的,commitlint 也需要一份配置,告诉它我们需要用的是哪个规范。配置 commitlint 的方式如下:

// 项目根目录创建配置文件
// commitlint.config.js
module.exports = { extends: ['@commitlint/config-conventional'] };

这里是用的是配置 @commitlint/config-conventional ,其他配置可以查看官方文档

commitlint 就必须得配合 git 钩子使用了,需要在 git commit 信息生成/填写之后,再执行校验。因此使用的 git 钩子是 commit-msg

# .git/hooks/pre-commit
npx commitlint -e $1
  • $1,传入命令行的第一个参数,在 commit-msg 的 git 钩子中,为 .git/COMMIT_EDITMSG,该文件存储的是当次 commit 的 message 文本
  • commitlint -e 文件路径:从文件中读取 git commit message 文本

git 钩子的同步

我们的 git commit 钩子是写在 .git/hooks 目录下的,当运行 git commit 的时候就会被运行

但是,git 的客户端钩子(commit 的几个钩子都是),不会被提交到 git 仓库中,这就意味着,别人 clone 了这个仓库,钩子是没有被复制过去的

为了解决这个痛点,我们需要使用一些辅助工具去同步我们的 git 钩子,常用的工具有 huskysimple-git-hooks

这里以 simple-git-hooks 为例,说明一下如何配置:

  1. 安装依赖
npm install simple-git-hooks --save-dev
  1. 配置 package.json
{
  "simple-git-hooks": {
    "pre-commit": "npx lint-staged",
    "prepare-commit-msg": "exec < /dev/tty && node_modules/.bin/cz --hook || true",
    "commit-msg": "npx commitlint -e $1",
  }
}
  1. 手动运行一下命令,安装钩子到 .git/hooks
npx simple-git-hooks

由于 package.json 会上传到 git 仓库,因此这些钩子配置也会被保存,clone 仓库之后,只需要执行安装钩子即可。

changelog 生成

生成 changelog 使用的是 conventional-changelog,只需要运行以下命令,就能生成 changlog 文件

conventional-changelog -p angular -i CHANGELOG.md -s
  • -p:angular 的 commit 规范
  • -i:选择输入的文件
  • -s:输入跟输出的文件一致

该命令会在 CHANGELOG.md 顶部,拼接上最新的一个版本的 changelog,不会修改之前的 changelog

changelog 单条内容就是单条的 git commit message,下面看看一个 changelog 的例子(节选自 @vitejs/plugin-vue),:

image-20220220214052451

生成 changelog 的基本流程

  1. 根据 git tag 将 所有 commit message 进行分割,git tag 需要以 v 开头,如 v1.9.3v1.9.2

  2. 筛选出需要生成 changelog 的 commit。

    • 并非所有类型的 message 都会生成 changelog,angular 规范中,只有 featfixpref,以及 revert 回滚的 commit,会生成到 changelog 中。其他的类型,需要自己补充进去。不建议将 docschorestyle, 、refactortest 加入到 changelog
  3. 根据筛选出来的 commit message,生成最新的 tag 版本的 changelog

    • 由于允许手动修改 changelog 文件,因此默认只会生成最近一个 tag 的 changelog,因此需要参数 -i,传入上一次的 changelog 文件,然后在它的顶部,拼接上最新生成的 changelog

monorepo 项目如何生成 changelog?

由于 monorepo 工程中,存在多个 package 包,它们的 changelog 是需要各自分开的

而按照上文 changelog 的生成方式,是会读取所有 commit 然后生成,并没有对 commit 进行过滤(去掉不属于该 package 的 commit)

那因此,需要用到另外一个参数 --commit-path

conventional-changelog -p angular -i CHANGELOG.md -s --commit-path <替换为 package 目录>

这告诉 conventional-changelog,在生成 changelog 时,需要对每个 git commit 的文件进行检查,如果 commit 中变更的文件,是 commit-path 目录中的文件,则该 commit 才被认为是有效 commit,才会用于生成 changelog

image-20220220220455004

上图展示了 commit 是如何通过 --commit-path 过滤 commit 的。由于最后一个 commit 中,同时修改了 A 和 B 的包,因此 A 和 B 在生成 changelog 时,都会有该 commit 的信息

总结

commit 的工具链已经介绍完毕,每个工具都有它各自的作用,我们可以看自己的需要,选择是否使用这些工具

就好像,tdesign-vue-next-starter 只是个业务模板,不是工具库,其实可以不需要 changelog(但要也行)

而 vite、vue 这些库,则没有使用 commitizen 生成 commit message,因为其实 commit message 直接手写可能更加方便(个人也认为手写更方便),因此也是不需要的。

而 commit 规范的校验,参考的这几个库,都有做到,不过方式不同,有的是 commitlint,有的是自己写的脚本,这是因为 vue 和 vite 扩展了 angular 规范,因此校验脚本也是自己写的,当然也可以用 commitlint 实现,可能是时间原因没做,也没有那么高的优先级。

实际上,我们也不必全部照搬这些,我们更多的是要理解其过程。而对于自己开发的项目,只需要结合自身需要即可。

如果这篇文章对您有所帮助,请帮忙点个赞👍,您的鼓励是我创作路上的最大的动力