|
| 1 | +#!/bin/bash |
| 2 | +# GitHub Actions merge-queue fast path. Companion to |
| 3 | +# .buildkite/should_skip_merge_queue.sh, applying the same reasoning to the |
| 4 | +# GitHub-hosted checks: the unit-test matrix (.github/workflows/ci.yml) and the |
| 5 | +# doc build (.github/workflows/docs.yml). |
| 6 | +# |
| 7 | +# Exit 0 => the merge group is provably identical to an already-green PR head, |
| 8 | +# so this workflow's real jobs can be skipped and a trivial gate job |
| 9 | +# reports the required check green. |
| 10 | +# Exit non-zero => run the real jobs. |
| 11 | +# |
| 12 | +# Usage: should_skip_merge_queue.sh <check-name-prefix> |
| 13 | +# <check-name-prefix> selects which of the PR's checks must already be green |
| 14 | +# for this workflow, e.g. "ci " for the ci matrix or "docbuild" for the docs. |
| 15 | +# |
| 16 | +# All of the following must hold to skip. ANY uncertainty (not a merge-queue |
| 17 | +# build, unparseable ref, failed fetch/API call, missing or non-green check) |
| 18 | +# exits non-zero and runs the full jobs, so a wrong guess can never merge |
| 19 | +# untested code: |
| 20 | +# |
| 21 | +# 1. This is a GitHub merge-queue build: GITHUB_EVENT_NAME == "merge_group" |
| 22 | +# and the head ref has the form |
| 23 | +# gh-readonly-queue/main/pr-<pr_number>-<base_sha> |
| 24 | +# |
| 25 | +# 2. <base_sha> (from the merge_group payload) is a real commit on `main` |
| 26 | +# (git merge-base --is-ancestor), i.e. the merge group stacks exactly one |
| 27 | +# PR on `main`. Groups combining multiple PRs stack on a synthetic |
| 28 | +# gh-readonly-queue commit that never reaches `main` and fail this check. |
| 29 | +# (Same argument as the Buildkite script; see its header for detail.) |
| 30 | +# |
| 31 | +# 3. The merge group's tree equals the PR head's tree, i.e. the merged code is |
| 32 | +# byte-identical to what the PR already tested. This is the "up to date |
| 33 | +# with origin/main" condition: it holds only when `main` has not advanced |
| 34 | +# past what the PR contains. Compares tree content, so it holds for the |
| 35 | +# squash, rebase, and merge-commit queue methods alike. |
| 36 | +# |
| 37 | +# 4. Every one of the PR's own checks whose name starts with <prefix> already |
| 38 | +# concluded successfully (queried with `gh pr checks`, which reports the |
| 39 | +# checks GitHub shows for the PR head and is agnostic to how Actions |
| 40 | +# associates runs). This is the GitHub analogue of the Buildkite |
| 41 | +# "staged bundle exists" condition: it confirms the PR build actually ran |
| 42 | +# these checks to green on the identical code. |
| 43 | +set -euo pipefail |
| 44 | + |
| 45 | +prefix="${1:?usage: should_skip_merge_queue.sh <check-name-prefix>}" |
| 46 | + |
| 47 | +# (1) merge-queue build + parse the PR number from the head ref. |
| 48 | +[[ "${GITHUB_EVENT_NAME:-}" == "merge_group" ]] || exit 1 |
| 49 | +head_ref="${MERGE_GROUP_HEAD_REF:-}" |
| 50 | +base_sha="${MERGE_GROUP_BASE_SHA:-}" |
| 51 | +[[ "$head_ref" =~ pr-([0-9]+)-[0-9a-fA-F]+$ ]] || exit 1 |
| 52 | +pr_number="${BASH_REMATCH[1]}" |
| 53 | +[[ -n "$base_sha" ]] || exit 1 |
| 54 | + |
| 55 | +# (2) <base_sha> must be a real commit on `main` (single-PR delta). |
| 56 | +git fetch -q origin main || exit 1 |
| 57 | +git merge-base --is-ancestor "$base_sha" FETCH_HEAD || exit 1 |
| 58 | + |
| 59 | +# (3) merge-group tree must equal the PR head tree (up to date, nothing new to |
| 60 | +# test). HEAD is the merge-group commit checked out by actions/checkout. |
| 61 | +git fetch -q origin "refs/pull/${pr_number}/head" || exit 1 |
| 62 | +pr_head="$(git rev-parse FETCH_HEAD)" || exit 1 |
| 63 | +git diff --quiet "$pr_head" HEAD || exit 1 |
| 64 | + |
| 65 | +# (4) every matching check on the PR already concluded success. `gh pr checks` |
| 66 | +# exits non-zero when checks are pending/failing, so read the JSON (still |
| 67 | +# emitted) and evaluate it ourselves; require >=1 match, all buckets "pass". |
| 68 | +checks="$(gh pr checks "$pr_number" --repo "$GITHUB_REPOSITORY" \ |
| 69 | + --json name,bucket 2>/dev/null || true)" |
| 70 | +[[ -n "$checks" ]] || exit 1 |
| 71 | +echo "$checks" | jq -e --arg p "$prefix" \ |
| 72 | + '[.[] | select(.name | startswith($p))] as $m |
| 73 | + | ($m | length) > 0 and ($m | all(.bucket == "pass"))' >/dev/null || exit 1 |
| 74 | + |
| 75 | +exit 0 |
0 commit comments