diff --git a/.gitea/scripts/gitea-merge-queue.py b/.gitea/scripts/gitea-merge-queue.py index 964d8aa26..af06d2170 100644 --- a/.gitea/scripts/gitea-merge-queue.py +++ b/.gitea/scripts/gitea-merge-queue.py @@ -183,7 +183,9 @@ def required_contexts_green( status = latest_statuses.get(context) state = status_state(status or {}) if state != "success": - if pr_labels and _is_tier_low_pending_ok(latest_statuses, context, pr_labels): + if pr_labels and _is_tier_low_pending_ok( + latest_statuses, context, pr_labels + ): continue # tier:low soft-fail: accept pending sop-checklist missing_or_bad.append(f"{context}={state or 'missing'}") return not missing_or_bad, missing_or_bad @@ -213,7 +215,9 @@ def choose_next_queued_issue( if "pull_request" not in issue: continue candidates.append(issue) - candidates.sort(key=lambda issue: (issue.get("created_at") or "", int(issue["number"]))) + candidates.sort( + key=lambda issue: (issue.get("created_at") or "", int(issue["number"])) + ) return candidates[0] if candidates else None @@ -247,7 +251,8 @@ def evaluate_merge_readiness( main_latest = latest_statuses_by_context(main_status.get("statuses") or []) main_ok, main_bad = required_contexts_green(main_latest, push_required_contexts()) if not main_ok: - return MergeDecision(False, "pause", "main required contexts not green: " + ", ".join(main_bad)) + msg = "main required contexts not green: " + ", ".join(main_bad) + return MergeDecision(False, "pause", msg) if not pr_has_current_base: return MergeDecision(False, "update", "PR head does not contain current main") @@ -259,7 +264,8 @@ def evaluate_merge_readiness( latest = latest_statuses_by_context(pr_status.get("statuses") or []) ok, missing_or_bad = required_contexts_green(latest, required_contexts, pr_labels) if not ok: - return MergeDecision(False, "wait", "required contexts not green: " + ", ".join(missing_or_bad)) + msg = "required contexts not green: " + ", ".join(missing_or_bad) + return MergeDecision(False, "wait", msg) return MergeDecision(True, "merge", "ready") @@ -294,7 +300,9 @@ def get_combined_status(sha: str) -> dict: else: all_statuses = [] except (ApiError, urllib.error.URLError, TimeoutError, OSError) as exc: - sys.stderr.write(f"::warning::could not fetch full statuses list for {sha[:8]}: {exc}\n") + sys.stderr.write( + f"::warning::could not fetch full statuses list for {sha[:8]}: {exc}\n" + ) all_statuses = [] # Build latest per context: process combined (ascending→reverse=newest # first), then fill gaps from all_statuses (already newest-first). @@ -345,11 +353,17 @@ def post_comment(pr_number: int, body: str, *, dry_run: bool) -> None: print(f"::notice::comment PR #{pr_number}: {body.splitlines()[0][:160]}") if dry_run: return - api("POST", f"/repos/{OWNER}/{NAME}/issues/{pr_number}/comments", body={"body": body}) + api( + "POST", + f"/repos/{OWNER}/{NAME}/issues/{pr_number}/comments", + body={"body": body}, + ) def update_pull(pr_number: int, *, dry_run: bool) -> None: - print(f"::notice::updating PR #{pr_number} with base branch via style={UPDATE_STYLE}") + print( + f"::notice::updating PR #{pr_number} with base branch via style={UPDATE_STYLE}" + ) if dry_run: return api( @@ -373,7 +387,12 @@ def merge_pull(pr_number: int, *, dry_run: bool) -> None: if dry_run: return try: - api("POST", f"/repos/{OWNER}/{NAME}/pulls/{pr_number}/merge", body=payload, expect_json=False) + api( + "POST", + f"/repos/{OWNER}/{NAME}/pulls/{pr_number}/merge", + body=payload, + expect_json=False, + ) except ApiError as exc: # Re-raise permission-like errors so process_once can skip this PR. # 403 = no push access, 404 = repo/pr not found, 405 = not allowed. @@ -393,7 +412,8 @@ def process_once(*, dry_run: bool = False) -> int: main_latest = latest_statuses_by_context(main_status.get("statuses") or []) main_ok, main_bad = required_contexts_green(main_latest, push_required_contexts()) if not main_ok: - print(f"::notice::queue paused: {WATCH_BRANCH}@{main_sha[:8]} required contexts not green: {', '.join(main_bad)}") + msg = f"queue paused: {WATCH_BRANCH}@{main_sha[:8]} required contexts not green" + print(f"::notice::{msg}: {', '.join(main_bad)}") return 0 issue = choose_next_queued_issue( @@ -411,10 +431,18 @@ def process_once(*, dry_run: bool = False) -> int: print(f"::notice::PR #{pr_number} is not open; skipping") return 0 if pr.get("base", {}).get("ref") != WATCH_BRANCH: - post_comment(pr_number, f"merge-queue: skipped; base branch is not `{WATCH_BRANCH}`.", dry_run=dry_run) + post_comment( + pr_number, + f"merge-queue: skipped; base branch is not `{WATCH_BRANCH}`.", + dry_run=dry_run, + ) return 0 if pr.get("head", {}).get("repo_id") != pr.get("base", {}).get("repo_id"): - post_comment(pr_number, "merge-queue: skipped; fork PRs are not supported by the serialized queue.", dry_run=dry_run) + post_comment( + pr_number, + "merge-queue: skipped; fork PRs are not supported by the serialized queue.", + dry_run=dry_run, + ) return 0 head_sha = pr.get("head", {}).get("sha") @@ -459,13 +487,17 @@ def process_once(*, dry_run: bool = False) -> int: # maintainers know why, then return 0 so this tick is done. # The PR stays in the queue; future ticks can retry after the # permission issue is resolved. - sys.stderr.write(f"::error::merge permission error for PR #{pr_number}: {exc}\n") + sys.stderr.write( + f"::error::merge permission error for PR #{pr_number}: {exc}\n" + ) post_comment( pr_number, ( - "merge-queue: merge failed with HTTP 405 'User not allowed to merge PR'. " + "merge-queue: merge failed with HTTP 405 " + "'User not allowed to merge PR'. " "No available token has Can-merge permission on this repo. " - "Fix: grant Can-merge to a token, or add a maintain/admin collaborator. " + "Fix: grant Can-merge to a token, or add a " + "maintain/admin collaborator. " "Skipping to next queued PR on next tick." ), dry_run=dry_run,