Hello,我是Ludens,名字来源于小岛工作室的吉祥物——LUDENS。
前言
最近在掘金上的关于Git系列文章有很多,那我也来记录一下之前遇到过的问题和解决方法。
之前在开发中遇到一个问题,拉取仓库最新代码之后,本地运行报语法错误,有成员把错误的代码push上去了,从而引发了一次团队内部对代码提交流程的思考。最后决定把Husky
,这只哈士奇运用到项目里面去。
那在介绍这只哈士奇之前,首先我们要了解一下Githooks
。
Githooks
Githooks是什么
Githooks的原文描述是
Hooks are programs you can place in a hooks directory to trigger actions at certain points in git’s execution. Hooks that don’t have the executable bit set are ignored.
钩子是您可以放置在挂钩目录中的程序,以便在 git 执行的某些点触发操作。没有可执行位集的挂钩将被忽略。 ----Bing翻译
简单来说那就是类似Vue的生命周期钩子一样,Git也会在它运行周期里面的某些时间点,让用户添加和执行自定义的函数
。
Githooks保存在哪里
Githooks默认保存在项目的.git/hooks
文件夹内,打开之后能看到很多Hooks的sample
我们打开一个Simple看一下里面是什么样子的
可以发现,Githooks文件其实是一个shell
脚本,我们可以通过修改这个shell脚本来实现自定义功能。
那问题来了,如果Githooks是存放在.git/hooks
文件夹内,那么我们该如何去对自定义的Hooks进行版本控制呢?
好在Git也提供了一个自定义Hooks文件夹的config,可以通过设置core.hooksPath
可以使Hooks存放的路径指向自定义的目录,这样我们的问题就迎刃而解了。
Githooks能解决什么
回到项目中的实际问题
- 问题:远程仓库的代码中存在语法错误
- 原因:有成员没有仔细检查,误把有语法错误的代码上传了
- 解决方法:在
commit之前
检查在暂存区的代码,看是否存在语法错误 显然,如果只靠当前成员去检查自己添加到暂存区的代码有没有错误,是很不可靠且低效的行为,那么这里我们可以用到pre-commit
这一个hook了,它可以在git commit之前执行我们自定义的语句,当执行错误
或者返回非0状态
的时候阻止commit行为。
除了这个Hook以外,还有哪些就不一一介绍了,一般的Git操作,例如commit、push等会提供操作前或操作后的Hook,有兴趣的可以查看Githooks去了解。
Husky
通过上文我们了解Githooks之后,Husky就可以出场了。
Husky是什么
用一句话概括,那就是它能够
简化
上述创建或者修改Githooks的操作。
那让我们来大概看一下它的源码是如何实现的
- 初始化的时候,会先检查该项目是否通过Git来托管代码的
if (spawnSync('git', ['rev-parse']).status !== 0) {
l('not a Git repository, skipping hooks installation')
return
}
- 检查通过之后,会创建.husky文件夹用来存放Githooks和husky的配置
// 创建 .husky/_
mkdirSync(join(dir, '_'), { recursive: true })
// 创建 .husky/.gitignore
writeFileSync(join(dir, '.gitignore'), '_\n')
// 将husky.sh 复制到 .husky/_/husky.sh
copyFileSync(
fileURLToPath(new URL('./husky.sh', import.meta.url)),
join(dir, '_/husky.sh'),
)
- 然后通过core.hooksPath使项目Githooks的路径指向新创建的.husky文件夹,这样husky初始化的流程大致完成了
const { error } = spawnSync('git', ['config', 'core.hooksPath', dir])
if (error) {
throw error
}
- 初始化之后通过add命令来进行创建或者往文件后面增加语句
if (existsSync(file)) {
appendFileSync(file, `${cmd}\n`)
l(`updated ${file}`)
} else {
set(file, cmd)
}
add命令的编辑功能只支持往文件后面
增加语句
,如果要修改之前的语句,需要打开该文件进行修改
如何使用Husky
为了更好的演示这个功能,我们来做一个小Demo
- 先用npm初始化一个小项目,然后我们安装
eslint
、lint-staged
和husky
npm install --save-dev eslint lint-staged husky
- eslint初始化之后,我们来初始化husky
npx husky install
- 这时候我们就看到了.husky的文件夹,添加
lintstagedrc.js
用来配置暂存区文件相对应的lint
// .husky/lintstagedrc.js
module.exports = {
'*.js': ['eslint'],
};
- 创建一个pre-commit hook用来对暂存区的文件进行检查
npx husky add .husky/pre-commit "npx lint-staged -c .husky/lintstagedrc.js"
- 在项目中新增一个js,故意写上语法错误
const testA = {
console.log(testA);
- 为了更好的观看,这里用了GitHub Desktop去做Git操作,可以看到在commit之前会进行检测,在检测到错误的时候会返回错误提示,且阻止commit行为
- 把语法错误修复之后再commit,这时候就可以顺利通过检测且commit了,这样我们的目的就达成了
注意,在Windows
上,如果是通过Git Bash
去进行Git操作的,Githook中的语句含有Yarn
时,会导致该Githook失效,这时候需要在.husky中添加一个common.sh
// .husky/common.sh
command_exists () {
command -v "$1" >/dev/null 2>&1
}
# Workaround for Windows 10, Git Bash and Yarn
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi
并且在用到Yarn的Hook文件中添加
// .husky/(hook文件)
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
yarn ...
Yorkie和Husky区别
熟悉Vue CLI的朋友们可能知道,Vue CLI中自带Git Hook的功能,实现这个的Yorkie正是yyx fork自旧版本的Husky并做了修改,但其实两者本质上区别不大,也是大大简化了Githooks创建和新增语句的过程。
自从Git新增core.hooksPath
这个可以自定义Githooks路径的config之后,Husky就发生了不同。
- 新版本的Husky就开始通过这个config,将hooks的路径修改到
/husky
,从而更好的管理属于该项目的Githooks。 - 旧版本的Husky是根据package.json中的Githooks字段去维护
.git/hooks
中的Hooks,相当于该项目对Githooks的自定义是存在于两个地方.git/hooks
和package.json
,这样会有可能导致两者之间不同步,以上问题Yorkie同理。 所以在core.hooksPath
新增之后,Husky就放弃了修改.git/hooks这种方式。
参考资料
后记
通过这次经历,对Git有了更多的了解,也侧面反映了自己对于工程化认识的不足,这方面还要多加学习,后续还可以通过Husky搭配一系列的lint工具(例如commitlint、stylelint),来更好的规范代码的提交流程。
第一次写分享技术类心得的文章,里面有不足或者错误的,请各位多多包涵和提出错误点或宝贵意见,也非常感谢阿宝哥的这篇文章《写了 200 多篇文章后,我总结的写作心得》,在我写这篇文章时给了我很大的帮助。