在 GitHub 上,允许批准您自己的 PR,但不允许推送到主分支

jos*_*hlf 8 configuration branch github pull-request

我正在尝试使用以下属性配置 GitHub 项目:

  1. main所有用户 - 包括管理员 - 都需要通过拉取请求提交代码,并且不能直接推送到main
  2. 所有用户 - 包括管理员 - 必须等待所有 CI 测试通过才能合并拉取请求
  3. 所有用户的拉取请求都必须获得批准,但管理员可以绕过此要求并合并他们自己的拉取请求

我很难同时满足第一个和第三个要求。具体来说,如果我启用“不允许绕过上述设置”设置,那么管理员就无法绕过拉取请求批准。但是,如果我禁用它,那么管理员可以直接推送到main. 有什么办法可以让我鱼与熊掌兼得吗?

以下是我对main分支的完整分支保护设置:

在此输入图像描述 在此输入图像描述 在此输入图像描述

Ben*_* W. 3

您可以禁用“需要批准”,然后使用 GitHub Actions 工作流程和 GitHub API 检查这两个条件之一是否为真:

  • PR 作者是仓库管理员
  • 有 PR 批准

并将结果作为必要的检查。

使用GitHub CLI,您可以获得用户的权限级别:$user

gh api "repos/{owner}/{repo}/collaborators/$user/permission" --jq '.permission'
Run Code Online (Sandbox Code Playgroud)

检查 PR 批准有点复杂,因为如果没有所需的批准,reviewDecision则不再填充 PR 对象中的字段。相反,我们必须查看一系列评论,并确定是否至少有一个评论者最近的无评论评论是批准的。

对于使用 ID 的评论$id,这看起来像这样:

gh pr view "$id" --json reviews --jq '
    .reviews
    | map(select(.state != "COMMENTED"))
    | reduce .[] as $item ({}; . + {($item.author.login): $item.state})
    | to_entries
    | map(select(.value == "APPROVED"))
    | length > 0
'
Run Code Online (Sandbox Code Playgroud)

true如果至少有一项批准,则会返回此结果。

当打开拉取请求以及提交审核时,必须触发使用这两项检查的工作流程;此外,同步 PR 可能会驳回审核,因此这也应该是一个触发器。

拉取请求触发器可以按基本分支进行过滤,但评论不能,因此我们必须单独添加此条件。

最后一个障碍是,拥有多个触发器 (pull_requestpull_request_review) 会导致多个状态检查,但我们不能同时要求它们;对于非管理员创建的 PR,pull_request检查通过后检查仍然失败pull_request_review

多次检查

为此,工作流程创建了一个单独的第三个检查,这是我们必须在分支保护规则中使用的检查。对于具有最新提交哈希$sha和结果的 PR 分支$state,用于设置状态的 GitHub CLI 命令如下所示

gh api "repos/{owner}/{repo}/statuses/$sha" \
    -f "state=$state" -f context='Non-admin PR approval'
Run Code Online (Sandbox Code Playgroud)

如需其他信息,可以添加 URL,如下面的工作流程所示。所需的检查可以在“非管理员 PR 批准”下找​​到。

在此输入图像描述

即使不满足条件,工作流程也会继续,但如果第一步确定作者是管理员,则会跳过检查 PR 批准的步骤。总体结果是使用STATE环境变量传达的,该变量在最后一步中用于设置状态。

name: Check PR approval for non-admin authors

on:
  # PR into main opened, reopened, or synchronized
  pull_request:
    branches:
      - main

  # When a review is submitted
  pull_request_review:
    types:
      - submitted

jobs:
  checkapproval:
    name: Check PR approval

    runs-on: ubuntu-20.04

    # Event has to be a pull request, or the base branch has to be main
    if: >-
      github.event_name == 'pull_request'
        || github.event.pull_request.base.ref == 'main'

    steps:
      - name: Check if author is repo admin
        env:
          author: ${{ github.event.pull_request.user.login }}
          repo: ${{ github.repository }}
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          perm=$(gh api "repos/$repo/collaborators/$author/permission" \
              --jq '.permission')

          if [[ $perm != 'admin' ]]; then
              echo "Author is not admin; approval required" >&2
          else
              echo "Author is admin; no approval required" >&2

              # Set success state in environment
              echo "STATE=success" >> "$GITHUB_ENV"
          fi

      - name: Check for PR approval
        # Run only if the previous step failed
        if: env.STATE != 'success'
        env:
          prid: ${{ github.event.pull_request.number }}
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          approved=$(gh pr view "$prid" --repo "$GITHUB_REPOSITORY" \
              --json reviews --jq '
                  .reviews
                  | map(select(.state != "COMMENTED"))
                  | reduce .[] as $item (
                      {}; . + {($item.author.login): $item.state}
                  )
                  | to_entries
                  | map(select(.value == "APPROVED"))
                  | length > 0
              ')

          if [[ $approved != 'true' ]]; then
              echo "No PR approval found" >&2

              # Set failure state in environment
              echo "STATE=failure" >> "$GITHUB_ENV"
              exit 0
          fi

          echo "PR approval found" >&2

          # Set success state in environment
          echo "STATE=success" >> "$GITHUB_ENV"

      - name: Set result in separate status
        env:
          GITHUB_TOKEN: ${{ github.token }}
          sha: ${{ github.event.pull_request.head.sha }}
          repo: ${{ github.repository }}
          id: ${{ github.run_id }}
        run: |
          gh api "repos/$repo/statuses/$sha" \
              --raw-field state="$STATE" \
              --raw-field context='Non-admin PR approval' \
              --raw-field target_url="https://github.com/$repo/actions/runs/$id"
Run Code Online (Sandbox Code Playgroud)