概述
为提高了代码质量和团队协作效率。通过 Git hooks 和工具链的配合,实现了代码提交检测机制:
- 代码质量保证:通过 ESLint 检测代码规范和质量问题
- 提交信息规范:通过自定义检查确保提交信息格式统一
- 自动化流程:在提交时自动执行检查和格式化
- 错误拦截:检测不通过时阻止提交,确保代码库质量
1. 检测机制架构
采用两层检测机制来确保代码质量和提交规范:
sql
git commit
↓
┌─────────────────────────────────────┐
│ Pre-commit Hook │
│ ├── Prettier 代码格式化 │
│ ├── ESLint 代码质量检测 │
│ └── 重新暂存文件 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Prepare-commit-msg Hook │
│ └── 提交信息格式检查 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 提交完成 │
└─────────────────────────────────────┘
2. Git Hooks 实现原理
2.1 Yorkie 工具
项目使用 yorkie
包来管理 Git hooks,配置在 package.json
中:
json
{
"devDependencies": {
"yorkie": "^2.0.0"
},
"gitHooks": {
"pre-commit": "lint-staged",
"prepare-commit-msg": "node ./scripts/git-hooks/prepare-commit-msg.mjs"
}
"lint-staged": {
"*.{js,vue,json}": [
"prettier --write",
"eslint",
"git add ."
]
}
}
2.2 Hook 文件位置
实际的 Git hook 文件位于:
.git/hooks/pre-commit
.git/hooks/prepare-commit-msg
2.3 Yorkie Runner
yorkie
通过 node_modules/yorkie/src/runner.js
执行配置的命令:
javascript
const fs = require('fs')
const path = require('path')
const execa = require('execa')
const cwd = process.cwd()
const pkg = fs.readFileSync(path.join(cwd, 'package.json'))
const hooks = JSON.parse(pkg).gitHooks
const hook = process.argv[2]
const command = hooks[hook]
console.log(` > running ${hook} hook: ${command}`)
try {
execa.shellSync(command, { stdio: 'inherit' })
} catch (e) {
process.exit(1)
}
2.4 eslint配置示例
ESLint 配置位于 .eslintrc.js
文件中:
javascript
module.exports = {
// ... 其他配置
rules: {
// 不允许出现未使用变量
'no-unused-vars': 'error',
// 其他规则...
}
};
会被检测到的错误:
javascript
// ❌ 错误:声明了但未使用
const unusedVariable = 'hello';
const anotherVar = 123;
// ❌ 错误:函数参数未使用
function unusedParam(param1, param2) {
console.log('hello');
// param1 和 param2 都没有使用
}
2.5 提交信息标准格式
项目要求提交信息必须符合以下格式:
bash
type(可选 scope): 本次代码做了哪些事情,前面要有一个空格
https://project.feishu.cn/xxx
支持的提交类型
feature
- 新功能bugfix
- 修复bugtask
- 任务docs
- 文档style
- 样式refactor
- 重构performance
- 性能优化test
- 测试chore
- 杂务revert
- 回滚config
- 配置fix
- 修复feat
- 功能
3. 提交信息格式检查
3.1 提交代码检查
通过"pre-commit": "lint-staged"
自动检测提交的代码
3.2 提交信息检查
3.2.1 检查文件
提交信息检查由以下文件实现:
scripts/git-hooks/prepare-commit-msg.mjs
- 主检查文件scripts/git-hooks/messge-checker.mjs
- 检查逻辑实现
3.2.2 检查过程
messge-checker.mjs
javascript
import Readline from 'n-readlines';
const rules = [
{
example: 'type(可选 scope): 本次代码做了哪些事情,前面要有一个空格',
checker(line) {
const commitRE =
/^(feature|bugfix|task|docs|style|refactor|performance|test|chore|revert|config|fix|feat)(\(.+\))?(:) .*$/;
return commitRE.test(line);
},
special(line) {
const commitRE = /^([A-Za-z]+)(\(utils\)) .*$/;
return commitRE.test(line);
}
},
{
example: '必须是空行',
checker: function (line) {
return line.trim().length === 0;
}
},
{
example: 'https://project.feishu.cn/brm/story/detail/15260602',
checker: function (line) {
const commitRE = /^https:\/\/project.feishu.cn\//;
return commitRE.test(line) || line.includes('.feishu.cn/base/');
}
}
];
function commitMessageChecker(fileName, debug) {
const liner = new Readline(fileName);
let i = 0;
let isSpecial = false;
let line;
const ret = {
code: 0,
msg: []
};
debug && console.log('--------------------------------------------------------------');
while ((line = liner.next())) {
// docs 类型的提交,只需要校验第一行
if (
`${line}`.startsWith('docs') ||
`${line}`.startsWith('fix') ||
`${line}`.startsWith('feat') ||
`${line}`.startsWith('config')
) {
if (rules[i] && !rules[i].checker(`${line}`)) {
ret.code = -1;
ret.msg.push(`第${i + 1}行格式不对,规范为:${rules[i].example}`);
}
return ret;
}
// 忽略注释行
if (`${line}`.startsWith('#')) {
continue;
}
debug && console.log(`${line}`);
if (rules[i] && !rules[i].checker(`${line}`)) {
ret.code = -1;
ret.msg.push(`第${i + 1}行格式不对,规范为:${rules[i].example}`);
}
// 特殊的提交,只需要校验第一行
if (rules[i] && rules[i].special && rules[i].special(`${line}`)) {
isSpecial = true;
break;
}
i++;
}
debug && console.log('--------------------------------------------------------------');
// console.log(`isSpecial = ${isSpecial}, i = ${i}`)
// 不是utils域,就必须要3行
if (!isSpecial && i !== rules.length) {
ret.code = -1;
ret.msg.push(`commit message必须为${rules.length}行,而本次有${i}行`);
}
return ret;
}
export default commitMessageChecker;
// 另外一个实现切分文件多行的方法,这里没有采用主要是担心兼容性不够
/*
const data = fs.readFileSync('file.txt', 'UTF-8');
const lines = data.split(/\r?\n/);
lines.forEach((line) => {
console.log(line);
});
*/
prepare-commit-msg
javascript
import chalk from 'chalk';
import checker from './messge-checker.mjs';
const msgPath = './.git/COMMIT_EDITMSG';
const ret = checker(msgPath, true);
if (ret.code !== 0) {
console.error(
` ${chalk.bgRed.white(' ERROR ')} ${chalk.red('commit message 格式错误,信息如下:')}\n\n${ret.msg
.map((m) => {
return ` ${chalk.green(m)}`;
})
.join('\n\n')}\n\n${chalk.red(' 详细规范 README.md #commit message 规范')}`
);
process.exit(1);
}
4. 检测触发流程
4.1 完整执行流程
bash
git commit
↓
┌─────────────────────────────────────┐
│ .git/hooks/pre-commit │
│ ↓ │
│ yorkie runner │
│ ↓ │
│ lint-staged │
│ ↓ │
│ prettier --write [files] │
│ ↓ │
│ eslint [files] ← 这里检测未使用变量 │
│ ↓ │
│ git add . │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ .git/hooks/prepare-commit-msg │
│ ↓ │
│ yorkie runner │
│ ↓ │
│ prepare-commit-msg.mjs │
│ ↓ │
│ messge-checker.mjs ← 检查提交信息 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 提交完成 │
└─────────────────────────────────────┘
5. 错误处理机制
5.1 ESLint 错误处理
当 ESLint 检测到错误时:
bash
src/pages/header/guideDialogs/audioTranscriptionModal.vue
231:8 error 'SearchSelect' is defined but never used no-unused-vars
373:7 error 'searchCompanyByKeyword' is assigned a value but never used no-unused-vars
✖ 2 problems (2 errors, 0 warnings)
- 显示具体的错误位置和类型
- 返回非零退出码
- 阻止提交继续执行
5.2 提交信息错误处理
当提交信息格式错误时:
bash
ERROR commit message 格式错误,信息如下:
第1行格式不对,规范为:type(可选 scope): 本次代码做了哪些事情,前面要有一个空格
详细规范 README.md #commit message 规范
- 使用
chalk
库美化错误信息 - 显示具体的错误位置和规范要求
- 调用
process.exit(1)
强制终止