本文目录导读:

Git 钩子脚本是实现发布门禁检查的常用方式,通常部署在服务端(如远程仓库)的 pre-receive 或 update 钩子中,下面详细说明如何编写和配置这些脚本。
核心原理
服务端钩子的工作流程:
pre-receive:在引用更新前执行,接收所有待更新的引用列表update:每个引用单独执行一次,参数更清晰
钩子脚本通过退出状态码控制门禁:
- 返回 0:允许推送
- 返回 非0:拒绝推送
常用门禁检查项
1 分支命名规范
#!/bin/bash
# pre-receive 钩子示例
# 定义允许的分支模式
BRANCH_PATTERN="^(feature|bugfix|hotfix|release|main|develop)/.*$|^main$|^develop$"
while read oldrev newrev refname; do
# 提取分支名
branch=${refname#refs/heads/}
# 跳过标签
[[ "$refname" == *refs/tags/* ]] && continue
if [[ ! "$branch" =~ $BRANCH_PATTERN ]]; then
echo "错误: 分支名 '$branch' 不符合规范"
echo "允许的分支格式: feature/*, bugfix/*, hotfix/*, release/*"
exit 1
fi
done
exit 0
2 Commit 信息格式检查
# 检查 commit message 格式
check_commit_message() {
local commit_msg=$(git log --format=%B -1 "$1")
# 示例规则:必须以 JIRA 编号开头
if [[ ! "$commit_msg" =~ ^[A-Z]+-[0-9]+ ]]; then
echo "错误: Commit 必须以 JIRA 编号开头 (如: PROJ-1234 ...)"
return 1
fi
# 检查长度
local first_line=$(echo "$commit_msg" | head -1)
if [[ ${#first_line} -gt 72 ]]; then
echo "错误: Commit 首行不能超过72个字符"
return 1
fi
return 0
}
3 代码质量检查
check_code_quality() {
local newrev="$1"
# 获取新增/修改的文件
local files=$(git diff --name-only --diff-filter=AM "$2" "$newrev" | grep '\.py$\|\.js$\|\.java$')
# 运行 linter
for file in $files; do
case "$file" in
*.py)
git show "$newrev:$file" | python -m pyflakes -
if [[ $? -ne 0 ]]; then
echo "错误: Python 文件 $file 存在语法或风格问题"
exit 1
fi
;;
*.js)
git show "$newrev:$file" | npx eslint --stdin --
if [[ $? -ne 0 ]]; then
echo "错误: JS 文件 $file 存在语法或风格问题"
exit 1
fi
;;
esac
done
}
4 安全检查(禁止敏感信息)
check_security() {
local newrev="$1"
# 扫描新增的文件
local files=$(git diff --name-only --diff-filter=AM "$2" "$newrev")
# 敏感信息模式
local patterns=(
'password\s*=\s*['"'"'"]'
'api_key\s*=\s*['"'"'"]'
'AKIA[0-9A-Z]{16}' # AWS key 模式
'-----BEGIN RSA PRIVATE KEY-----'
)
for file in $files; do
local content=$(git show "$newrev:$file" 2>/dev/null)
for pattern in "${patterns[@]}"; do
if echo "$content" | grep -qiE "$pattern"; then
echo "错误: 文件 $file 可能包含敏感信息"
exit 1
fi
done
done
}
完整的 pre-receive 脚本
#!/bin/bash
# .git/hooks/pre-receive
set -e
RED='\033[0;31m'
NC='\033[0m'
echo "开始执行发布门禁检查..."
while read oldrev newrev refname; do
echo "检查: $refname ($oldrev -> $newrev)"
# 跳过删除分支
if [[ "$newrev" == "0000000000000000000000000000000000000000" ]]; then
continue
fi
# 1. 分支命名检查
branch=${refname#refs/heads/}
if [[ "$refname" != *refs/tags/* ]]; then
if [[ ! "$branch" =~ ^(main|develop|release/.+)$ ]]; then
echo -e "${RED}错误: 不允许直接推送到分支 '$branch'${NC}"
exit 1
fi
fi
# 2. 获取所有新 commit
if [[ "$oldrev" == "0000000000000000000000000000000000000000" ]]; then
# 新分支
commits=$(git rev-list "$newrev" --not --all)
else
commits=$(git rev-list "$oldrev..$newrev" 2>/dev/null)
fi
# 3. 对每个 commit 进行检查
for commit in $commits; do
# 检查 commit message
commit_msg=$(git log --format=%B -1 "$commit")
if [[ ! "$commit_msg" =~ ^(feat|fix|docs|style|refactor|test|chore)\(.*\): ]]; then
echo -e "${RED}错误: Commit $commit 格式不符合 Conventional Commits${NC}"
exit 1
fi
# 检查是否包含大文件
files_modified=$(git diff-tree --no-commit-id -r --name-only "$commit")
# 限制单个文件大小 (5MB)
for file in $files_modified; do
file_size=$(git show "$commit:$file" | wc -c)
if [[ $file_size -gt 5242880 ]]; then
echo -e "${RED}错误: 文件 $file 超过 5MB 限制${NC}"
exit 1
fi
done
done
# 4. 合并检查(针对合并到主分支)
if [[ "$branch" == "main" || "$branch" == "master" ]]; then
# 检查是否有未验证的 merge 提交
merge_commits=$(git rev-list --merges "$oldrev..$newrev")
if [[ -z "$merge_commits" ]]; then
echo -e "${RED}错误: 推送到主分支必须通过 MR/PR 合并${NC}"
exit 1
fi
fi
done
echo "所有门禁检查通过!"
exit 0
部署与配置
1 部署钩子
# 在服务端仓库设置 cd /path/to/your/repo.git # 创建 hooks 目录 mkdir -p hooks # 复制脚本(使用规范名称) cp /path/to/your/pre-receive-script hooks/pre-receive # 添加执行权限 chmod +x hooks/pre-receive chown -R git:git hooks
2 GitLab/GitHub 配置(替代方案)
如果使用 GitLab,可以用 CI 替代钩子:
# .gitlab-ci.yml
stages:
- gate
release_gate:
stage: gate
script:
- ./scripts/check_release_gates.sh
only:
- main
- tags
最佳实践
1 性能优化
# 批量检查而不是逐个
check_commits_batch() {
local oldrev="$1"
local newrev="$2"
# 一次性获取所有 commit 信息
git log --format="%H %s" "$oldrev..$newrev" |
while read hash subject; do
# 检查逻辑
done
}
2 错误处理
# 友好的错误信息
print_error() {
local message="$1"
local commit="$2"
echo "========================================"
echo "🚫 门禁检查失败"
echo "提交: ${commit:0:8}"
echo "错误: $message"
echo "========================================"
}
3 测试你的钩子
# 本地测试 cd /tmp/test-repo git push origin feature/test-branch # 观察钩子输出 # 绕过测试(不推荐生产环境使用) git push -o ci.skip origin feature/test-branch
注意事项
- 性能:钩子脚本会在推送时执行,避免进行耗时操作(如编译)
- 环境:确保服务端有脚本所需的工具(linters, formatters 等)
- 兼容性:考虑脚本的兼容性和幂等性
- 备份:修改前备份原钩子脚本
- 监控:记录钩子执行日志以便排查问题
通过合理配置 Git 钩子,可以在代码准入环节自动执行质量检查,确保只有符合规范的代码才能进入受保护分支。