环境说明#
版本信息#
- GitLab 版本:13.10.2
如需了解 GitLab 服务器安装方法,请参考 安装文档。本文将详细介绍如何在 GitLab 中添加和配置
pre-receive钩子,并演示如何对git push操作进行限制。
参考文档#
Pre-receive 钩子简介#
什么是 Pre-receive 钩子?#
当我们需要对 Git 提交内容进行校验时,可以使用服务端的 pre-receive 钩子。相比客户端的 pre-commit 钩子,它具有以下优势:
- 集中管理:无需在每个客户端单独配置
- 统一维护:所有规则在服务端统一管理
- 强制执行:无法被客户端绕过
工作原理#
pre-receive钩子会在代码推送时对提交内容进行校验- 如果校验脚本正常退出(exit 0),则允许推送
- 如果校验失败,则拒绝推送操作
钩子类型#
GitLab 服务端支持三种类型的钩子:
pre-receive:推送前执行update:更新时执行post-receive:推送后执行
详细说明请参考 官方文档
Pre-receive 钩子配置#
钩子脚本说明#
pre-receive 钩子脚本具有以下特点:
- 多语言支持:可以使用 shell、Python、Ruby 等任何可执行程序
- 输入参数:从标准输入获取三个参数:
旧版本号、新版本号、分支名称 - 环境变量:提供丰富的内置环境变量供使用
- 权限要求:必须设置正确的执行权限
详细的脚本编写指南请参考 官方文档。
配置全局钩子#
环境准备#
本示例基于 Docker 部署的 GitLab,数据通过持久卷映射保存。
GitLab 支持两种钩子作用域:
- 全局钩子:对所有项目生效
- 项目钩子:仅对特定项目生效
配置步骤#
# 1. 进入 GitLab 配置目录
$ pwd
/application/gitlab
$ ll
total 4
drwxrwxr-x 3 root root 239 Apr 9 16:38 config
drwxr-xr-x 20 root root 4096 Apr 13 10:14 data
drwxr-xr-x 20 root root 326 Apr 9 16:41 logs
# 2. 编辑 GitLab 配置文件
vi config/gitlab.rb
# 取消注释并设置全局钩子目录
gitlab_shell['custom_hooks_dir'] = "/etc/gitlab/hooks"
# 3. 创建钩子目录
mkdir -p config/hooks/pre-receive.d/
# 4. 验证容器内目录映射
docker exec -it gitlab-ce bash -c 'ls /etc/gitlab/hooks'
pre-receive.d
# 5. 重新加载配置(重要:必须执行此步骤)
docker exec -it gitlab-ce bash -c 'gitlab-ctl reconfigure'
# 6. 重启容器使配置生效
docker restart gitlab-ce

