From 4bb48931315c92be0135f8caaf71b2cc84218fe0 Mon Sep 17 00:00:00 2001 From: Kit OC5 Date: Wed, 23 Sep 2026 16:16:15 -0400 Subject: [PATCH] bridge: replace the mirror PR when a GitHub PR is retargeted to another base The mirror kept its creation-time base forever. eternitas #167 was stacked on fix/one-hallway, retargeted to main after #166 merged; ci.yml (pull_request: branches [main]) then silently never ran for it, while unfiltered workflows did. An edited event triggers nothing, so close the stale mirror and open a fresh one on the new base (runs CI at once). An unknown base is left alone, never guessed. Co-Authored-By: Claude Opus 5.5 --- api/tests/test_pr_status_bridge.py | 21 +++++++++++++++++++++ scripts/pr_status_bridge.py | 12 +++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/api/tests/test_pr_status_bridge.py b/api/tests/test_pr_status_bridge.py index c690c14..b7ea418 100644 --- a/api/tests/test_pr_status_bridge.py +++ b/api/tests/test_pr_status_bridge.py @@ -390,3 +390,24 @@ def test_ci_hygiene_posts_under_its_own_context(fake, monkeypatch): bridge.post_ci_hygiene("windy-chat", SHA, "main", True) # the compute-guard status with the same description must not suppress it assert [(p["context"], p["description"]) for p in f.posted] == [("windy-git/ci-hygiene", "WARN 1")] + + +def test_retargeted_pr_gets_a_fresh_mirror_on_the_new_base(fake): + # eternitas #167: stacked on fix/one-hallway, retargeted to main on GitHub. + gh = [{"number": 167, "title": "feat", "html_url": "u", + "head": {"ref": "feat/x", "sha": SHA, "repo": {"full_name": f"{bridge.GH_OWNER}/eternitas"}}, + "base": {"ref": "main"}}] + wg = [{"number": 9, "title": "[GH#167] feat", "base": {"ref": "fix/one-hallway"}}] + f = fake(gh_prs=gh, wg_prs=wg) + assert bridge.sync_prs("eternitas") == [SHA] + assert f.closed == [f"/repos/{bridge.WG_OWNER}/eternitas/pulls/9"] + assert [(o["base"], o["head"]) for o in f.opened] == [("main", "feat/x")] + + +def test_unchanged_base_leaves_the_mirror_alone(fake): + gh = [{"number": 5, "title": "t", "html_url": "u", + "head": {"ref": "b", "sha": SHA, "repo": {"full_name": f"{bridge.GH_OWNER}/windy-chat"}}, + "base": {"ref": "main"}}] + f = fake(gh_prs=gh, wg_prs=[{"number": 3, "title": "[GH#5] t", "base": {"ref": "main"}}]) + bridge.sync_prs("windy-chat") + assert f.closed == [] and f.opened == [] diff --git a/scripts/pr_status_bridge.py b/scripts/pr_status_bridge.py index 11b709f..1d76e45 100755 --- a/scripts/pr_status_bridge.py +++ b/scripts/pr_status_bridge.py @@ -202,7 +202,17 @@ def sync_prs(repo: str) -> list[str]: wanted.add(tag) heads.append(pr["head"]["sha"]) if tag in ours: - continue + cur = (ours[tag].get("base") or {}).get("ref") + if cur is None or cur == pr["base"]["ref"]: # unknown base: never guess, leave it + continue + # Retargeted on GitHub (e.g. a stacked PR moved to main after its + # parent merged). The mirror kept the OLD base, so workflows filtered + # on the base (`pull_request: branches: [main]`) silently stopped + # running: eternitas #167, 09-23. Replace the mirror: an "edited" + # event triggers nothing, a freshly opened PR runs CI at once. + gitea("PATCH", f"/repos/{WG_OWNER}/{repo}/pulls/{ours[tag]['number']}", {"state": "closed"}) + print(f" {repo}: GH#{pr['number']} retargeted {cur} -> " + f"{pr['base']['ref']}: replacing its mirror PR") st, _ = gitea( "POST", f"/repos/{WG_OWNER}/{repo}/pulls",