hook
hook,钩子:即某个时期或事件之前、之后执行和一些操作或脚本。与我们常说的中间件很相似。
git-hooks
git有很多hook,每当我们git init初始化一个git仓库时,都会创建一个.git的隐藏文件夹,git的hook就放在.git/hooks下。hooks以.sample为后缀的是示例文件。git-hooks有客户端hooks和服务端的hooks。
这里介绍客户端会用到的hooks
- pre-commit: 钩子在键入提交信息前运行。 它用于检查即将提交的快照,例如,检查是否有所遗漏,确保测试运行,以及核查代码。
- commit-msg: 钩子在启动提交信息编辑器之前,默认信息被创建之后运行。 它允许你编辑提交者所看到的默认信息。
- post-merge: 钩子在拉取代码合并后运行。
git仓库根目录的隐藏文件夹.git里的git-hooks是不被git管理的,只存在于本地。所以无法让仓库中所有成员使用同样的hooks。这时候就需要通过工具库来使用git-hooks,就是husky。
husky
- 安装 husky
pnpm add --save-dev husky
- 添加 git hooks
pnpm exec husky init
init命令简化了在项目中设置husky的过程。它在.husty/中创建了一个预提交脚本,并在package.json中更新了prepare脚本
- 添加新钩子
# echo方式,是适用于linux/mac的。
echo "command" > .husky/pre-[hook_name]
#window
node --eval "fs.writeFileSync('./.husky/.pre-[hook-name]','commoand')"
这样就可以在hooh_name钩子期间触发command命令
接下来我们就可以借助lint-staged来实现对暂存区的代码进行风格校验和格式化、借助commitlint校验commit信息。
lint-staged
一般情况下 lint-staged 搭配着 Husky 一起使用。需要保证lint-staged 会在 pre-commit hook 中被运行。在使用lint-staged校验代码和代码风格时这里使用的是prettier和eslint。
- 安装
pnpm install lint-staged --save-dev
- 添加 git hooks
#mac/linux
echo "npx lint-staged" > .husky/pre-commit
#window
node --eval "fs.writeFileSync('.husky/pre-commit','npx lint-staged\n')"
- 配置
形式一:package.json 中添加一个 lint-staged 项。
"lint-staged": {
"*.{vue,js,jsx,ts,tsx}": ["eslint --fix ", "prettier --write"],
"*.{md,json,scss,css,sass,less,html}": ["prettier --write"]
}
形式二:根目录添加一个 .lintstagedrc 文件。
//.lintstagedrc
{
"*.{vue,js,jsx,ts,tsx}": ["eslint --fix ", "prettier --write"],
"*.{md,json,scss,css,sass,less,html}": ["prettier --write"]
}
之后每次的pre-commit钩子触发都会执行代码校验和代码格式化。
commitlint
commitlint是对commit信息校验的一款npm包。
- 安装
pnpm i @commitlint/cli @commitlint/config-conventional -D
- 添加git-hook
#linux/mac
echo 'npx --no-install commitlint --edit "$1"' > .husky/commit-msg
#window
node --eval "fs.writeFileSync('./.husky/commit-msg','npx --no-install commitlint --edit "$1"')"
现在我们只是增加了对commit message的校验,但是在使用 Git 过程中,填写 commit message其实是比较麻烦的。如果没有良好的 commit message 规范,commit历史就会很乱。这时就需要 commitizen 来协助开发者填写 commit 信息。
commitizen
commitizen是基于 Node.js 开发的 git commit 命令行工具,用来生成标准化的commit-message。commitizen 本身只提供命令行交互框架以及一些 git 命令的执行,实际的规则则需要通过适配器来定义,commitizen 留有对应的适配器接口。市面上有很多适配器,比如: cz-conventional-changelog、cz-git、cz-conventional-changelog-zh-cn。常见适配器生产commit-message模板
<type>(<scope>): <subject>
<空行>
<body>
<空行>
<footer>
- 安装
commitizen和标准输出格式的 commitizen 适配器cz-git
pnpm add --save-dev commitizen cz-git
配置 package.json 命令
//提交时只需使用用cz而不是git commit。您还可以使用git-cz,它是cz的别名。
"scripts": {
"commit": "cz"
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
},
运行pnpm run commit以触发git-hook规范commit message
配置模板
例如 Emoji 模板
//commitlint.config.js
import { defineConfig } from "cz-git";
export default defineConfig({
extends: ["@commitlint/config-conventional"],
rules: {
// @see: https://commitlint.js.org/#/reference-rules
},
prompt: {
alias: { fd: "docs: fix typos" },
messages: {
type: "Select the type of change that you're committing:",
scope: "Denote the SCOPE of this change (optional):",
customScope: "Denote the SCOPE of this change:",
subject: "Write a SHORT, IMPERATIVE tense description of the change:\n",
body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new line:\n',
footerPrefixesSelect: "Select the ISSUES type of changeList by this change (optional):",
customFooterPrefix: "Input ISSUES prefix:",
footer: "List any ISSUES by this change. E.g.: #31, #34:\n",
generatingByAI: "Generating your AI commit subject...",
generatedSelectByAI: "Select suitable subject by AI generated:",
confirmCommit: "Are you sure you want to proceed with the commit above?",
},
types: [
{ value: "feat", name: "feat: ✨ A new feature", emoji: ":sparkles:" },
{ value: "fix", name: "fix: 🐛 A bug fix", emoji: ":bug:" },
{ value: "docs", name: "docs: 📝 Documentation only changes", emoji: ":memo:" },
{ value: "style", name: "style: 💄 Changes that do not affect the meaning of the code", emoji: ":lipstick:" },
{
value: "refactor",
name: "refactor: ♻️ A code change that neither fixes a bug nor adds a feature",
emoji: ":recycle:",
},
{ value: "perf", name: "perf: ⚡️ A code change that improves performance", emoji: ":zap:" },
{
value: "test",
name: "test: ✅ Adding missing tests or correcting existing tests",
emoji: ":white_check_mark:",
},
{
value: "build",
name: "build: 📦️ Changes that affect the build system or external dependencies",
emoji: ":package:",
},
{ value: "ci", name: "ci: 🎡 Changes to our CI configuration files and scripts", emoji: ":ferris_wheel:" },
{ value: "chore", name: "chore: 🔨 Other changes that don't modify src or test files", emoji: ":hammer:" },
{ value: "revert", name: "revert: ⏪️ Reverts a previous commit", emoji: ":rewind:" },
],
useEmoji: true,
emojiAlign: "center",
useAI: false,
aiNumber: 1,
themeColorCode: "",
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: "bottom",
customScopesAlias: "custom",
emptyScopesAlias: "empty",
upperCaseSubject: null,
markBreakingChangeMode: false,
allowBreakingChanges: ["feat", "fix"],
breaklineNumber: 100,
breaklineChar: "|",
skipQuestions: [],
issuePrefixes: [{ value: "closed", name: "closed: ISSUES has been processed" }],
customIssuePrefixAlign: "top",
emptyIssuePrefixAlias: "skip",
customIssuePrefixAlias: "custom",
allowCustomIssuePrefix: true,
allowEmptyIssuePrefix: true,
confirmColorize: true,
scopeOverrides: undefined,
defaultBody: "",
defaultIssues: "",
defaultScope: "",
defaultSubject: "",
},
});
中文模板
//commitlint.config.js
import { defineConfig } from 'cz-git';
export default defineConfig({
extends: ['@commitlint/config-conventional'],
rules: {
// @see: https://commitlint.js.org/#/reference-rules
},
prompt: {
alias: { fd: 'docs: fix typos' },
messages: {
type: '选择你要提交的类型 :',
scope: '选择一个提交范围(可选):',
customScope: '请输入自定义的提交范围 :',
subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixsSelect: '选择关联issue前缀(可选):',
customFooterPrefixs: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?'
},
types: [
{ value: 'feat: 特性', name: '特性: ✨ 新增功能', emoji: '✨' },
{ value: 'fix: 修复', name: '修复: 🧩 修复缺陷', emoji: '🧩' },
{ value: 'style: 格式', name: '样式: 🎨 样式调整(不涉及业务,UI调整)', emoji: '🎨' },
{ value: 'docs: 文档', name: '文档: 📚 文档变更', emoji: '📚' },
{ value: 'style: 格式', name: '格式: 🎨 代码格式(不影响功能,例如空格、分号等格式修正)', emoji: '🎨' },
{ value: 'refactor: 重构', name: '重构: ♻️ 代码重构(不包括 bug 修复、功能新增)', emoji: '♻️' },
{ value: 'perf: 性能', name: '性能: ⚡️ 性能优化', emoji: '⚡️' },
{ value: 'test: 测试', name: '测试: ✅ 添加疏漏测试或已有测试改动', emoji: '✅' },
{ value: 'chore: 杂务', name: '杂务: ☕ 零星工作(如升级 npm 包、修改配置等)', emoji: '☕' },
{ value: 'ci: 集成', name: '集成: 🛠️ 修改 CI 配置、脚本', emoji: '🛠️' },
{ value: 'revert: 回退', name: '回退: ⏪️ 回滚 commit', emoji: '⏪️' },
{ value: 'build: 打包', name: '打包: 📦️ 项目打包发布', emoji: '📦️' }
],
useEmoji: true
}
});
拉去远程仓库代码自动安装依赖
- 添加git-hook
#linux/mac
echo 'pnpm install' > .husky/post-merge
#window
node --eval "fs.writeFileSync('.husky/post-merge','pnpm install')"