示例:提交信息长度验证#
脚本功能#
创建一个验证提交信息长度的钩子,确保每次提交的描述信息足够详细。
脚本内容#
# 创建并编辑钩子脚本
cat config/hooks/pre-receive.d/commit-length
#!/bin/bash
# Pre-receive 钩子:验证提交信息长度
# 调试模式:取消注释下行可启用调试
#set -x
LG=18 # 最小字节长度要求
validate_ref()
{
# 获取参数
oldrev=$(git rev-parse $1)
newrev=$(git rev-parse $2)
refname="$3"
# 获取提交列表
commitList=`git rev-list $oldrev..$newrev`
split=($commitList)
# 遍历每个提交
for s in ${split[@]}
do
echo "检查提交: $s"
# 提取提交信息
msg=`git cat-file commit $s | sed '1,/^$/d'`
echo "提交信息: $msg"
# 验证长度
if [ ${#msg} -lt "$LG" ];then
echo "错误:提交信息长度不足 18 个字节"
exit 1
else
echo "通过:提交信息长度符合要求(大于 $LG 字节)"
fi
done
}
fail=""
# 支持命令行和钩子两种运行模式
if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
# 命令行模式
PAGER= validate_ref $2 $3 $1
else
# 钩子模式:从标准输入读取
while read oldrev newrev refname
do
validate_ref $oldrev $newrev $refname
done
fi
# 检查是否有失败
if [ -n "$fail" ]; then
exit $fail
fi
# 设置脚本执行权限
docker exec -it gitlab-ce bash -c 'chmod 777 -R /etc/gitlab/hooks/'
重要说明#
字符编码注意事项:
- 1 个中文字符 = 3 个字节
- 最少需要约 6 个中文字符才能满足 18 字节要求
- 空格也会被计入字节数
- 如需排除空格,可修改脚本中的字符串处理逻辑
权限设置:
- 必须为钩子脚本设置执行权限
- 权限不正确会导致钩子无法生效
验证脚本效果#
方式一:GitLab Web 界面验证

从界面可以看到推送被拒绝,但 Web 界面不会显示详细的错误信息。
方式二:命令行验证
# 克隆测试仓库
cd /tmp
git clone http://gitlab.treesir.pub/gitlab-instance-7c228ebb/Monitoring.git
# 创建测试文件
touch test.md
git add ./*
# 提交一个过短的信息
git commit -m "aaa"
# 尝试推送(会被拒绝)
git push origin master
推送结果:
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 4 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 266 bytes | 266.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: 检查提交: d1d9d8d5cad9ece2e82e4a9252efacb271c4fd0f
remote: 提交信息: aaa
remote: 错误:提交信息长度不足 18 个字节
To http://gitlab.treesir.pub/gitlab-instance-7c228ebb/Monitoring.git
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'http://gitlab.treesir.pub/gitlab-instance-7c228ebb/Monitoring.git'
项目级钩子配置#
应用场景#
有时我们不希望对所有项目都应用相同的限制,而是希望:
- 针对特定项目组设置规则
- 为某些重要项目设置特殊验证
- 在不同项目中实施不同的代码规范
钩子类型对比#
| 钩子类型 | 作用范围 | 配置路径 | 适用场景 |
|---|---|---|---|
| 全局钩子 | 所有项目 | /etc/gitlab/hooks | 统一的代码规范 |
| 项目钩子 | 单个项目 | 项目路径/custom_hooks/ | 特定项目需求 |
下面我们通过实际示例来演示项目级钩子的配置方法。
示例:强制关联 JIRA Issue ID#
应用场景#
当 GitLab 与 JIRA 集成后,我们希望确保每个提交都关联到具体的 JIRA 工单,以便:
- 追踪代码变更的业务背景
- 建立代码与需求的关联关系
- 便于后续的问题排查和版本管理
脚本实现#
# 创建 JIRA Issue ID 验证脚本
cat custom_hooks/pre-receive.d/require-jira-issue
#!/bin/bash
#
# 拒绝不符合提交信息格式要求的推送
# 确保每个提交都关联到 JIRA Issue ID
#
# 示例:此钩子要求提交信息包含格式为 [DT-数字] 的 JIRA Issue ID
# 参考:https://help.github.com/en/enterprise/user/articles/working-with-pre-receive-hooks
set -e
# 空提交的 SHA 值
zero_commit='0000000000000000000000000000000000000000'
# 正则表达式定义
msg0_regex='^DT-[0-9]+ ' # JIRA Issue 格式:DT-123 开头
msg1_regex='^Merge' # 允许 Merge 提交
while read -r oldrev newrev refname; do
# 忽略分支或标签删除操作
[ "$newrev" = "$zero_commit" ] && continue
# 计算新分支或更新分支的提交范围
[ "$oldrev" = "$zero_commit" ] && range="$newrev" || range="$oldrev..$newrev"
# 检查每个新提交
for commit in $(git rev-list "$range" --not --all); do
# 验证提交信息是否符合格式要求
if ! git log --max-count=1 --format=%B $commit | egrep -iq "$msg0_regex|$msg1_regex"; then
echo "错误:提交被拒绝"
echo "错误:提交 $commit 在分支 ${refname#refs/heads/}"
echo "错误:缺少 JIRA Issue ID"
echo "错误:正确格式示例:'DT-123 这是一个提交示例'"
echo "错误:"
echo "错误:请修正提交信息后重新推送"
echo "错误:修改提交信息方法:https://help.github.com/en/articles/changing-a-commit-message"
echo "错误:"
exit 1
fi
done
done
格式要求说明#
支持的提交信息格式:
DT-123 添加用户登录功能✅DT-456 修复数据库连接问题✅Merge branch 'feature' into main✅
不支持的格式:
添加用户登录功能❌(缺少 Issue ID)修复 bug❌(缺少 Issue ID)
定位项目存储路径#
GitLab 存储机制说明#
从 GitLab 某个版本开始,项目在磁盘上以 hash 形式存储,这样做的好处是:
- 提高磁盘 I/O 性能
- 避免文件系统路径长度限制
- 提升大量项目时的访问效率
操作步骤#
步骤 1:创建测试项目

步骤 2:获取项目的 Hash 路径
通过 GitLab 管理界面查看项目信息:



从界面中可以获得项目的 hash 路径:
@hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.git
步骤 3:配置项目钩子
# 进入项目存储目录
cd /application/gitlab/data/git-data/repositories/
cd @hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.git

步骤 4:创建钩子文件
# 创建钩子目录
mkdir -p custom_hooks/pre-receive.d/
# 创建钩子脚本文件
vim custom_hooks/pre-receive.d/require-jira-issue
# 将上面的脚本内容复制到此文件中
# 验证目录结构
tree custom_hooks
custom_hooks/
└── pre-receive.d
└── require-jira-issue
1 directory, 1 file
# 设置执行权限(重要!)
chmod 777 -R ./custom_hooks
注意: 权限设置是必须的,否则钩子将不会生效。
功能测试#
测试步骤#
# 1. 克隆测试项目
git clone http://gitlab.treesir.pub/root/test-commit.git
cd test-commit
# 2. 创建测试文件
touch test.md
git add ./*
# 3. 提交不符合格式的信息
git commit -m "test"
# 4. 尝试推送(将被拒绝)
git push
测试结果#
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 205 bytes | 205.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: 错误:提交被拒绝
remote: 错误:提交 38bcec39ffd4132c495a22af0ed1485fbe8852f4 在分支 master
remote: 错误:缺少 JIRA Issue ID
remote: 错误:正确格式示例:'DT-123 这是一个提交示例'
remote: 错误:
remote: 错误:请修正提交信息后重新推送
remote: 错误:修改提交信息方法:https://help.github.com/en/articles/changing-a-commit-message
remote: 错误:
To http://gitlab.treesir.pub/root/test-commit.git
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'http://gitlab.treesir.pub/root/test-commit.git'
批量配置建议#
当项目数量较多时,逐个配置会比较繁琐。建议使用以下方法:
- 使用 GitLab API:通过 API 查询项目的 hash 路径
- 编写自动化脚本:批量为多个项目添加钩子
- 使用全局钩子:如果规则适用于所有项目,优先考虑全局配置
具体的 API 使用方法和自动化脚本编写需要一定的编程基础。
其他实用钩子#
文件类型限制钩子#
应用场景#
在某些项目中,我们需要限制特定类型文件的提交,例如:
- 防止提交大型二进制文件影响仓库性能
- 避免提交可执行文件带来安全风险
- 确保代码仓库的纯净性
脚本实现#
# 创建文件类型限制脚本
cat custom_hooks/pre-receive.d/block-files
#!/usr/bin/env bash
#
# Pre-receive 钩子:阻止特定类型文件的提交
# 限制文件类型:.gz, .zip, .tgz, .exe
#
# 详细文档:https://help.github.com/enterprise/admin/guides/developer-workflow/managing-pre-receive-hooks-on-the-github-enterprise-appliance/
zero_commit="0000000000000000000000000000000000000000"
# 排除已存在于仓库中的提交
# 这可以防止在启用钩子后创建新分支时出现错误
# 如果不需要此行为,可以将此变量设为空
excludeExisting="--not --all"
while read oldrev newrev refname; do
echo "检查分支: $refname ($oldrev -> $newrev)"
# 忽略分支或标签删除操作
if [ "$newrev" = "$zero_commit" ]; then
continue
fi
# 确定检查范围
if [ "$oldrev" = "$zero_commit" ]; then
# 新分支或标签
span=`git rev-list $newrev $excludeExisting`
else
# 现有分支更新
span=`git rev-list $oldrev..$newrev $excludeExisting`
fi
# 检查每个提交中的文件
for COMMIT in $span; do
for FILE in `git log -1 --name-only --pretty=format:'' $COMMIT`; do
case $FILE in
*.zip|*.gz|*.tgz|*.exe )
echo "错误:检测到受限制的文件类型"
echo "文件:$FILE"
echo "限制类型:*.zip, *.gz, *.tgz, *.exe"
echo "如需提交此类文件,请联系管理员讨论替代方案"
exit 1
;;
esac
done
done
done
exit 0
配置说明#
受限制的文件类型:
.zip- 压缩文件.gz- Gzip 压缩文件.tgz- Tar.gz 压缩文件.exe- Windows 可执行文件
自定义文件类型: 可以根据需要修改脚本中的文件类型匹配规则,例如:
*.zip|*.gz|*.tgz|*.exe|*.dmg|*.pkg|*.deb|*.rpm
这样可以扩展到更多文件类型的限制。
