使用Git钩子
Git钩子的基本概念
Git钩子是Git在特定事件发生时自动运行的脚本,它们存储在.git/hooks
目录中。当执行某些Git操作时,如提交代码或推送更改,Git会检查这个目录中是否存在对应的钩子脚本,如果有就会执行它们。钩子分为客户端钩子和服务端钩子两类,客户端钩子由本地操作触发,服务端钩子则在服务器上响应网络操作时运行。
每个钩子脚本都需要是可执行文件,Git会根据文件名来识别它们。默认情况下,.git/hooks
目录包含一些示例脚本,这些脚本以.sample
结尾,要启用它们需要移除.sample
后缀。
客户端钩子
客户端钩子影响的是开发者的本地工作流程,它们可以分为提交工作流钩子、电子邮件工作流钩子和其他客户端钩子。
预提交钩子(pre-commit)
pre-commit
钩子在键入提交信息前运行,它用于检查即将提交的快照。例如,我们可以用它来运行代码风格检查:
#!/bin/sh
# 运行ESLint检查
npm run lint
# 如果lint检查失败,则终止提交
if [ $? -ne 0 ]; then
echo "Lint检查失败,请修复错误后再提交"
exit 1
fi
准备提交信息钩子(prepare-commit-msg)
这个钩子在启动提交信息编辑器之前运行,它允许我们自动生成提交信息模板或修改默认信息。例如:
#!/bin/sh
# 获取当前分支名
BRANCH_NAME=$(git symbolic-ref --short HEAD)
# 如果分支名包含JIRA编号,则添加到提交信息中
if [[ $BRANCH_NAME =~ (PROJ-[0-9]+) ]]; then
echo "[${BASH_REMATCH[1]}] $(cat $1)" > $1
fi
提交信息钩子(commit-msg)
commit-msg
钩子接收一个参数,此参数是包含提交信息的临时文件的路径。我们可以用它来验证提交信息格式:
#!/usr/bin/env node
const fs = require('fs');
const msg = fs.readFileSync(process.argv[2], 'utf8').trim();
const commitRegex = /^(revert: )?(feat|fix|docs|style|refactor|perf|test|chore)\(.+\): .{1,50}/;
if (!commitRegex.test(msg)) {
console.error(`无效的提交信息格式:"${msg}"`);
console.error('提交信息应遵循:类型(范围): 描述');
console.error('例如:feat(login): 添加登录验证');
process.exit(1);
}
服务端钩子
服务端钩子在推送到服务器时执行,它们可以用来实施项目策略。这些钩子运行在服务器上,而不是开发者的机器上。
预接收钩子(pre-receive)
pre-receive
钩子在处理来自客户端的推送操作时最先运行,它可以用来拒绝不符合某些策略的推送。例如,检查提交者邮箱是否为公司邮箱:
#!/bin/sh
while read oldrev newrev refname; do
# 获取推送的所有提交
commits=$(git rev-list $oldrev..$newrev)
for commit in $commits; do
email=$(git show -s --format='%ae' $commit)
if [[ ! "$email" =~ @company\.com$ ]]; then
echo "错误:提交 ${commit} 使用了非公司邮箱 ${email}"
exit 1
fi
done
done
更新钩子(update)
update
钩子类似于pre-receive
,但它为每个要更新的分支运行一次。我们可以用它来确保主分支只能通过合并请求更新:
#!/bin/sh
refname="$1"
oldrev="$2"
newrev="$3"
# 如果是主分支且不是快进更新
if [ "$refname" = "refs/heads/main" ] && [ "$(git merge-base $oldrev $newrev)" != "$oldrev" ]; then
echo "错误:主分支只能通过合并请求更新"
exit 1
fi
实用的钩子示例
自动运行测试
在每次提交前自动运行测试,确保不会提交失败的代码:
#!/bin/sh
# 运行测试
npm test
if [ $? -ne 0 ]; then
echo "测试失败,请修复后再提交"
exit 1
fi
防止提交大文件
防止意外提交大文件到仓库:
#!/usr/bin/env node
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const execSync = require('child_process').execSync;
const files = execSync('git diff --cached --name-only --diff-filter=ACM')
.toString()
.split('\n')
.filter(Boolean);
for (const file of files) {
const size = parseInt(execSync(`git cat-file -s ":${file}"`).toString(), 10);
if (size > MAX_FILE_SIZE) {
console.error(`错误:文件 ${file} 太大 (${size} 字节)`);
console.error('请使用Git LFS或从提交中移除该文件');
process.exit(1);
}
}
自动增加版本号
在每次提交到主分支后自动增加package.json中的版本号:
#!/bin/sh
branch=$(git symbolic-ref --short HEAD)
if [ "$branch" = "main" ]; then
# 增加补丁版本号
npm version patch --no-git-tag-version
# 将版本变更添加到当前提交
git add package.json
git commit --amend --no-edit
fi
钩子的共享与管理
默认情况下,钩子存储在.git/hooks
目录中,这不会被Git跟踪。要在团队中共享钩子,可以考虑以下方法:
-
将钩子存储在项目中:在项目中创建
hooks
目录,然后在README中说明如何手动复制它们到.git/hooks
。 -
使用husky等工具:husky可以让我们在package.json中定义Git钩子:
{
"husky": {
"hooks": {
"pre-commit": "npm run lint",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
- 使用pre-commit框架:创建一个
.pre-commit-config.yaml
文件来定义钩子:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
钩子的调试技巧
调试Git钩子可能会比较困难,因为它们是在Git命令执行过程中运行的。以下是一些调试技巧:
- 输出日志信息:在钩子脚本中添加日志输出:
#!/bin/sh
echo "pre-commit钩子开始运行" >> /tmp/git-hooks.log
date >> /tmp/git-hooks.log
- 手动运行钩子:可以直接执行钩子脚本来测试:
./.git/hooks/pre-commit
- 临时禁用钩子:在调试时可以暂时重命名钩子文件:
mv .git/hooks/pre-commit .git/hooks/pre-commit.disabled
- 使用set -x:在bash脚本中添加
set -x
来显示执行的命令:
#!/bin/sh
set -x # 开始调试输出
npm run lint
set +x # 结束调试输出
钩子的性能考虑
钩子脚本执行会增加Git操作的时间,特别是当它们运行复杂检查或测试时。为了减少对开发流程的影响:
-
只检查暂存的文件:而不是整个项目,使用
git diff --cached
获取暂存的文件。 -
并行运行任务:如果可能,使用工具并行运行检查。
-
缓存结果:对于耗时的操作,可以考虑缓存结果。
-
提供跳过选项:在紧急情况下允许跳过某些检查:
#!/bin/sh
if [ -n "$SKIP_HOOKS" ]; then
echo "跳过钩子检查"
exit 0
fi
使用时可以这样调用:
SKIP_HOOKS=1 git commit -m "紧急修复"
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:重写提交历史