Skip to content

husky管理git hooks

前言

Git 能在特定的重要动作发生时触发自定义脚本,其中比较常用hooks的有:pre-commit、commit-msg、pre-push 等钩子(hooks)。我们可以在 pre-commit 触发时进行代码格式验证,在 commit-msg 触发时对 commit 消息和提交用户进行验证,在 pre-push 触发时进行单元测试、e2e 测试等操作。

git Hooks

每一个本地的git托管的项目,都会有有一个.git的目录。默认是不可见的,可以将vscode设置中设置.git文件可见

image.png

git Hooks就是.git/hooks文件下,保存了一些 shell 脚本,然后在对应的钩子中执行这些脚本就行了。一个还没有配置 Git Hooks 的仓库,默认会有很多.sample结尾的文件,这些都是示例文件,若要启用某个hooks,只需要将后缀.sample删除即可

在提交代码到至远程仓库时,.git文件夹不会被提交到远程,这就导致一个问题,我们在本地配置好 Git Hook 后,共同开发的同学是没办法通过pull获得我们本地配置好的hooks的,若要共享我们配置好的钩子,需要采用第三方工具,比如 Husky,下文会讲到。

git hooks钩子触发时机

v2161c0a131454d4b24f16a9a3125d947f_r.jpg

介绍husky

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

husky原理

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

husky版本差异

特别注意:Husky的老板恶霸那边与新版本的变化非常大。一些老版本配置好的配置,在新版本下可能会失效。所以若出现明明配置对了,但是不起作用钩子,建议先检查一下版本,V4版本之前与之后的配置项可能完全不同,下文中,使用的时V4.2.5以及更低的版本 另外,安装husky,Node.js 版本要 >=10

node 脚本与Husky

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

1 在 node 脚本中如何退出

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

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

2 在 node 中执行 shell 命令

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

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

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

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

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

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

/**
 * 执行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()
      }
    })
  })
}

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

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

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

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

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

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

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

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

Husky简单配置commit-msg自定义格式

方式一

commitlint + husky

1、下载 @commitlint/cli@16.2.3 husky@1.3.1

npm i npm i @commitlint/cli@16.2.3 husky@1.3.1  -D

2、根目录下编写 commitlint.config.js文件

// commitlint.config.js
module.exports = {
  rules: {
    "code-count": [2, "always"]
  },
  plugins: [
    {
      rules: {
        "code-count": params => {
          const reg = /^%\d+[\s\S]*/;
          const { subject, raw } = params;
          console.log(params, subject, raw);
          return [
            reg.test(raw),
            "ERROR: commit格式错误,需要以【%需求ID】开头,例如:'%834723 xxx功能开发完成'"
          ];
        }
      }
    }
  ]
};

3、package.json文件下添加配置

"husky": {
    "hooks": {
      "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS"
    }
  },

方式二

nodejs shell + husky 参考文章: https://zhuanlan.zhihu.com/p/391221822https://note.xiexuefeng.cc/post/husky-and-git-hooks/

1、 下载husky

js
npm i husky@4.2.5 -D

2、修改package.json

(1)添加scripts

json
 "scripts": {
    "postinstall": "git config core.hooksPath hooks"
  },

