【gitlab】通过 `pre-receive` 钩子控制 MR 合并时的分支路径合并方向,阻止未经允许的合并路径

📜 专用钩子脚本

将以下脚本保存为:/gitlab/custom_hooks/pre-receive.d/check-merge-rules,并确保其可执行 (chmod +x)。

bash 复制代码
#!/usr/bin/env bash

# 全局 pre-receive 钩子:仅校验MR合并的分支路径规则
# 注意:分支的推送权限(是否允许直接push)应由GitLab界面上的"分支保护"功能控制。

# --- 规则配置 ---
readonly MAIN_BRANCH="main"
readonly DEVELOP_BRANCH="develop"

# 定义允许合并到 main 的源分支模式
readonly ALLOWED_MERGE_TO_MAIN=("develop" "hotfix/*")
# 定义允许合并到 develop 的源分支模式
readonly ALLOWED_MERGE_TO_DEVELOP=("feature/*" "bugfix/*" "sync-hotfix-*")

# --- 函数:拒绝推送并给出提示 ---
reject_with_message() {
    echo "❌ 合并请求路径校验失败"
    echo "📌 目标分支: $1"
    echo "📌 源分支: $2"
    echo "📋 规则: $3"
    echo "💡 请创建符合上述规则的合并请求。"
    exit 1
}

# --- 主逻辑 ---
while read -r oldrev newrev refname; do
    # 提取目标分支名
    target_branch="${refname#refs/heads/}"

    # 仅对推送到 main 或 develop 的合并操作进行检查
    if [[ "$target_branch" != "$MAIN_BRANCH" && "$target_branch" != "$DEVELOP_BRANCH" ]]; then
        continue # 推送到其他分支,不予干涉
    fi

    # 判断是否为"快进合并"推送(这是GitLab执行MR合并的典型方式)
    if git merge-base --is-ancestor "$oldrev" "$newrev" 2>/dev/null; then
        # 是快进推送,尝试分析是否为合并提交
        # 获取本次推送引入的最后一个提交(最可能是合并提交)
        latest_commit=$(git rev-list -n 1 "$oldrev".."$newrev")

        # 检查这个提交是否是合并提交(有两个或以上父提交)
        parent_count=$(git log --pretty=%P -n 1 "$latest_commit" | wc -w)
        if [[ $parent_count -lt 2 ]]; then
            # 如果不是合并提交,说明是直接推送,这应由分支保护功能阻止
            # 此处我们只记录日志并放行,最终应由GitLab的"允许推送"设置来拦截
            echo "信息: 检测到向 ${target_branch} 的直接推送,分支保护规则将决定是否允许。"
            continue
        fi

        # 是合并提交,提取其第二个父提交(即被合并的源分支的最后提交)
        source_commit=$(git log --pretty=%P -n 1 "$latest_commit" | cut -d' ' -f2)
        # 查找包含此提交的源分支名(取第一个找到的远程分支)
        source_branch=$(git branch -r --contains "$source_commit" 2>/dev/null | head -1 | sed 's|^origin/||')

        if [[ -z "$source_branch" ]]; then
            # 如果通过父提交找不到远程分支,尝试一种备用方案:
            # 检查本次推送引入的提交信息中,是否包含类似 "Merge branch 'feature/xxx'" 的GitLab默认合并信息
            merge_message=$(git log --format=%B -n 1 "$latest_commit")
            if [[ "$merge_message" =~ [Mm]erge\ branch\ \'([^\']+) ]]; then
                source_branch="${BASH_REMATCH[1]}"
                echo "信息: 通过提交信息解析出源分支为: '$source_branch'"
            elif [[ "$merge_message" =~ [Mm]erge\ request\ \!\d+ ]]; then
                # 如果提交信息是类似 "Merge request !123" 格式,说明是GitLab MR合并
                # 这种情况下,我们无法准确获知源分支名,但可以判断这是一个合规的MR合并操作
                # 出于安全,我们对此类"无法识别源分支的MR合并"进行放行,但记录日志。
                # 你也可以选择在此处添加更严格的判断,例如要求必须匹配特定模式。
                echo "警告: 检测到标准的GitLab MR合并提交,但无法解析具体源分支名。予以放行。"
                continue
            else
                # 如果既找不到分支,提交信息也不是标准合并,则拒绝
                reject_with_message "$target_branch" "(未知)" "无法识别合并来源,请确保从有效的远程分支创建MR。"
            fi
        fi

        # 根据目标分支应用对应规则进行匹配
        case "$target_branch" in
            "$MAIN_BRANCH")
                allowed=false
                for pattern in "${ALLOWED_MERGE_TO_MAIN[@]}"; do
                    if [[ "$source_branch" == $pattern ]]; then # 注意这里的 $pattern 不加引号,以启用通配符匹配
                        allowed=true
                        break
                    fi
                done
                if ! $allowed; then
                    reject_with_message "$MAIN_BRANCH" "$source_branch" "仅允许从 ${ALLOWED_MERGE_TO_MAIN[*]} 合并。"
                fi
                ;;

            "$DEVELOP_BRANCH")
                allowed=false
                for pattern in "${ALLOWED_MERGE_TO_DEVELOP[@]}"; do
                    if [[ "$source_branch" == $pattern ]]; then
                        allowed=true
                        break
                    fi
                done
                if ! $allowed; then
                    reject_with_message "$DEVELOP_BRANCH" "$source_branch" "仅允许从 ${ALLOWED_MERGE_TO_DEVELOP[*]} 合并。"
                fi
                ;;
        esac

        echo "信息: 合并路径检查通过 [${source_branch} -> ${target_branch}]。"
    else
        # 非快进推送(如强制推送),拒绝以保护历史
        echo "❌ 拒绝向 ${target_branch} 的强制推送或非快进推送。"
        echo "💡 请通过创建合并请求来合入代码。"
        exit 1
    fi
