hook
hook 翻译为钩子,简单说就是监听某个事件(操作),然后触发自定义逻辑
在 git 中可以监听 commit,push 等操作,在操作之前或之后触发对应的 hook,在 hook 中写自定义的逻辑,比如在提交之前做一些检查,检查未通过不允许提交
下面这些是 git hook 触发的时机
其中蓝色方块是某些操作之后触发,它不会阻塞操作,就是执行一些额外的操作
黄色和红色方块是某些操作之前触发,可以做一些检查,不满足条件会阻塞操作,区别在于黄色方块的操作可以跳过
克隆一个仓库并显示隐藏的文件,可以看到仓库根目录下的 .git 文件夹中有提供了一些 hook 例子,这些 hook 默认不生效,去掉 .sample 后缀才会生效
这些 hook 都是用 shell 命令编写的,查看 pre-commit
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
against=$(git hash-object -t tree /dev/null)
fi
allownonascii=$(git config --type=bool hooks.allownonascii)
exec 1>&2
if [ "$allownonascii" != "true" ] &&
test $(git diff --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
exec git diff-index --check --cached $against --
它会检查提交的文件名里是否有非 ASCII 码,exit 1 表示检查没有通过,exit 0 表示检查通过
这些 hook 我们可以修改逻辑,但是它只会在本地生效,因为 .git 文件夹里的修改不会上传到仓库,为了参与项目的人都能使用 hook,需要借助第三方工具来实现
commitlint + husky
首选需要安装 node.js,官网下载安装即可
在项目根目录安装
npm install --save-dev @commitlint/cli @commitlint/config-conventional
安装 commitlint/cli 和 commitlint/config-conventional 这两个包,并将其添加到 devDependencies(开发阶段的依赖,就是我们在开发过程中需要用的依赖,只在开发阶段起作用的),并将该信息保存到 package.json中
commitlint/cli 是一个命令行工具,用于校验 Git 提交信息的格式是否符合规范。
commitlint/config-conventional 是一个常用的配置文件,它定义了一些常见的 Git 提交信息规范,例如 Angular 的规范。它包含了一些预定义的规则,可以直接使用,也支持自定义规则
新建 commitlint.config.js 文件,增加配置项
module.exports = {
// 继承的规则
extends: ['@commitlint/config-conventional'],
// 定义规则类型
rules: {
// type 类型定义,表示 git 提交的 type 必须在以下类型范围内
'type-enum': [
2,
'always',
[
'feat', // 新功能 feature
'fix', // 修复 bug
'docs', // 文档注释
'style', // 代码格式(不影响代码运行的变动)
'refactor', // 重构(既不增加新功能,也不是修复bug)
'perf', // 性能优化
'test', // 增加测试
'chore', // 构建过程或辅助工具的变动
'revert', // 回退
'build' // 打包
]
],
// subject 大小写不做校验
'subject-case': [0]
}
}
rule由name和配置数组组成,如上面的:‘type-enum:[2, ‘always’, […]]’,数组中第一位为level,可选0,1,2,0为disable,1为warning,2为error,这里的 2 表示错误级别,表示如果提交消息不符合规则,将会以错误级别报错,第二位为应用与否,可选 always|never,always 表示规则必须被应用到每一个提交消息中,第三位该rule的值,这里定义了 git 提交的类型,提交信息的开头必须是上面定义的类型,如
fix: 修复了某个bug
安装 husky
npm install husky --save-dev
在 package.json 中添加 prepare 脚本并运行,创建 .husky 文件夹
npm pkg set scripts.prepare="husky install"
npm run prepare
将一个名为 pre-commit 的 git hook 添加到 .husky 目录中
npx husky add .husky/pre-commit "npm test"
这里的 “npm test” 是 hook 中的指令,生成后可以随意修改,比如检查文件的名称,路径是否合法
添加一个 commit-msg 钩子,它会在每次提交代码时运行 commitlint,以确保提交的消息符合规范
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
修改文件做测试
提交信息的开头包含必须是配置中定义的类型(feat,fix等)才能提交
最后把生成的这些文件提交到仓库,参与项目的人提交时都会受到在 hook 中做检查
使用 hook 实现主仓库和子模块的联动操作
子模块
子模块相当于子仓库,就是把一个 git 仓库作为另一个 git 仓库的子仓库
git submodule add https://gitee.com/xxx/sub1.git
在仓库下执行 git submodule add url(仓库地址),就会添加一个子模块,这时会生成 .gitmodules 和 一个子模块的文件夹,.gitmodules 中记录子模块的路径和仓库地址
[submodule "sub1"]
path = sub1
url = https://gitee.com/xxx/sub1.git
初始化所有的子仓库并更新
git submodule update --init --recursive
修改提交后,在主仓库可以看到子模块后面有个commit hash 值,这对应子模块的某一个版本
当我们克隆一个包含子模块的仓库时,它默认是不生成子模块的内容,需要递归的方式克隆整个项目才行
git clone url --recursive
或者克隆的时候勾选 Recursive
联动
为了让主仓库更新时,子模块也更新,在 .husky 文件夹下添加一个新的 post-merge 这个 hook,使用 git pull 更新后会触发 post-merge,因为 git pull 就是 git fetch 和 git merge 的简写,在 post-merge 里更新子模块
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# git pull 触发 merge 操作,更新所有子仓库
git submodule foreach 'git pull'
exit 0
如果子模块有更新,主仓库没有更新,此时执行 git pull 不会触发 post-merge,因为没有发生 merge 操作
如果需要主仓库切分支时,子模块也切分支,需要添加 post-checkout 这个 hook。
在执行 git clone 和 git checkout 后会触发 post-checkout,在克隆仓库或切分支后,会检查是否有子模块,有就初始化子模块,主仓库切分支后,如果这个分支名称是dev开头,子模块也会切过去
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
## $1是HEAD的前一个SHA值,$2是HEAD的当前SHA值。如果$1和$2相同,表示没有切换分支
if [ "$1" = "$2" ]; then
exit 0
fi
# git clone 也会触发 post-checkout
# 存在子模块且未初始化,则初始化并更新
if [ -f .gitmodules ]; then
if git submodule status | grep -q '^[-]'; then
git submodule update --init --recursive --remote --merge
fi
else
exit 0
fi
branch=$(git branch --show-current)
#echo "branch-name: $branch"
# branch为空跳过
if [ -z "$branch" ]; then
exit 0
fi
#如果分支名以dev开头,子模块也切过去,没有对应分支则创建分支并切过去
if [[ $branch == dev* ]]; then
git submodule foreach "git checkout -b $branch || git checkout $branch"
fi
exit 0
参考
git hook