前言

最近由于团队人员变更,仓库迁移,需要配置某些仓库的目录权限,但是距离上次调整已经过去多年,又是因时间长短而遗忘的知识,故今日进行文章记录(温故而知新)

PS:文章中示例的项目仓库名称 wind-pro ,请根据实际情况更换正确的项目仓库名称,用户权限使用root

场景说明:

  • 核心开发人员:什么目录都能提交。
  • 业务开发人员:只能提交这 3 个目录:

目录示例:

wind-pro-java/src/main/java/com/windseeker/apis/business/
wind-pro-vue/src/views/business/
wind-pro-react/src/views/business/

业务开发如果提交其他目录,GitLab 服务器直接拒绝 push。


配置及使用

第 1 步:登录 GitLab 服务器

ssh root@GitLab服务器IP

第 2 步:创建核心开发人员名单

mkdir -p /opt/wind-git-hooks

cat > /opt/wind-git-hooks/core-developers.txt <<'EOF'
Lehman
zhangsan
lisi
EOF

把里面的 Lehmanzhangsanlisi 换成核心开发人员的 GitLab 用户名。

注意:这里填的是 GitLab 登录用户名,不是姓名。

然后设置权限:

chown root:git /opt/wind-git-hooks/core-developers.txt
chmod 755 /opt/wind-git-hooks/core-developers.txt

第 3 步:获取项目仓库信息

下面两种方式二选一

1.管理员直接获取

进入 GitLab 管理后台:

Admin Area -> Overview -> Projects -> wind-pro

记录两个值:

- Storage name,通常是: default
- Relative path,类似:@hashed/xx/xx/xxxxxxxxxxxxxx.git

2.命令行获取

执行:

gitlab-rails runner "p=Project.find_by_full_path('product/wind-pro'); puts p.repository_storage; puts p.disk_path"

这里的 product/wind-pro 要换成 GitLab 上真实的项目路径。

比如浏览器地址是:

https://gitlab.xxx.com/product/wind-pro

那就是:

product/wind-pro

命令执行后会输出两行,类似:

default
@hashed/xx/xx/xxxxxxxxxxxxxx

最终获取这两个值:

第一行:default
第二行:@hashed/xx/xx/xxxxxxxxxxxxxx.git

第 4 步:创建拦截脚本

执行:

mkdir -p /opt/wind-git-hooks/custom_hooks

cat > /opt/wind-git-hooks/custom_hooks/pre-receive <<'EOF'
#!/usr/bin/env bash

# 可开启日志监控
#echo "DEBUG USER=$GL_USERNAME" >> /tmp/git-hook-debug.log
#env >> /tmp/git-hook-env.log

set -euo pipefail

ZERO="0000000000000000000000000000000000000000"
EMPTY_TREE="4b825dc642cb6eb9a060e54bf8d69288fbee4904"
CORE_USERS_FILE="/opt/wind-git-hooks/core-developers.txt"

is_core_user() {
  local user="${GL_USERNAME:-}"
  [ -n "$user" ] && grep -Fxiq "$user" "$CORE_USERS_FILE"
}

