husky钩子的一些用法

前言

husky是一个npm包,可以将git内置的钩子暴露出来,很方便地进行钩子的命令注入,而不需要在.git目录下自己写shell脚本了;不仅可以执行js文件作为脚本,还可以将脚本暴露出来,方便在git项目中进行管理。

基本用法

原理

husky支持的钩子就是git hooks,可以在对应钩子指定一条shell命令,husky会自动将这个命令写入.git目录下的对应钩子脚本,当触发这个git钩子时则会执行我们写入的命令;

因此husky本质上就是执行shell命令,因此只要是shell命令都可以在钩子中执行;由于nodeJS也可以通过命令行执行,所以直接用js来充当脚本也是可以的;

命令配置

可以在package.json文件的husky.hooks对象中写入相应的命令即可,键名就是钩子名称,键值就是需要执行的命令,如:

1
2
3
4
5
6
7
8
9
{
"husky": {
"hooks": {
"pre-commit": "node ./hooks/pre-commit",
"commit-msg": "node ./hooks/commit-msg $HUSKY_GIT_PARAMS && commitlint -E HUSKY_GIT_PARAMS",
"post-merge": "node ./hooks/post-merge"
}
}
}

husky当然也支持通过专门的配置文件来指定钩子命令,但是使用过程中经常发生钩子配置不起作用的情况,还是package.json这种方式最稳定;

常用钩子

  • pre-commit:由git commit命令触发,在commit-msg之前;
  • commit-msggit commitgit merge都会触发,会传递一个参数,该参数为存放当前commit消息的临时文件路径;可以通过--no-verify参数来跳过commit-msg钩子;
  • post-merge:触发于merge完成后;

一些技巧

在 node 脚本中如何退出

当使用node脚本进行检测,希望检测不通过时阻止git进行下一步操作,即终止操作;仅仅抛出错误是不能终止命令的,只能抛出exit状态才能终止;如:

1
process.exit(1) // exit状态为1才能终止

在 node 中执行 shell 命令

有些用于检测的信息只能通过shell命令执行获取(如git相关的信息),如果想要在node中获取到这些信息,可以使用node自带的一些方法来执行;比如execspawn方法。

上面两个都是child_process模块里面的方法:

1
const { spawn, exec } = require('child_process')

虽然这两个方法都可以执行shell命令,但是具体用途有所不同;就作用而言,spawn方法更加广泛,可控性更强;

  • 当仅仅需要执行shell命令来获取信息(文本)时,可以使用exec方法;
  • 如果需要按照原格式(即包含颜色,缩进,换行等)暴露shell命令的标准输出,那么就需要用到spawn方法了;因为exec得到的标准输出已经格式化了,仅仅是普通的文本字符串;

可以将上述方法包装成Promise对象,这样更加方便进行同步调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 执行shell命令,返回promise;
* 这种方式不会捕获的命令输出会被格式化,即没有颜色缩进等;
* @param {string} shell shell命令
*/
function execShell (shell) {
return new Promise((resolve, reject) => {
exec(shell, (err, stdout, stderr) => {
if (err) {
process.stdout.write(stdout)
error(`命令执行错误!\n\n${stderr}`)
reject(stderr)
} else {
resolve(stdout)
}
})
})
}

/**
* 执行shell命令,返回promise;
* 这种方式不能捕获命令输出,但是可以按原格式进行输出;
* @param {string} shell shell命令
*/
function execShellOrigin (shell) {
const shellArr = shell.split(' ')
const process = spawn(shellArr[0], shellArr.slice(1), {
stdio: 'inherit' // 命令的输出按原格式进行输出
})
return new Promise((resolve, reject) => {
process.on('close', code => {
if (code) { // 根据exit code可以判断命令执行结果
reject(code)
} else {
resolve()
}
})
})
}

在 commit-msg 钩子中获取/修改 commit 消息

执行commit命令后,git会将commit消息存放于一个临时文件中;然后触发pre-commit钩子,pre-commit钩子成功之后就会触发commit-msgcommit-msg钩子成功后则会将临时文件中的文本作为此次commit消息进行存储;

并且commit-msg钩子会对脚本传一个参数,这个参数就是存放commit消息的临时文件的路径;所以得到这个参数,就可以读取该文件的内容,也就能得到当前commit消息了;同理,在commit-msg钩子中覆盖这个文件就能对此次commit消息进行修改了;

It takes a single parameter, the name of the file that holds the proposed commit log message.[1]

不过,由于在husky中的指定的commit-msg钩子命令并不是git直接执行的,因此只能通过husky间接暴露的变量$HUSKY_GIT_PARAMS来获取临时文件的地址,如:

1
2
# $HUSKY_GIT_PARAMS变量就是commit-msg钩子传递的文件路径参数
node ./hooks/commit-msg $HUSKY_GIT_PARAMS

Git hooks can get parameters via command-line arguments and stdin. Husky makes them accessible via HUSKY_GIT_PARAMS and HUSKY_GIT_STDIN environment variables.[2]

node脚本内部就可以利用process.argv来获取命令行参数了;

1
const param = process.argv[process.argv.length - 1] // 获取git commit消息临时存放文件地址

读取和写入操作既可以依靠node自带的方法,也可以利用shell命令(shell命令简单粗暴);

钩子没有触发

当第一次安装husky的时候,可能会出现.git/hooks里面的文件没有被覆盖的情况;此时,git hooks仍然是之前的状态(默认是没有效果的);如果是husky安装正常,使用命令ls .git/hoooks查看文件则是下面这样:

img

随便打开一个钩子脚本文件,内容可能是这样:

1
2
3
4
5
6
7
8
#!/bin/sh
# husky

# Created by Husky v4.2.5 (https://github.com/typicode/husky#readme)
# At: 2020-6-16 17:14:54
# From: /Users/snowdream/Desktop/bhb/dp-admin/node_modules/husky (https://github.com/typicode/husky#readme)

. "$(dirname "$0")/husky.sh"

如果不是上述形式,那么就是husky注入失败;可以通过重新安装(先uninstallinstall)进行重新注入,因为每次安装husky时会重新覆盖一次.git/hooks脚本文件。

可用 shell 命令收集

获取当前 git 分支名称

1
git symbolic-ref --short -q HEAD

获取最新的一条 git 提交信息

1
git rev-list --no-walk --header HEAD

可以得到很详细的信息,用于进一步检测,比如检测最新的一次提交是不是mergemerge行为是否合法等待;

1
2
3
4
5
6
7
635a70d8e1eaccfe7a460e76ae400a0e5cc95161
tree 6ff519aef0471392e6da9f068cace81f3e874b56
parent c4e3385d4cab66b27ba64f2bce3df179b0bb4d3a
author xiexuefeng <xxx@qq.com> 1592881689 +0800
committer xiexuefeng <xxx@qq.com> 1592881689 +0800

fix: xxx

读取或写入文件

读取使用cat命令:

1
cat file-path

写入使用echo命令:

1
echo "content" > file-path

相关文档


  1. Git - githooks Documentation ↩︎

  2. GitHub - typicode/husky: Git hooks made easy 🐶 woof! ↩︎