done

exit 0

🔧 脚本与GitLab功能的分工

此脚本与GitLab Web界面设置形成了明确分工,如下表所示:

控制维度 负责组件 具体设置/行为
能否直接推送 GitLab 分支保护 设置 > 仓库 > 受保护的分支 中,将 maindevelop"允许推送" 设置为 "无一人"
谁能合并MR GitLab 分支保护 在同上位置,设置 "允许合并" 为"维护者"或"开发者+维护者"。
MR的源分支是否符合路径规则 pre-receive 钩子 如上脚本,校验 feature/* -> develop, hotfix/* -> main 等规则。
Hotfix同步回Develop pre-receive 钩子 脚本 ALLOWED_MERGE_TO_DEVELOP 规则已包含 sync-hotfix-* 模式,允许以此类分支为源创建到 develop 的MR。

🚀 部署与验证流程

  1. 保存并配置脚本

    bash 复制代码
    sudo vim /gitlab/custom_hooks/pre-receive.d/check-merge-rules
    # 粘贴脚本内容
    sudo chmod +x /gitlab/custom_hooks/pre-receive.d/check-merge-rules
    sudo chown git:git /gitlab/custom_hooks/pre-receive.d/check-merge-rules
  2. 在GitLab Web界面完成关键设置

    • 进入项目 设置 > 仓库 > 受保护的分支
    • main 分支
      • 允许推送无一人
      • 允许合并维护者 (根据你的团队权限调整)
    • develop 分支
      • 允许推送无一人
      • 允许合并开发者 + 维护者
  3. 测试验证

    • 测试1 (合规MR) :从 feature/test 创建到 develop 的MR,合并时应成功
    • 测试2 (违规MR) :从 feature/test 创建到 main 的MR,合并时钩子应拒绝,并显示清晰的规则错误。
    • 测试3 (直接推送) :尝试 git push origin developGitLab分支保护应直接拒绝 (与钩子无关)。
    • 测试4 (Hotfix同步)
      • hotfix/xxx 合并到 main (应成功)。
      • main 新建分支 sync-hotfix-xxx
      • 创建 sync-hotfix-xxx -> develop 的MR,合并时应成功

💡 核心要点与建议

  • 钩子专注于规则:脚本只验证"从哪合并到哪"的路径,不管"谁能推"或"谁能合并",这些交给界面。
  • 清晰的错误提示:脚本拒绝时会明确给出目标分支、违规的源分支和正确规则,便于团队成员自助排查。
  • 同步Hotfix的正确姿势 :规则中预设了 sync-hotfix-* 作为合规源分支,这要求团队在同步时遵循 "从main创建同步分支,再向develop提MR" 的安全流程。
  • 紧急绕过:若脚本有Bug需紧急合并,管理员可临时重命名钩子文件使其失效,修复后再恢复。

通过以上配置,你将得到一个既严格又清晰的分支合并管控系统。如果需要调整分支命名模式(例如将 bugfix/* 改为 fix/*),只需修改脚本顶部的配置数组即可。

相关推荐
johnnyAndCode16 小时前
Idea 设置GitLab时使用账密,而不是token的配置方法
gitlab·idea
天外飞雨16 小时前
Gitlab使用
gitlab
历程里程碑17 小时前
Linux19 实现shell基本功能
linux·运维·服务器·算法·elasticsearch·搜索引擎·哈希算法
yqd6661 天前
elasticsearch
大数据·elasticsearch·搜索引擎
Elastic 中国社区官方博客1 天前
金融服务公司如何大规模构建上下文智能
大数据·人工智能·elasticsearch·搜索引擎·ai·金融·全文检索
BUTCHER51 天前
GitLab SSH 密钥配置
运维·ssh·gitlab
好好沉淀2 天前
Elasticsearch 中批量更新文档(Update By Query)的标准写法
大数据·elasticsearch
小程故事多_802 天前
Elasticsearch ES 分词与关键词匹配技术方案解析
大数据·人工智能·elasticsearch·搜索引擎·aigc
念丶小宇2 天前
Git常用指令
大数据·git·elasticsearch
明月心9522 天前
GitLab使用
gitlab