is_allowed_path() {
  case "$1" in
    wind-pro-java/src/main/java/com/windseeker/apis/business/*) return 0 ;;
    wind-pro-vue/src/views/business/*) return 0 ;;
    wind-pro-react/src/views/business/*) return 0 ;;
    *) return 1 ;;
  esac
}

changed_paths() {
  local oldrev="$1"
  local newrev="$2"
  local base

  if [ "$oldrev" = "$ZERO" ]; then
    base="$EMPTY_TREE"
  else
    base="$oldrev"
  fi

  git diff --name-status -M "$base" "$newrev" | while IFS=$'\t' read -r status path1 path2 rest; do
    case "$status" in
      R*|C*)
        echo "$path1"
        echo "$path2"
        ;;
      *)
        echo "$path1"
        ;;
    esac
  done
}

if is_core_user; then
  exit 0
fi

bad_paths="$(mktemp)"
trap 'rm -f "$bad_paths"' EXIT

while read -r oldrev newrev refname; do
  [ "$newrev" = "$ZERO" ] && continue

  if [[ "$refname" == refs/tags/* ]]; then
    echo "GL-HOOK-ERR: 普通业务开发不允许创建或修改 tag,请联系核心开发人员。"
    exit 1
  fi

  while IFS= read -r path; do
    [ -z "$path" ] && continue

    if ! is_allowed_path "$path"; then
      echo "$path" >> "$bad_paths"
    fi
  done < <(changed_paths "$oldrev" "$newrev")
done

if [ -s "$bad_paths" ]; then
  echo "GL-HOOK-ERR: Push 被拒绝。"
  echo "GL-HOOK-ERR: 业务开发只能修改以下 business 目录:"
  echo "GL-HOOK-ERR: - wind-pro-java/src/main/java/com/windseeker/apis/business/**"
  echo "GL-HOOK-ERR: - wind-pro-vue/src/views/business/**"
  echo "GL-HOOK-ERR: - wind-pro-react/src/views/business/**"
  echo "GL-HOOK-ERR:"
  echo "GL-HOOK-ERR: 本次提交包含以下非业务目录文件:"
  sort -u "$bad_paths" | sed 's/^/GL-HOOK-ERR: - /'
  echo "GL-HOOK-ERR:"
  echo "GL-HOOK-ERR: 如确实需要修改这些文件,请联系核心开发人员二次确认。"
  exit 1
fi

exit 0
EOF

设置权限:

chmod 755 /opt/wind-git-hooks/custom_hooks/pre-receive
chown -R root:git /opt/wind-git-hooks

第 5 步:安装到 wind-pro 仓库

先打包:

cd /opt/wind-git-hooks
tar -cf custom_hooks.tar custom_hooks

然后执行安装命令。

假设第 3 步查出来的是:

default
@hashed/xx/xx/xxxxxxxxxxxxxx.git

那么执行:

cat custom_hooks.tar | sudo -u git -- /opt/gitlab/embedded/bin/gitaly hooks set \
  --storage default \
  --repository @hashed/xx/xx/xxxxxxxxxxxxxx.git \
  --config /var/opt/gitlab/gitaly/config.toml

重点:

- `--storage` 后面填第 3 步第一行。
- `--repository` 后面填第 3 步第二行,并且确保末尾为 `.git`。

第 6 步:测试

让业务开发人员提交这个文件,应该成功:

wind-pro-vue/src/views/business/test/index.vue

再让他提交这个文件,应该失败:

wind-pro-vue/package.json

失败时会看到类似:

Push 被拒绝。
业务开发只能修改以下 business 目录。
本次提交包含以下非业务目录文件:
- wind-pro-vue/package.json

然后让核心开发人员提交 package.json,应该成功。

维护说明

新增核心开发人员,只改这个文件:

vim /opt/wind-git-hooks/core-developers.txt

不用重启 GitLab。
如果以后新增业务目录,比如再允许:

wind-pro-vue/src/api/business/

就修改:

vim /opt/wind-git-hooks/custom_hooks/pre-receive

is_allowed_path() 里面加一行,然后重新执行第 5 步安装。


多仓库管理(👍️)

如果存在多个仓库需要统一做目录权限控制,那么继续手工一个一个安装 Hook 会越来越难维护。
因此建议将:仓库信息storagerepository项目路径,统一放到一个 JSON 配置文件中。
最终形成:

/opt/wind-git-hooks
├── core-developers.txt
├── repository-data.json
├── install-hooks.sh
├── custom_hooks
│   └── pre-receive
└── custom_hooks.tar
  • 新增仓库
  • 重装 Hook
  • 批量更新 Hook

上述操作以后都只需要修改配置文件并重新执行安装脚本即可。


0. 准备就绪

请参考:配置及使用 下的 前四步


1. 创建 repository-data.json

vim /opt/wind-git-hooks/repository-data.json

示例:

[
  {
    "project": "study/wind-pro",
    "storage": "default",
    "repository": "@hashed/ab/cd/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.git"
  },
  {
    "project": "product/order-center",
    "storage": "default",
    "repository": "@hashed/ab/cd/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.git"
  },
  {
    "project": "product/user-center",
    "storage": "default",
    "repository": "@hashed/ef/gh/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.git"
  }
]

字段说明:

字段说明
projectGitLab 项目路径
storageGitLab Storage name
repositoryGitLab Relative path

2. 安装 jq

因为脚本需要读取 JSON,我们安装jq。

sudo apt install jq -y


3. 创建批量安装脚本

执行:

vim /opt/wind-git-hooks/install-hooks.sh

脚本内容:

#!/usr/bin/env bash
set -euo pipefail

HOOK_DIR="/opt/wind-git-hooks"
CONFIG="/var/opt/gitlab/gitaly/config.toml"
JSON_FILE="$HOOK_DIR/repository-data.json"
TAR_FILE="$HOOK_DIR/custom_hooks.tar"

if ! command -v jq >/dev/null 2>&1; then
  echo "请先安装 jq:sudo apt install jq -y"
  exit 1
fi

if [ ! -f "$JSON_FILE" ]; then
  echo "repository-data.json 不存在"
  exit 1
fi

cd "$HOOK_DIR"

echo "正在打包 hooks..."
tar -cf "$TAR_FILE" custom_hooks

COUNT=$(jq length "$JSON_FILE")

echo "发现 $COUNT 个仓库"
echo

for ((i=0; i<COUNT; i++)); do

  project=$(jq -r ".[$i].project" "$JSON_FILE")
  storage=$(jq -r ".[$i].storage" "$JSON_FILE")
  repository=$(jq -r ".[$i].repository" "$JSON_FILE")

  echo "======================================"
  echo "项目: $project"
  echo "storage: $storage"
  echo "repository: $repository"
  echo "======================================"

  cat "$TAR_FILE" | sudo -u git -- /opt/gitlab/embedded/bin/gitaly hooks set \
    --storage "$storage" \
    --repository "$repository" \
    --config "$CONFIG"

  echo "Hook 安装完成: $project"
  echo
done

echo "全部仓库 Hook 安装完成"

设置权限:

chmod +x /opt/wind-git-hooks/install-hooks.sh

4. 批量安装 Hook

执行:

/opt/wind-git-hooks/install-hooks.sh

脚本会自动:

  • 打包 Hook
  • 遍历所有仓库
  • 自动安装
  • 输出安装日志

这样以后新增仓库,只需要修改 repository-data.json, 新增核心开发人员,只需要修改:core-developers.txt


这套方案的效果就是:普通业务开发人员在本地可以随便改,但只要 push 到 GitLab,服务器会自动检查,不符合目录规则就直接拒绝。

Last modification:May 15, 2026
如果觉得我的文章对你有用,您可以给博主买一杯果汁,谢谢!