关于git:在Git项目中使用husky统一管理hooks

通过摸索能够学得更多,而不是指令。

We learn best by discovery, not instruction.

— 《程序员的思维修炼 | 开发认知潜能的九堂课》

写在后面

最近总想尽快调研完husky的我的项目,而后尽快确定我的项目中能够集成的git-hook管理工具。之前曾经探索了pre-commit,再看完这个我的项目,就能够确定计划了。

装置 & 卸载

执行环境

node -v
# v16.4.0
npm -v
# 7.18.1
git --version
# git version 2.24.3 (Apple Git-128)

装置

  1. 装置husky
npm install -D husky
# + husky@6.0.0
  1. 在我的项目中装置husky
npx husky install

执行之后能够再package.json的devDependencies看到husky的配置。并在根目录减少一个.husky/的目录。(之后会解说.husky/_/husky.sh

.husky
├── .gitignore # 疏忽 _ 目录
└── _
    └── husky.sh

查看.git/config,能够看到配置中批改了core.hooksPath指向为.husky。这就是husky 6.0.0的实现原理:替换.git/hooks的目录为自定义目录,且该目录会提交到近程仓库。

$ cat .git/config
[core]
        ...
        hooksPath = .husky

在晓得能够批改core.hooksPath之前,我都是间接创立换一个软连贯来实现该成果的rm .git/hooks && ln -s .husky .git/hooks

  1. 增加husky install到package.json scripts中
#(npm 7.x中才无效)
npm set-script prepare "husky install"

执行之后会在package.json的script中会减少一个prepare。

卸载

npm uninstall husky

6.0.0版本做了很大的批改,且和之前的版本不再兼容。次要应用到了2016年git提供的新个性 core.hooksPath,容许用户指定本人的githooks目录。官网在Why husky has dropped conventional JS config给出了如下的阐明。示意尽管能够间接批改core.hooksPath的指定门路将hooks放到仓库中,但husky提供了更多便捷的性能。

“Why still use husky if there’s core.hooksPath?”

Husky provides some safe guards based on previous versions feedbacks, user-friendly error messages and some additional features. It’s a balance between going full native and a bit of user-friendliness.

增加husky hook

npx husky add .husky/pre-commit "npm test"

执行之后会减少文件.husky/pre-commit(其中的正文是我另外增加的)。

#!/bin/sh
# . 指令为source,示意不产生新的shell,在以后shell下执行命令,共享上下文,相似将两个文件拼接到一起
# 执行 .husky/_/husky.sh
. "$(dirname "$0")/_/husky.sh"

npm test

当然你也能够间接在.husky/创立特定的git hook,毕竟husky只是帮你从新指定了hooksPath。

.husky/_/husky.sh

上面将解说一下.husky/_/husky.sh以学习husky的设计原理。

.husky/_/husky.sh

#!/bin/sh
if [ -z "$husky_skip_init" ]; then
  debug () {
    # 当 HUSKY_DEBUG 存在值为1时,开启debug
    [ "$HUSKY_DEBUG" = "1" ] && echo "husky (debug) - $1"
  }

  readonly hook_name="$(basename "$0")"
  debug "starting $hook_name..."
  # 如果 HUSKY=0,则疏忽hook
  if [ "$HUSKY" = "0" ]; then
    debug "HUSKY env variable is set to 0, skipping hook"
    exit 0
  fi
  # 如果存在~/.huskyrc,则执行
  if [ -f ~/.huskyrc ]; then
    debug "sourcing ~/.huskyrc"
    . ~/.huskyrc
  fi
  # 设置husky_skip_init=1,再次执行hook时不再进入该代码块
  export readonly husky_skip_init=1
  # 再次执行以后hook,其中的$0为hook门路,$@为所有参数
  sh -e "$0" "$@"
  exitCode="$?"

  if [ $exitCode != 0 ]; then
    # 打印hook执行失败信息
    echo "husky - $hook_name hook exited with code $exitCode (error)"
    exit $exitCode
  fi

  exit 0
fi

从前文能够晓得,通过husky生成的hook都会在开始都会通过. "$(dirname "$0")/_/husky.sh"执行一遍该文件,因而该文件会影响这些hook的行为。

#!/bin/sh
# . 指令为source,示意不产生新的shell,在以后shell下执行命令,共享上下文,相似将两个文件拼接到一起
# 执行 .husky/_/husky.sh
. "$(dirname "$0")/_/husky.sh"

npm test

从文件中,咱们能够得悉如下信息:

  1. 设置HUSKY_DEBUG=1开启husky的debug
# 间接在终端设置变量值,也能够在~/.bash_profile中设置为全局环境变量
HUSKY_DEBUG=1
# 执行中指定变量值
HUSKY_DEBUG=1 git commit -m "this is msg"
  1. 设置HUSKY=0跳过hooks的执行,应用办法同HUSKY_DEBUG
  2. 能够通过~/.huskyrc在hook执行前执行一些命令,该文件能够本人创立。

    该文件也是通过. ~/.huskyrc执行的,如果在该文件中exit 0或者exit 1都会间接完结hook,前者示意失常执行,后者示意执行失败。

husky.sh有如下的执行流程:

  1. 执行hook;
  2. 执行husky.sh,此时husky_skip_init=,因而执行判断外部代码。
  3. husky.sh中设置husky_skip_init=1并通过sh -e "$0" "$@"再一次执行以后hook,此时因为husky_skip_init=1跳过判断外部代码,执行hooks残余代码。

从代码上来看,这个有点绕的流程是为了获取到hook执行的后果,并在debug模式下输入。

自定义本地脚本

还是和之前的pre-commit一样,这里须要为开发者提供一个自定义本地hooks的性能。利用husky/_/.husky.sh会执行~/.huskyrc脚本的个性,能够在~/.huskyrc中进行性能拓展。

.huskyrc

#!/bin/bash

readonly CUSTOMIZED_HOOK="$(dirname "$0")/customized/$(basename "$0")"

if [ -f "$CUSTOMIZED_HOOK" ];then
    debug "husky - run customized hook - $CUSTOMIZED_HOOK"
    sh -e $CUSTOMIZED_HOOK $@
    resultCode="$?"

    if [ $resultCode != 0 ]; then
        echo "husky - $CUSTOMIZED_HOOK hook exited with code $resultCode (error)"
        exit $resultCode
    fi
fi

Makefile

install-husky-hooks: 
ifeq ($(wildcard .husky/_/husky.sh),)
    npx husky install
endif
ifeq ($(wildcard ~/.huskyrc),)
    ln -s $(PWD)/.huskyrc ~/.huskyrc
else
    @echo "Fail: ~/.huskyrc already exist, please handle it manually."
endif
ifeq ($(wildcard .gitignore),)
    touch .gitignore
endif
ifeq ($(shell grep -c .husky/customized .husky/.gitignore),0)
    echo "\n# 疏忽.husky/customized中开发人员自定义脚本\n.husky/customized" >> .husky/.gitignore
endif

在非node我的项目中执行husky

mkdir new-project
cd new-project
git init
npm init
npm install -D husky
npx husky install

这样做尽管也没有问题,能够失常的应用husky定义的hook,毕竟hooks也仅仅从新指定了core.hooksPathd.husky/。但一个非node我的项目中蕴含了一个node_module,package.jsonpackage-lock.json总感觉有些奇怪。

vue我的项目中的 yokie

依照Vue Cli的网站阐明。在装置之后,@vue/cli-service 也会装置 yorkie,它会让你在 package.jsongitHooks 字段中不便地指定 Git hook:

{
  "gitHooks": {
    "pre-commit": "lint-staged"
  },
   "lint-staged": {
    "*.{js,vue}": [
      "vue-cli-service lint",
      "git add"
    ]
  }
}

yorkie fork 自 husky并且与后者不兼容。

yokie显示的语法为husky 6.0.0之前的版本的写法,husky 6.0.0之后就没有反对了。

总结

在理解到husky时,我感觉很厉害,能够简略地通过一个配置文件增加git hooks。但因为我自身是做后端开发的,所以多少又有点悲观,毕竟没法很好集成到后端我的项目中。在这次进行学习理解之后,又感觉husky如同也没有做什么,和我之前写的gromithook/git-hooks的思路也基本相同。当然,他的确有解决了提交hook到仓库对立团队开发的hook,也能够取巧地实现开发人员本地自定义hook的性能。但还是没有之前的pre-commit那样给人带来惊喜。

无地方hooks仓库,复用靠拷贝: 之前开发的时候,因为团队中有多个repo,但都须要集成雷同的hook,这时候如果应用husky,那么就须要将这些hook拷贝到每个我的项目 中。如果应用pre-commit,则没有这个懊恼,因为pre-commit是一个近程hook仓库+config文件的配置形式,只须要批改配置文件即可实现多个我的项目应用雷同的hooks。

每个hook只能定义一个: 还有一处是husky令人感到悲观的,每个hook只能定义一个文件,如果要在一个阶段做多种工作,那么久必须将这些工作都写到一个hook中。

综上,如果不须要在多个repo中共享hook,且hook工作比较简单,那么能够思考抉择husky,否则就是用pre-commit。这里我首推pre-commit。

相干文章

举荐

  • 本文章源码 Donespeak/menubook
  • 在Git我的项目中应用pre-commit对立治理hooks
  • 定义全局Git Hooks和自定义Git Hooks
  • 通过Git Hook关联Tapd和Commit

参考

  • husky @typicode.github.io/husky
  • husky @github.com
  • Why husky has dropped conventional JS config 阐明6.0.0版本批改的起因
  • npm-scripts @docs.npmjs.com
  • Husky原理解析及在代码Lint中的利用 解说了husky 6.0.0 之前版本的配置和原理
  • 记一次gitHook带来的思考🤔 同时用到husky和yorkie导致的问题,简略来说就是旧版本的husky和yorkie都会间接批改.git/hooks中脚本
  • yorkie @github.com
  • Vue Cli/cli-service/git-hook

评论

《“关于git:在Git项目中使用husky统一管理hooks”》 有 1 条评论

  1. 周萌萌 的头像
    周萌萌

    写的不错,理解了husky的原理

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理