【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/*),只需修改脚本顶部的配置数组即可。

相关推荐
Elastic 中国社区官方博客2 小时前
Elasticsearch:2025年的企业搜索 - 是否需要进行抓取?
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
Dxy12393102162 小时前
ES批量写入数据:从兼容旧版到适配ES8的最佳实践
大数据·elasticsearch
啃火龙果的兔子3 小时前
vscode中的git插件
git·vscode·elasticsearch
Elastic 中国社区官方博客13 小时前
Elasticsearch:圣诞晚餐 BBQ - 图像识别
大数据·数据库·elasticsearch·搜索引擎·ai·全文检索
努力成为一个程序猿.13 小时前
1.ElasticSearch单节点部署
大数据·elasticsearch·搜索引擎
云和数据.ChenGuang18 小时前
git commit复合指令
大数据·git·elasticsearch
Elasticsearch19 小时前
Elasticsearch:圣诞晚餐 BBQ
elasticsearch
叮咚侠21 小时前
Ubuntu 24.04.3 LTS系统中Elasticsearch 8.14.0+kibana 8.14.0集群部署搭建
大数据·ubuntu·elasticsearch·搜索引擎·集群·kibana
Elasticsearch1 天前
Elasticsearch:2025年的企业搜索 - 是否需要进行抓取?
elasticsearch