blog icon indicating copy to clipboard operation
blog copied to clipboard

手牵手使用Husky & Nodejs自定义你的Git钩子

Open kevinuyoung opened this issue 6 years ago • 9 comments

手牵手使用Husky & Nodejs自定义你的Git钩子

概述

和其它版本控制系统一样,Git能在一些特定的重要动作发生时触发自定义脚本。Git钩子最常见的使用场景包括进行提交规范审核,代码规范审核,进行自动化部署等。在本篇文章中,我会用一个例子详细介绍使用Husky & nodeJs脚本配合使用Git常用钩子。

Git钩子在何方

当你用git init初始化一个项目的时候,钩子都被存储在当前项目下.git/hooks子目录中。Git默认会在这个目录中放置一些示例脚本,这些脚本大多是shell和PERL语言,但是你可以使用任何脚本语言。如果你想启用它们,需要将 .sample 这个后缀删除。

├── applypatch-msg.sample
├── commit-msg.sample
├── post-update.sample
├── pre-applypatch.sample
├── pre-commit.sample
├── pre-push.sample
├── pre-rebase.sample
├── pre-receive.sample
├── prepare-commit-msg.sample
└── update.sample

背景

在多人协作的项目中,代码风格统一、代码提交信息的说明等重要性不言而喻。因此,在本篇文章中,我会介绍如下两个Git钩子:

  1. pre-commit
  2. commit-msg

NodeJs安装

安装过程就不详细介绍了,可以去官网直接下载

husky介绍

husky继承了Git下所有的钩子,在触发钩子的时候,husky可以阻止不合法的commit,push等等。

npm install husky --save-dev

Eslint

  1. 介绍 ESLint 是一个开源的 JavaScript 代码检查工具,由 Nicholas C. Zakas 于2013年6月创建。在小项目或者小团队中,成员间可以拟定一份约定的代码规范并依靠自觉和人工检查去遵守这个规范来达到这个目的。但是随着规模的扩大的新成员的加入,这个问题就不能只靠自觉和人工来解决,我们需要强制和自动化来确保每个成员提交的代码都是符合规范的。

  2. 全局安装

npm install -g eslint

项目搭建

github地址

  • 新建一个项目,git-hooksDemo, 安装所需依赖,依次执行以下命令。
// 进入目录
cd git-hooksDemo
// 初始化git & package.json
git init 
npm init
npm install husky eslint --save-dev

项目结构如下

├── node_modules
├── package.json
├── src
│   ├── assets
│   │   └── images
│   │       ├── brand.jpg
│   │       └── hello.jpg
│   └── js
│       ├── README.md
│       └── message.js
└── tools
    ├── checkcommitmsg.js
    └── checkfilesize.js
  • 初始化Eslint配置,详细配置可以参考Eslint config,eslint初始化时,你可以根据自身项目的需求选择对应的代码风格。目前,我们项目主要用的最多的还是Standard标准
node_modules/.bin/eslint --init

//如果是全局安装eslint 可以直接键入命令
eslint --init

创建pre-commit钩子

客户端钩子,它会在Git键入提交信息前运行。因此,我们可以利用这个时机来做代码风格检查。 查看husky文档 & husky对应Git的钩子,可以清楚看到 precommit对应pre-commit 编辑package.json, 在scripts中加入如下命令:

{
  "scripts": {
    "eslint": "eslint src/**/*.js",
    "precommit": "npm run eslint"
  }
}

当你在终端输入git commit -m "xxx",提交代码的时候, 如果代码不符合Eslint规则,则可能会有如下错误提示:

 11:22  error  Missing semicolon  semi

✖ 1 problem (1 error, 0 warnings)
  1 error, 0 warnings potentially fixable with the `--fix` option.


husky > pre-commit hook failed (add --no-verify to bypass)

当然你可以根据提示 增加参数 git commit -m "xxx" --no-verify 绕过验证,强制提交。

已经解决了提交前的Eslint代码检查工作。现在,有一个需求,在提交代码之前,检查项目下所有图片文件大小是否符合要求。我们结合上面所提到的,使用NodeJs脚本实现这个需求。 在package.json文件中,新增脚本命令如下:

