fix(git-tools): add admin-gated --no-ci-expected merge assertion to ci-queue-wait
A repository with no CI configured has no sanctioned wrapper merge path: the queue guard fails closed on zero status contexts for purpose=merge (exit 3). That fail-closed default stays correct, because at merge time no-status may also mean CI has not reported yet; but it left CI-less repos unmergeable without the break-glass override, which makes the override a gap rather than an exception. --no-ci-expected reclassifies only the zero-context case for merge as queue-clear, and only when the acting token holds repository admin. The elevation check reads the repository object's permissions.admin: the branch-head and combined-status responses the guard already fetches carry no permissions object at all. The assertion prints its own audit line (purpose, branch, asserting identity, reason) and writes a NO_CI_ASSERTED JSONL record to the existing audit sink; an unauditable pass is refused (exit 70). A non-admin caller is refused with exit 77 (ASSERTION_REFUSED, own text, audited), kept distinct from ASSERTED_NOT_READY's exit 3; an unavailable permissions lookup holds as CANNOT_ASSERT exit 75. --require-status contradicts the flag and is a usage error; a pending or failed context still holds or fails exactly as before; push behavior is unchanged. pr-merge.sh gains a pass-through --no-ci-expected that only forwards the flag to the guard invocation. PowerShell twins are unchanged: no existing test exercises their guard path (pr-merge.ps1 only runs with -SkipQueueGuard; ci-queue-wait.ps1 has no test). Closes #1372
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
# pr-merge.sh - Merge pull requests on Gitea or GitHub
|
||||
# Usage: pr-merge.sh -n PR_NUMBER [-m squash] [-d] [--expect-head SHA] [--co-author-trailers --escalate-to PRINCIPAL]
|
||||
# Usage: pr-merge.sh -n PR_NUMBER [-m squash] [-d] [--expect-head SHA] [--no-ci-expected] [--co-author-trailers --escalate-to PRINCIPAL]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -16,6 +16,7 @@ DRY_RUN=false
|
||||
EXPECT_HEAD=""
|
||||
CO_AUTHOR_TRAILERS=false
|
||||
ESCALATE_TO=""
|
||||
NO_CI_EXPECTED=false
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
@@ -29,6 +30,7 @@ Options:
|
||||
-d, --delete-branch Delete the head branch after merge
|
||||
--dry-run Run metadata/login preflight without merging
|
||||
--expect-head SHA Refuse unless the PR head matches this full commit SHA
|
||||
--no-ci-expected Assert the target repository has no CI: forward --no-ci-expected to the queue guard (requires repository admin)
|
||||
--co-author-trailers Build verified trailers from linked PR commit authors
|
||||
--escalate-to NAME Named principal for an unresolved-author BLOCK
|
||||
-h, --help Show this help message
|
||||
@@ -70,6 +72,10 @@ while [[ $# -gt 0 ]]; do
|
||||
EXPECT_HEAD="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-ci-expected)
|
||||
NO_CI_EXPECTED=true
|
||||
shift
|
||||
;;
|
||||
--co-author-trailers)
|
||||
CO_AUTHOR_TRAILERS=true
|
||||
shift
|
||||
@@ -154,13 +160,18 @@ if [[ "$DRY_RUN" != true ]]; then
|
||||
if [[ -z "$BASE_REPO" ]]; then
|
||||
BASE_REPO="$(get_repo_owner)/$(get_repo_name)"
|
||||
fi
|
||||
"$SCRIPT_DIR/ci-queue-wait.sh" \
|
||||
--purpose merge \
|
||||
-B "$HEAD_BRANCH" \
|
||||
-R "$BASE_REPO" \
|
||||
--sha "$HEAD_SHA" \
|
||||
-t "${MOSAIC_CI_QUEUE_TIMEOUT_SEC:-900}" \
|
||||
guard_args=(
|
||||
--purpose merge
|
||||
-B "$HEAD_BRANCH"
|
||||
-R "$BASE_REPO"
|
||||
--sha "$HEAD_SHA"
|
||||
-t "${MOSAIC_CI_QUEUE_TIMEOUT_SEC:-900}"
|
||||
-i "${MOSAIC_CI_QUEUE_POLL_SEC:-15}"
|
||||
)
|
||||
if [[ "$NO_CI_EXPECTED" == true ]]; then
|
||||
guard_args+=(--no-ci-expected)
|
||||
fi
|
||||
"$SCRIPT_DIR/ci-queue-wait.sh" "${guard_args[@]}"
|
||||
fi
|
||||
|
||||
PLATFORM=$(detect_platform)
|
||||
|
||||
Reference in New Issue
Block a user