跳过正文
  1. 博客文章/

GitLab Pre-receive 钩子的配置与使用指南

·968 字·5 分钟·
DevOps Gitlab Webhook Shell
Zayn
作者
Zayn
专注 Kubernetes、CI/CD、可观测性等云原生技术栈,记录生产环境中的实战经验与踩坑复盘。
目录

环境说明
#

版本信息
#

  • 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

image-20210413110811283

示例:提交信息长度验证
#

脚本功能
#

创建一个验证提交信息长度的钩子,确保每次提交的描述信息足够详细。

脚本内容
#

# 创建并编辑钩子脚本
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 界面验证

image-20210413112643927

从界面可以看到推送被拒绝,但 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:创建测试项目

image-20210413114851496

步骤 2:获取项目的 Hash 路径

通过 GitLab 管理界面查看项目信息:

image-20210413114933852

image-20210413115002149
image-20210413115025924

从界面中可以获得项目的 hash 路径:@hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.git

步骤 3:配置项目钩子

# 进入项目存储目录
cd /application/gitlab/data/git-data/repositories/
cd @hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.git

image-20210413115213400

步骤 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'

批量配置建议
#

当项目数量较多时,逐个配置会比较繁琐。建议使用以下方法:

  1. 使用 GitLab API:通过 API 查询项目的 hash 路径
  2. 编写自动化脚本:批量为多个项目添加钩子
  3. 使用全局钩子:如果规则适用于所有项目,优先考虑全局配置

具体的 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

这样可以扩展到更多文件类型的限制。

相关文章

Gitlab 和 jira 之间的集成
·63 字·1 分钟
DevOps Jira Gitlab
企业级 GitLab 平台部署与运维完整指南
·3964 字·19 分钟
Docker Docker Compose DevOps Gitlab Docker Devops Git CI/CD
企业级 Jenkins CI/CD 平台部署与配置完整指南
·4484 字·22 分钟
DevOps Jenkins CI/CD Devops Automation Pipeline