Husky,这只哈士奇是什么?

3,559 阅读6分钟

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

Githooks Sample文件截图.png

我们打开一个Simple看一下里面是什么样子的

Githooks pro-commit截图.png

可以发现,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初始化一个小项目,然后我们安装eslintlint-stagedhusky
    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行为

git 报错.png

  • 把语法错误修复之后再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/hookspackage.json,这样会有可能导致两者之间不同步,以上问题Yorkie同理。 所以在core.hooksPath新增之后,Husky就放弃了修改.git/hooks这种方式。

参考资料

后记

通过这次经历,对Git有了更多的了解,也侧面反映了自己对于工程化认识的不足,这方面还要多加学习,后续还可以通过Husky搭配一系列的lint工具(例如commitlint、stylelint),来更好的规范代码的提交流程。

第一次写分享技术类心得的文章,里面有不足或者错误的,请各位多多包涵和提出错误点或宝贵意见,也非常感谢阿宝哥的这篇文章《写了 200 多篇文章后,我总结的写作心得》,在我写这篇文章时给了我很大的帮助。