{
  "scripts": {
    "checkFileSize": "cd tools && node checkfilesize.js",
    "eslint": "eslint src/**/*.js",
    "precommit": "npm run eslint"
  }
}

终端输入命令git commit -m "xxx", 运行结果如下:

[13:52:15] Starting check all images size...
[13:52:15] Finished check all images size...

 /Users/simon/practice/git-hooksDemo/src/assets/images/01f53a5787a1920000018c1b3c94cb.jpg 大小为: 2.79 MB

 以上1个文件超过默认大小1MB,请检查!


husky > pre-commit hook failed (add --no-verify to bypass)

precommit钩子,会先检查代码合法性,如果通过,再去检查所有图片大小合法性。

截取部分关键代码:

/*
* 打印警告-超过默认1MB大小文件
* git hooks用于指定在git任务执行的特定时间点自动执行的任务的脚本
* 脚本可以是shell脚本、node脚本等,只要脚本返回有效的退出码(exit code),
* 其中0表示成功,>0表示错误。
* 钩子返回值不是0,那么 git commit 命令就会中止执行。
*/
function warningAndLogLargeFile (allAssetsArr) {
  var newArr = checkFileSizeLimit(allAssetsArr);
  var flag = false;
  console.log('[' + getCurrentDate() + '] Finished check all images size...');
  var overSizeLen = 0;
  for (var i = 0, len = newArr.length; i < len; i++) {
    var item = newArr[i];
    if (item.overLimit) {
      flag = true;
      overSizeLen = overSizeLen + 1;
      console.warn('\n', item.name, '大小为:', bytesToSize(item.size));
    }
  }
  if (flag) {
    console.log('\n 以上' + overSizeLen + '个文件超过默认大小1MB,请检查!\n');
    process.exit(1);
  } else {
    console.log('\n 图片大小符合规格, Good job! \n');
    process.exit(0);
  }
}

创建commit-msg钩子

客户端钩子,它会根据Git键入提交信息判断 提交信息是否符合团队规范性

依旧在package.json新增脚本命令:

{
  "scripts": {
    "checkFileSize": "cd tools && node checkfilesize.js",
    "eslint": "eslint src/**/*.js",
    "precommit": "npm run eslint",
    "commitmsg": "cd tools && node checkcommitmsg.js ${GIT_PARAMS}"
  }
}

终端输入命令git commit -m "xxx", 运行结果如下:

 提交代码信息不符合规范,信息中应包含字符"HELLO-".

 例如:08-28版本HELLO- frist commit.


husky > commit-msg hook failed (add --no-verify to bypass)

当你在终端键入git commit -m "HELLO- xxx"的时候,就可以通过该检测。

功能代码如下:

/*
*    功能: git commit时,自动验证提交信息是否符合规范
* 提交规范: 信息中应包含字符"HELLO-",同时提交 当前版本的信息。例如:"08-18 HELLO-某个功能开发"
* https://github.com/typicode/husky/issues/71
* https://github.com/typicode/husky/issues/
* 主要是读取 .git/COMMIT_EDITMSG 这个文件,文件记录了当前commit之后的信息
*/
var fs = require('fs');
var path = require('path');

var gitPath = path.join('../', process.env.GIT_PARAMS);
var commitMsg = fs.readFileSync(gitPath, 'utf-8');

var pattern = /HELLO-/g;

if (!pattern.test(commitMsg)) {
  console.log(' 提交代码信息不符合规范,信息中应包含字符"HELLO-".\n');
  console.log(' 例如:08-28版本HELLO- frist commit.\n');
  process.exit(1);
}
process.exit(0);

最后

pre-commit & commit-msg 两个钩子的用法已经介绍完了。 Git钩子还有很多功能,可以帮助开发者实现更好的功能需求。 感谢您的阅读,文章中的DEMO已经放在 github地址,欢迎提出issue,star是最好的了。2333333

kevinuyoung avatar Sep 27 '17 07:09 kevinuyoung

你们开发环境是windows么?我这边用windows/git bash,eslint报错后git commit依然成功。(git/2.15.1 node/6.10.2 npm/3.10.10)

yidazh avatar Dec 06 '17 02:12 yidazh