注意: 在macx系统上面,以上脚本可能会报错,须要加上chmod 700 hooks/*,原因是 hooks 脚本默认为不可执行,所以需要将它设为可执行

json
"scripts": {
    "postinstall": "git config core.hooksPath hooks && chmod 700 hooks/*"
  },

(2)添加husky节点

json
"husky": {
    "hooks": {
      "commit-msg": "node ./hooks/commit-msg $HUSKY_GIT_PARAMS"
    }
  }

3、根目录下新建hook目录

commit-msg hooks 中,我们可以对 commit 消息和用户进行校验。

/hook/commit-msg文件内容

a. sh模式

bash
#!/bin/sh

# 用 `` 可以将命令的输出结果赋值给变量
# 获取当前提交的 commit msg
commit_msg=`cat $1`

# 获取用户 email
email=`git config user.email`
# 根据需要修改正则即可
msg_re="^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|release|workflow)(\(.+\))?: .{1,100}"

if [[ ! $commit_msg =~ $msg_re ]]
then
    echo -e "\e[31m不合法的 commit 消息提交格式,请使用正确的格式: \e[0m"
    echo -e "\e[31mfeat: add comments \e[0m"
    echo -e "\e[31mfix: handle events on blur (close #28) \e[0m"
    echo -e "\e[31m详情请查看 git commit 提交规范:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md \e[0m"


    # 异常退出
    exit 1
fi
# 对用户权限做判断则比较简单,只需要检查用户的邮箱或用户名就可以了(假设现在只有 abc 公司的员工才有权限提交代码)。
email_re="@abc\.com"
if [[ ! $email =~ $email_re ]]
then
    echo "此用户没有权限,具有权限的用户为: xxx@abc.com"

    # 异常退出
    exit 1
fi

或者b. node shell模式

js
#!/usr/bin/env node
const childProcess = require('child_process');
const fs = require('fs');

const email = childProcess.execSync('git config user.email').toString().trim();
const msg = fs.readFileSync(process.argv[2], 'utf-8').trim(); // 索引 2 对应的 commit 消息文件
const commitRE = /^(feat|fix|docs|style|refactor|perf|test|workflow|build|ci|chore|release|workflow)(\(.+\))?: .{1,100}/;

if (!commitRE.test(msg)) {
  console.log();
  console.error('不合法的 commit 消息格式,请使用正确的提交格式:');
  console.error('feat: add \'comments\' option');
  console.error('fix: handle events on blur (close #28)');
  console.error('详情请查看 git commit 提交规范:https://github.com/woai3c/Front-end-articles/blob/master/git%20commit%20style.md。');
  process.exit(1);
}

if (!/@qq\.com$/.test(email)) {
  console.error('此用户没有权限,具有权限的用户为: xxx@qq.com');
  process.exit(1);
}

pre-commit提交前校验

eslint中文网:http://eslint.cn/

团队协作开发,每个人用的格式化工具不一致,I编码风格不一致,会导致代码提交到仓库后难以维护, 你的代码提交后,被别人拉取, 由于格式原 因,会有很多无用的改动影响, 可以用eslint对代码风格进行约束,方法有很多,由于这里介绍的是husky,所以就用husky + pre-commit来进行代码提交前的校验和自动约束pre-commit会执行在git commit之前,

所用插件及作用

用到的插件:eslint+lint-staged+husky

eslint:提供可配置的规则,用这些规则校验代码规范 eslint . 默认会检查所有的代码规范 linf-staged:可以过滤出暂存区中的代码,用eslint对这部分改动的文件进行代码检查,而不是所有,从而提高检查速度 husky:提供可配置的git hooks,并将hooks一起提交到仓库,可以将git hooks的指向从.git 更改到自定义目录

eslint中文网:http://eslint.cn/

实现目标

实现提git commit时候自动检测代码规范,自动修复代码,并且同步到远程仓库 步骤如下

步骤一:配置eslint

  1. 下载eslint
js
npm i eslint -D
  1. 生成配置文件
js
npx eslint --init
// 或者
npm init @eslint/config

接着选择配置,参考: a2bd5b62086e65915f771de45803e6f.png

eslint 会有限找js配置文件。没有js会找json, 在没有才会找ymal,所以这里以js有限,可以提高查找效率

运行完上面命令后,会生成一个配置文件.eslintrc.js

js
// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true
  },
  extends: [
    'standard'
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  rules: {
  }
}

新建忽略文件.eslintignore

js
// .eslintignore
node_modules/*
  1. 配置lint命令
js
// package.json
"scripts": {
  "lint":"eslint ."
},
  1. 运行命令lint代码
js
npm run lint

就可以看到命令行有一堆报错和警告

可以运行命令检查并修复

js
npm run lint -- --fix

但是有一些报错不能自动修复,我们可以用规则将其关闭,例如: image.png

有声明了但是未使用的变量;

eslint规则文档地址

具体的eslint规则可以参考文档:http://eslint.cn/docs/rules/

步骤二: 配置husky+lint-staged

这里发规则继承的是eslint-config-egg, github地址:[https://github.com/eggjs/eslint-config-egg](https://github.com/eg> gjs/eslint-config-egg)

1.安装husky

安装husky的步骤参照上面commit-msg的步骤

2.添加配置

json
// package.json
"husky": {
  "hooks": {
    "pre-commit": "node ./hooks/pre-commit",
    "commit-msg": "node ./hooks/commit-msg $HUSKY_GIT_PARAMS"
  }
},

3. 在husky目录下创建pre-commit文件

在 pre-commit 钩子中要做的事情特别简单,只对要提交的代码格式进行检查,因此脚本代码比较少:

/hook/pre-commit文件内容

① sh脚本版本

#!/bin/sh
npm run lint-staged

# 获取上面脚本的退出码
exitCode="$?"
exit $exitCode

② nodejs版本

js
#!/usr/bin/env node
const childProcess = require('child_process');

try {
  childProcess.execSync('npm run lint-staged');
} catch (error) {
  console.log(error.stdout.toString());
  process.exit(1);
}

4. 添加lint-staged命令

package.json文件内添加"lint-staged":"lint-staged""lint-staged"对象

js
"scripts": {
  "lint":"eslint .",
  "lint-staged":"lint-staged"
},
"lint-staged":{
    "*src/*.js":[
    	"npm run lint -- --fix",
	"git add ."
    ]
}
  1. 在项目中添加.eslintrc文件
json
{
  "extends": "eslint-config-egg",
  "rules": { //自己的规则,可以覆盖继承的规则(根据团队约定配置)
    "indent": ["error", 2],
    "no-console":"off",
    "no-unused-vars":"off"
  }
}

步骤三:优化

  1. vscode编辑器设置保存时自动格式化
  2. vscode安装eslint插件,在编码阶段发现错误

配置vscode保存时自动修复eslint

步骤1. 下载eslint插件

image.png

步骤2. 配置保存时自动修复

Ctrl + Shift + p --> 输入 setting 选择 首选项:打开设置(json)image.png

json中加上这行配置

json
"editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
"eslint.run": "onSave"

注意: npm安装的 eslint插件 - 运行在编译阶段 vscode 里面的 eslint 插件 - 运行在编码阶段

npm 安装的 prettier 插件 - 运行在代码启动时(可以理解为编译阶段) vscode里面的prettier插件 - 运行在IDE保存阶段

要想开发时有代码检查,需要vscode安装eslint插件

相关文章

husky钩子的一些用法husky使用总结一文带你彻底学会 Git Hooks 配置husky官方文档自定义 Git - Git 钩子(官方)