git hooks原理及应用

757 阅读4分钟

git hooks原理及应用

什么是git hooks:

git hooks是指在执行git特定操作命令时触发的相应的自定义脚本,我们可以在这些钩子中配置各种指定操作,这些钩子大致分为两类客户端钩子和服务端钩子,常用的git hook钩子如下,每个钩子的具体含义就不一一说明了,大家可以查阅相关文档,根据需求找到相应的触发时机的钩子,钩子主要分为以下两种类型:

客户端钩子

  • pre-commit:在commit消息之前执行脚本
  • prepare-commit-msg:可以在这里执行 提交信息的模板,合并提交、压缩提交等
  • commit-msg: 对提交信息进行校验
  • pre-push:被 git push 调用,在 git push 前执行,防止进行推送

服务端钩子

  • pre-receive:处理来自客户端的推送操作时执行
  • update:响应 git push 后,更新起存储时被调用

以上简要列出了常用的几个钩子,git目前绑定了17个钩子,都以脚本的形式存储在.git/hooks目录下,当我们开发完项目commit提交代码时,会依次触发pre-commitprepare-commit-msgcommit-msgpost-commit等hooks。我们可以通过绑定这些钩子在提交过程中用pre-commit约束校验代码规范,用commit-msg钩子进行git提交规范的校验。

其中客户端钩子中所有hooks脚本被存放于git工程的.git/hooks目录下,当我们初始化一个git项目时,会自动生成.git文件夹,hooks文件夹下存放的是一系列git hooks脚本文件,

以下这些都是示例文件,如需个性化定制需求,需要去修改指定钩子的shell脚本,把一个正确命名且可执行的文件放入 Git 目录下的 hooks子目录中,便可以激活该钩子脚本,之后会一直被 Git 调用。

image-20210929184849349

下面是提交拦截钩子`commit-msg.sample脚本内容,该脚本内容默认是不生效的,如果要执行该脚本,需要把.sample后缀去掉,改为可执行脚本即可

#!/bin/sh
test "" = "$(grep '^Signed-off-by: ' "$1" |
	 sort | uniq -c | sed -e '/^[ 	]*1[ 	]/d')" || {
	echo >&2 Duplicate Signed-off-by lines.
	exit 1
}

git hooks约束作用

(1)在项目开发过程中,制定统一的编码规范,统一团队整体的编码风格;

(2)简化开发流程,自动化执行某些命令,如eslint规则校验,不用在提交前单独执行一个eslint命令,可以挂载到git提交前的钩子上;

(3)制定统一的提交规则,按照约定的规范提交代码;

定制git hooks钩子

通过上文讲解,我们知道了需要修改git hooks钩子时需要去修改.git目录下hooks文件夹下的指定钩子,下面我们针对git提交规范约束,来修改下commit-msg脚本文件;

约束规则:

git commit -m 命令后的提交信息需要按照指定规则分类(参考下面表格);

该脚本命令从控制台捕获message消息,未匹配到规范分类中的关键字,则视为本地提交不合格,阻止代码commit

提交类型描述
feat新增feature
fix修复bug
docs仅仅修改了文档,比如README, CHANGELOG, CONTRIBUTE等等;
style仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑;
refactor代码重构,没有加新功能或者修复bug
perf优化相关,比如提升性能、体验
test测试用例,包括单元测试、集成测试等
chore改变构建流程、或者增加依赖库、工具等
revert回滚到上一个版本

修改后的commit-msg脚本

if [[ $CI_COMMIT_MESSAGE ]]; then
    log_message=$CI_COMMIT_MESSAGE
else
        # $1 为传递进来的存储commit message的文件路径
    read log_message < $1
fi

echo "\033[33m The commit msg: \033[0m $log_message"

# "%%:*" shell语法, 删除第一个 : 及其右边的字符串
if [ "${log_message%%:*}" != "feat" -a "${log_message%%:*}" != "fix" -a "${log_message%%:*}" != "style" -a "${log_message%%:*}" != "docs" -a "${log_message%%:*}" != "refactor" -a "${log_message%%:*}" != "build" -a "${log_message%%:*}" != "ci" -a "${log_message%%:*}" != "merge" -a "${log_message%%:*}" != "perf" -a "${log_message%%:*}" != "test" -a "${log_message%%:*}" != "revert" -a "${log_message%%:*}" != "chore" -a "${log_message%%:*}" != "release" ];then
    echo "\033[31m Error: type must be one of [feat,fix,style,docs,refactor,build,ci,merge,perf,test,revert,chore,release] \033[m"
    exit 1
else
    if [ -z "${log_message#*:}" ];then
        echo "\033[31m Error: the content cannot be empty \033[m"
        exit 1
    fi
fi

以上hooks的修改方式适用于开发者本地修改提交触发约束规则钩子,但是在团队开发中,git是一个协同开发的工具,需要约束每一位开发者遵循相同的规则,相同的规则文件只能通过成员间的相互拷贝来实现,如何共享git hooks配置呢?

手动修改钩子脚本让规则约束接入变的更繁琐,接入成本也高,并且shell脚本也不是前端同学比较熟悉的语言,有没有一种自动化的配置能实现该功能呢?接下来我们来了解下市面上比较常用的Git hooks工具husky

husky原理及应用

husky是一个git hooks工具,通过安装husky包,设置相应配置,就可以绑定git hooks钩子。将其安装到指定项目仓库的过程中它会自动在 .git/ 目录下增加相应的钩子执行一系列操作。

husky自动安装配置

下面我们来安装配置下husky工具包,配合lint-stage实现代码校验功能:

// 安装husky
npm install --save-dev husky

新增husky配置

// 在package.json 新增husky配置
"husky": {
  "hooks": {
      // 提交commit时触发
    "pre-commit": "lint-staged",
      // 检测commit的message时触发
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}
"lint-staged": {
  "*.{js,jsx}": [
    "prettier --write",
    "eslint --fix",
    "git add -A"
  ]
}

husky手动添加脚本

npm install husky --save-dev
npx husky install # 初始化

npx husky add .husky/pre-commit "npm run lint" # 表示在 pre-commit 执行 npm test

执行该命令后会在.husky目录下新增一个pre-commit脚本文件,当执行git commit 命令时hooks会优先触发执行pre-commit脚本

pre-commit脚本内容

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run lint

写在最后

通过对上文的学习,我们对git hooks的实现原理有了一定了解,并且实际去在自己项目中应用了市面上比较流行的husky工具包,能够快速的接入一系列的git hooks命令,对于制定团队统一的代码编码规范和git提交规范起到了很大的推进作用;当然,既然了解了原理,我们也可以根据自己团队的应用场景个性化定制开发属于自己内部的约束工具包来替代husky。