@yidazh 用的Mac开发,我把DEMO下载看了下,测试后,eslint报错,commit后没有提交代码噢。如果 加了参数 git commit -m "fx" --no-verify,是可以绕过审查的。

kevinuyoung avatar Dec 07 '17 01:12 kevinuyoung

问题

image

您好!按照我得理解,你输入 git commit -m 'xxxx'得话,会触发pre-commit钩子。也就是按照您说得触发precommit,然后执行后面得eslint检查。

问题1:

写在script里面的,为什么git commit -m 'xxx'会触发,按照我的理解,不应该是npm run precommit触发么,就像npm run build一样,如果是npm run precommit触发,那么和git commit -m有什么关系了,我的最终疑惑是git -commit -m 不能够触发scirpt里面precommit的命令,

猜想

是因为husky,它监听到了git commit -m, 从而不用 npm run precommit命令,也会执行precommit么

"script": {
    "precommit": "npm run eslint"
}

问题2:

image 还是这个问题,您git commit -m 'xxx'。因为pre-commit执行了precommit,但是为什么checkFileSize会被触发,它不是git的钩子哇,我是哪里没理解到么

期望

期望您的回答!万分感谢

yizhengfeng-jj avatar Dec 29 '18 02:12 yizhengfeng-jj

@yizhengfeng-jj 看了下git-hook的原理,试着说下我的理解

当你用git init初始化一个项目的时候,钩子都被存储在当前项目下.git/hooks子目录中

只要执行git commit 就好触发 .git/hooks/pre-commit的执行,不同的git 命令对应不同的里面的文件名称, 里面可以写要执行的相关处理逻辑。husky的作用是把写好的 hooks逻辑加到这个目录下,已npm run -s xxxx 的形式再package.json中去自定义 相关的script脚步

sumaolin avatar Feb 28 '19 10:02 sumaolin

husky 能指定hooks的执行顺序吗,比如我想先执行 commit-msg,再执行pre-commit 中的eslint检查。原因:如果我commit-msg失败了,我还需要再跑一次pre-commit,而 pre-commit 中的eslint检查是比较耗时的。

pandaomeng avatar Aug 19 '19 08:08 pandaomeng

看了 @yizhengfeng-jj 的问题后,我思考了整个的执行流程。

1、首先要知道git在commit、merge、push时会执行git hooks,git的hooks放在项目根目录下的.git/hooks 如果你执行git commit -m 'add .gitignore'那么会调用pre-commit(默认情况下没有该文件pre-commit.sample是git提供的模板),该文件脚本使用Bash Shell书写。 这是git预设的

2、当你安装husky(版本^0.14.3)时,会自动写入.git/hooks/pre-commit文件,在代码53行有这段代码

npm run -s precommit || {
  echo
  echo "husky > pre-commit hook failed (add --no-verify to bypass)"
  exit 1
}

我这里有张截图(图里的node -v不会执行,因为会执行 npm run node -v,我就懒得改了) image 这应该可以解决你的疑问1

疑问2就简单了

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "eslint": "eslint src/**/*.js",
    "checkFileSize": "cd tools && node checkfilesize.js",
    "precommit": "npm run eslint && npm run checkFileSize",
    "commitmsg": "cd tools && node checkcommitmsg.js ${GIT_PARAMS}"
  }

git commit会调用precommit,precommit里执行了eslintcheckFileSize 同样commitmsg也是一个git预设的钩子

现在husky更新到4.3.0了,原理应该是类似。

wind8866 avatar Nov 20 '20 06:11 wind8866

为啥我的文件里打印 GIT_PARAMS 为空?使用了 husky,打印 HUSKY_GIT_PARAMS 也是为空呢?我是在 commit-msg.sh 文件里运行了 node checkcommitmsg.js ${GIT_PARAMS},但是这样拿不到这个变量呢?

Inchill avatar Oct 18 '21 11:10 Inchill

我的版本"husky": "1.3.1", GIT_PARAMS改为HUSKY_GIT_PARAMS有值

ZTrainWilliams avatar Sep 26 '22 02:09 ZTrainWilliams

这是来自QQ邮箱的假期自动回复邮件。您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

Anjing1993 avatar Sep 26 '22 02:09 Anjing1993