Compare commits

...

1 Commits

Author SHA1 Message Date
Molecule AI Dev Engineer A (Kimi) f6c55b53e2 style(scripts): fix ruff E501 line-too-long in gitea-merge-queue.py
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 9s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 6s
CI / Python Lint & Test (pull_request) Successful in 5s
E2E API Smoke Test / detect-changes (pull_request) Successful in 9s
E2E Chat / detect-changes (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 8s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 7s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 6s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
CI / all-required (pull_request) Successful in 4m8s
qa-review / approved (pull_request) Failing after 7s
security-review / approved (pull_request) Failing after 8s
CI / Platform (Go) (pull_request) Successful in 4s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 2s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 3s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m7s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m11s
gate-check-v3 / gate-check (pull_request) Successful in 12s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 4s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 4s
lint-mask-pr-atomicity / lint-mask-pr-atomicity (pull_request) Successful in 1m23s
audit-force-merge / audit (pull_request) Has been skipped
2026-05-24 21:25:16 +00:00
+46 -14
View File
@@ -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,