前言
本篇文章主要讲解如何从零搭建一套 Git 提交规范化流程,主要分为 lint 校验和 Commit Message 规范化
在团队合作中,Git 提交如果没有规范化,会让代码库变得混乱不堪,影响协作效率,特别是在多人协作中。如何从零开始搭建一套高效的 Git 提交规范化流程,是每个团队都需要思考的问题
本篇文章结构紧凑,因果关系明确,最好从头读到尾,跟着逻辑思路走,不建议中途跳段
本文也是《通俗易懂的中后台系统建设指南》系列的第五篇文章,该系列旨在告诉你如何来构建一个优秀的中后台管理系统
写在前面
上一篇文章中,我们在代码层面从零搭建起了一套代码规范体系
而在这篇文章中,我们会聚焦在工程层面,致力于从零搭建起一套 Git 提交规范化流程,通过工具如 Husky、Lint-staged、Commitlint 和 Commitizen,一步步实现 Git 提交规范化,首先,我们需要先了解一下这些工具的基础概念,请看下文
孟子曰:不以规矩,不能成方圆
基础概念
你可以在这里先了解下贯穿全文的工具概念,我们后面会使用到它们:
- husky:一个 Git hook 工具,使用 Husky 可以挂载 Git 钩子,使现代的原生 Git 钩子变得简单,即可以帮助我们在 commit 前做一些自定义操作
- Lint-staged: 对暂存的 git 文件运行 linters,也就是只对暂存的文件进行 ESLint、Prettier、Stylelint 等检查
- commitlint:对 commit 信息进行检查,即 lint Commit Message
- Commitizen:基于 Node.js 的
git commit
命令行工具,辅助生成标准化规范化的 Commit Message
git-hook
在开始前,我们先来了解一下什么是 git-hook,这是前置概念
hook 翻译过来是钩子的意思,有点类似于 Vue 的生命周期钩子一样,Git 能在特定的重要动作发生时触发自定义脚本
在我们执行 git init
命令后,会在当前目录下生成一个 .git
文件,目录中就存在一个 hooks
文件夹
Mac 用户如果看不到 .git
文件,使用 Command + Shift + . 来显示隐藏文件
└── .git
├── COMMIT_EDITMSG # 保存最新的commit message
├── config # 仓库的配置文件
├── description # 仓库的描述信息,主要给gitweb使用
├── HEAD # 指向当前分支
├── hooks # 存放一些shell脚本,可以设置特定的git命令后触发相应的脚本
├── index # 二进制暂存区(stage)
├── info # 仓库的其他信息
│ └── exclude # 本地的排除文件规则,功能和.gitignore类似
├── logs # 保存所有更新操作的引用记录,主要用于git reflog等
├── objects # 所有文件的存储对象
└── refs # 具体的引用,主要存储分支和标签的引用
参考资料:https://blog.cti.app/archives/1344、解析.git 文件夹,深入了解 git 内部原理
.git/hooks
目录下存放着一些 shell 脚本,以 .sample
为后缀,表示默认不启动,这些 shell 脚本主要分为两大类:客户端和服务端,我们这里只关注客户端
在客户端下,也细分一些钩子种类,比如 提交工作流钩子、电子邮件工作流钩子和其它钩子:
pre-commit
钩子在键入提交信息前运行prepare-commit-msg
钩子在启动提交信息编辑器之前,默认信息被创建之后运行commit-msg
存有当前提交信息的临时文件,如果该钩子脚本以非零值退出,Git 将放弃提交post-commit
钩子在整个提交过程完成后运行
在文章开头,我们说这篇文章要做的事分为两类: lint 校验和 Commit Message 规范化,结合上面的钩子,所以我们的主要思路是:
pre-commit
:在键入提交信息前进行 lint 校验,比如 ESLint、Stylelint、Prettier 等commit-msg
:对当前 Commit Message 进行检查并使用工具来规范化 Message 信息
要在 git 钩子里去做这些事情,我们就需要借助工具 Husky 来配置 Git 钩子,请看下文
Husky
这是什么?借用 Chat GPT 的话来说:Husky 是一个 Git hooks 工具,它可以帮助我们在 Git 操作执行前或执行后自动执行脚本
跟官网一样,首先安装它
pnpm add --save-dev husky
并且使用 Husky 推荐的 init
命令
pnpm exec husky init
这条命令会在根目录下新增一个 .husky
目录,目录下会创建 pre-commit
脚本
并在 package.json
中生成一个名为 prepare
的脚本
在终端运行这个命令,来初始化 husky
pnpm prepare
它会在 .husky
下生成一个文件夹 _
确保你的项目中存在.git
文件夹,否则出现找不到.git
问题
好了,上面我们说,pre-commit
可以在键入提交信息前进行 lint 校验,现在,我们可以在这个钩子里做些事情了,但我们先来想象一个场景:
假设我们修改了文件 A 的代码并准备提交时,团队规范要求在提交前通过 lint 校验进行检查。通常情况下,全量项目的 lint 校验可能会耗费大量时间,而且有时会出现文件 B 因为 lint 校验未通过而被标记为错误,尽管我们并没有修改这个文件,而是其他同事提交的
能不能有一个方案,只 lint 我当前修改的文件,而不是全量 lint ,这样就可以避免 lint 的耗时,提高效率且有针对性
Lint-staged 就是用来解决这样的问题的,帮助我们在暂存的文件上执行 lint,而不是对整个项目进行全量校验。这样可以显著减少 lint 校验的时间消耗,提高效率,并确保校验的针对性
Lint-staged
lint-staged 在 Git 暂存文件上运行 linter
安装
pnpm add --save-dev lint-staged
配置
lint-staged 安装完成后,我们需要来配置一下它,这个做法和 ESLint、Stylelint 等很像
可以先看看 lint-staged-Configuration,即可以通过多种方式来配置,一种普遍的方式是直接在 package.json
中配置,比如
"lint-staged": {
"*.{js,ts,jsx,tsx}": [
"prettier --write",
"eslint --fix"
],
"*.vue": [
"prettier --write",
"eslint --fix",
"stylelint --fix",
]
},
还有种方式是创建一个配置文件
本文中使用 lint-staged.config.js
来配置,并采用 ESM 模式,请确保 package.json
中包含了 "type": "module"
,参阅 使用 JS 配置文件
在根目录下新建一个 lint-staged.config.js
,这个文件默认导出一个对象,写入以下基础模版
/** @type {import("lint-staged").Config} */
export default {
//...
};
lint-staged 会自动找到这个配置文件
然后,就可以在里面写入你的个性化配置,比如
/** @type {import("lint-staged").Config} */
export default {
"*.vue": ["prettier --write --cache", "eslint --fix", "stylelint --fix"],
"*.{js,ts,jsx,tsx}": ["prettier --write --cache", "eslint --fix"],
"*.{css,scss,less}": ["prettier --write --cache", "stylelint --fix"],
"*.html": ["prettier --write --cache", "stylelint --fix"],
"*.json": "prettier --write --cache",
};
你可以在官网找到配置说明
注意,在运行指定的 linter 和代码格式化工具时,建议先运行 Prettier ,因为它会对代码进行整体格式化,这样可以确保代码在进行 lint 校验之前是一致且格式良好的状态
接下来,我们需要确保 Git 钩子 pre-commit
能够正确调用 lint-staged
在根目录下的终端运行 shell 命令:
echo "pnpm lint-staged" > .husky/pre-commit
这条命令的作用是将 "pnpm lint-staged"
写入到 .husky/pre-commit
文件中,推荐你阅读 husky-v9.0.1
示例
接下来我们测试一下上述配置是否生效,在这之前,请确保你已经做好了如下准备:
- 确保你的项目里存在
.git
文件。如果是测试项目,可以使用命令git init
生成一个.git
文件 - 确保上文中的配置你已经操作完成
- 确保你之前已经安装并配置好了 Prettier、ESLint、Stylelint,如果没有,请看上一篇文章:受够了团队代码风格不统一?7千字教你从零搭建代码规范体系
在项目里添加一个 test.ts
文件,一个 test.vue
文件(位置随意)
随意写点内容,比如
//test.ts
const name = "fifteen";
//test.vue
<script setup lang="ts"></script>
<template>
<div>测试!!</div>
</template>
<style lang="less"></style>
将这两个文件提交到暂存区里:在终端输入命令 git add .
或者在 VS Code
中可视化操作
在终端执行命令 git commit -m 'test'
出现类似这样的画面,即表示 lint-staged 起作用了,可以看到,因为校验未通过,Commit 提交并未通过而是直接退出了
Commit 规范
在写下面章节之前,我想先来讲讲 Commit 规范的意义
试想一个场景,在工作中你接手了一个别人的项目,如何能够快速入手呢?除了基本的浏览代码,如果这个项目每次提交都遵循一定规范,比如 “feat:userManage Module”,那么项目的提交历史将变得规范而清晰,这很有助你接下来的上手工作,能了解项目的发展和主要功能,并找到相关的变更记录!
再比如团队的协同开发,你需要理解同事的部分业务代码,是否可以通过清晰、规范的提交信息来帮助我们了解这块的变更历史,从而简化交流达到提高效率的作用呢?
所以,commit 规范的意义体现在几个方面:
- 提高代码可读性和项目质量、改善项目可维护性
- 简化协作和沟通
- 方便版本管理和代码回溯
- 有助于自动化生成变更日志
这里不得不提到两个重要的提交规范:
Angular
规范,由Angular
团队制定并使用,也被社区广泛接受,推荐你阅读以下内容:
Conventional Commits
规范则是由Angular
规范发展调整而来的一个更通用的规范,官网的介绍是:约定式提交,一种用于给提交信息增加人机可读含义的规范,提供一套规则来创建清晰的提交历史:
如果你在开发 Angular 项目,那么遵循 Angular
提交规范是最优选择
如果你想要更通用的项目提交规范,那么选择 Conventional Commits 规范就行了,下文内容我们约定使用此规范
要让我们的提交信息能够符合 Conventional Commits 规范,可通过使用工具如 commitlint 来实现,请看下文
Commitlint
Commitlint 是一个用于检查 Git 提交信息是否符合规范的工具,用于在提交前自动检查提交信息
安装
pnpm add --save-dev @commitlint/{cli,config-conventional}
这条命令会在你的 package.json
文件的 devDependencies
中生成两个依赖:
@commitlint/cli
,命令行工具@commitlint/config-conventional
,此配置遵循 Conventional Commits 规范,与@commitlint/cli
配合使用,参阅 @commitlint/config-conventional
配置
类似的,我们需要一个配置文件,这里我们使用 commitlint.config.js
它也可以是一个 ts 格式的配置文件,但为了统一,我们这里使用 js
有两种方式配置,第一,在项目根目录下终端输入以下命令:
echo "/** @type {import('@commitlint/types').UserConfig} */ \n export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
这个 shell 脚本会在根目录下生成一个 commitlint.config.js
文件,并且写入以下内容:
第二种方式是手动创建
在项目根目录下新建一个 commitlint.config.js
文件,然后写入内容:
/** @type {import('@commitlint/types').UserConfig} */
export default { extends: ['@commitlint/config-conventional'] };
一个基本的提交约定遵循以下模式:
type(scope?): subject
body?
footer?
然后,我们就可以写入一些自定义的规则项了
比如,可以是这样的:
/** @type {import('@commitlint/types').UserConfig} */
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [
2,
"always",
[
"feat", // 新功能
"fix", // 修复bug
"docs", // 文档更新
"style", // 代码格式(不影响代码运行的变动)
"refactor", // 重构(既不是新增功能,也不是修改bug的代码变动)
"perf", // 性能优化
"test", // 增加测试
"chore", // 构建过程或辅助工具的变动
"revert", // 回滚到上一个版本
"build", // 编译相关的修改,例如发布版本、对项目构建或者依赖的改动,
"types", // 类型
"ci", // CI 配置文件和脚本的更改
],
],
"header-max-length": [2, "always", 100], //头部最大长度100
"body-max-line-length": [2, "always", 100], //body最大长度100
"footer-max-line-length": [2, "always", 100], //footer最大长度100
"type-empty": [2, "never"], //type 不能为空
"subject-empty": [2, "never"], //subject 不能为空
// "scope-empty": [2, "never"], //scope 不能为空
"type-case": [2, "always", "lower-case"], //type 小写
"scope-case": [2, "always", "lower-case"], //scope 小写
},
};
这些规则可以在 这里找到
在你写完自定义的配置规则后,我们要在创建提交之前对其进行 lint 校验,需要使用到 Husky 的 commit-msg
钩子,
commit-msg
概念:存有当前提交信息的临时文件,如果该钩子脚本以非零值退出,Git 将放弃提交
运行 shell 命令:
echo "pnpm dlx commitlint --edit \$1" > .husky/commit-msg
这个 shell 脚本会在 .husky
文件夹生成一个 commit-msg
文件,并且写入以下内容:
到这一步,我们已经实现了基本的 commit 信息校验,下面我们来试试
示例
测试一下对于提交信息的检测,看是否符合我们在 Commitlint 定义的规则
在终端输入命令:
pnpm exec commitlint --from HEAD~1 --to HEAD --verbose
这个命令的作用是检测你上一次的提交信息(前提是你有上一次提交信息),并让 Commitlint 输出更详细的信息
也可以来测试一下提交当前commit时,lint 校验是否起作用
在暂存区加一些测试文件,然后,输入 commit 信息
在这里,我的 commit 信息是 "ttt: a test" ,它的报错信息是:我的 type 类型不对,只能是枚举内的类型
我们上面配置了 lint-staged,所以会先对暂存的文件进行 lint
解决方法也很简单,修改 commit 信息使其符合规则即可
但你是否想过另一种方式?即通过自动化程序来避免这种纯手敲 commit 信息格式的错误呢
换句严谨点的话说,使用工具来给我们提供交互式命令行界面,创建符合 Commitlint 规范的 Commit 信息
社区有这样的解决方案,通过 Commitizen 来辅助我们生成一套标准化规范化的 Commit 信息,请看下文
Commitizen
Commitizen 是一个用于生成标准化规范化的 Commit 信息的命令行工具
在 Commitlint 官网的介绍中,有推荐一个 @commitlint/prompt-cli
来让我们创建交互式命令行,参阅这里
另一种代替方案是使用 Commitizen
我们简单比较一下这两者的区别:
@commitlint/prompt-cli
是官方提供的包,是 Commitlint 生态一部分,与 Commitlint 无缝衔接Commitizen
拥有着更大的社区、更广泛的使用、更成熟,灵活及定制化
本文中我们使用 Commitizen
安装
pnpm add --save-dev commitizen
安装完后,我们还需要安装一个适配器,这里介绍两个:
cz-conventional-changelog
是 Commitizen 文档中介绍的适配器,也是广泛使用的适配器@commitlint/cz-commitlint
是 Commitlint 官方提供的 Commitizen 适配器,提供了一种更现代的交互方式
这里使用 @commitlint/cz-commitlint
:
pnpm add --save-dev @commitlint/cz-commitlint
然后,你的 package.json
文件下的 devDependencies
会生成这两个依赖:
我们接着在 package.json
中写入以下内容:
{
"scripts": {
"commit": "git-cz"
},
"config": {
"commitizen": {
"path": "@commitlint/cz-commitlint"
}
},
}
配置
在做完上述的安装及基础配置后,我们可以自定义自己的交互文本,在 commitlint.config.js
文件中配置 prompt
属性,比如:
/** @type {import('@commitlint/types').UserConfig} */
export default {
extends: ["@commitlint/config-conventional"],
ignores: [(commit) => commit === ""],
rules: {
//...规则,上文已经配置
},
prompt: {
questions: {
type: {
description: "选择你要提交的变更类型",
enum: {
feat: {
description: "新功能",
title: "新功能",
emoji: "✨",
},
fix: {
description: "修复bug",
title: "Bug修复",
emoji: "🐛",
},
docs: {
description: "仅文档更改",
title: "文档",
emoji: "📚",
},
style: {
description: "不影响代码含义的更改(空白、格式化、缺少分号等)",
title: "样式",
emoji: "💎",
},
refactor: {
description: "既不修复bug也不添加新功能的代码更改",
title: "代码重构",
emoji: "📦",
},
perf: {
description: "提高性能的代码更改",
title: "性能优化",
emoji: "🚀",
},
test: {
description: "添加缺失的测试或修正现有的测试",
title: "测试",
emoji: "🚨",
},
//更多...
},
},
},
},
};
这些配置你可以在这里找到
示例
在配置好上面的内容后,我们就可以来测试一下效果了
修改一些文件内容,然后 git add .
放入暂存区
在项目终端输入命令
pnpm commit
出现这个交互式命令界面即为成功
本文中的所有示例都可以在 vue-clean-admin 中找到
参考资料
- Husky + Lint-staged + Commitlint + Commitizen + cz-git 配置 Git 提交规范
- Commitlint - 官网
- Commitizen - GitHub
了解更多
实战项目:vue-clean-admin
专栏往期回顾:
- 收下这份 Vue + TS + Vite 中后台系统搭建指南,从此不再害怕建项目
- 中后台开发必修课:Vue 项目中 Pinia 与 Router 完全攻略
- 用了这些 Vite 配置技巧,同事都以为我开挂了
- 受够了团队代码风格不统一?7千字教你从零搭建代码规范体系
交流讨论
文章如有错误或需要改进之处,欢迎指正
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。