diff --git a/tangled_pr.py b/tangled_pr.py index f60d840..abc5328 100755 --- a/tangled_pr.py +++ b/tangled_pr.py @@ -49,17 +49,26 @@ def main(): a = ap.parse_args() cookie = load_cookie(a.cookie_file) - url = f"{BASE}/{a.owner}/{a.repo}/pulls/" - form = {"targetBranch": a.target, "sourceBranch": a.source} + # Tangled's PR form is htmx: it POSTs to /pulls/new (NOT /pulls/, which is 405) and only processes + # the create when it sees the HX-Request header — otherwise it just re-renders the page (a 200 that + # creates nothing). Success is signalled by an HX-Redirect header pointing at the new pull. + new_url = f"{BASE}/{a.owner}/{a.repo}/pulls/new" + form = { + "source": "branch", # branch-compare mode (each PR targets the branch below it) + "targetBranch": a.target, + "sourceBranch": a.source, + "title": a.title, "titleDirty": "true", + "body": a.body, "bodyDirty": "true", + } if a.fork: form["fork"] = a.fork - if a.title: form["title"] = a.title - if a.body: form["body"] = a.body data = urllib.parse.urlencode(form).encode() - req = urllib.request.Request(url, data=data, method="POST", headers={ + req = urllib.request.Request(new_url, data=data, method="POST", headers={ "Cookie": cookie, "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "tangled-pr-bot", - "Referer": f"{BASE}/{a.owner}/{a.repo}/pulls/new", + "HX-Request": "true", + "HX-Current-URL": new_url, + "Referer": new_url, }) class NoRedirect(urllib.request.HTTPRedirectHandler): @@ -68,17 +77,18 @@ def main(): opener = urllib.request.build_opener(NoRedirect) try: r = opener.open(req, timeout=90) - code, loc, body = r.getcode(), r.headers.get("Location", ""), r.read(3000).decode("utf-8", "replace") + hdrs, code, body = r.headers, r.getcode(), r.read(3000).decode("utf-8", "replace") except urllib.error.HTTPError as e: - code, loc, body = e.code, e.headers.get("Location", ""), e.read(3000).decode("utf-8", "replace") - - print(f"HTTP {code}" + (f" -> {loc}" if loc else "")) - if code in (301, 302, 303, 307, 308) and "/pulls/" in loc and "/new" not in loc: - print("OK: pull created ->", (BASE + loc) if loc.startswith("/") else loc) + hdrs, code, body = e.headers, e.code, e.read(3000).decode("utf-8", "replace") + # htmx success is signalled by HX-Redirect (…/pulls/), not a normal 3xx Location. + target = hdrs.get("HX-Redirect", "") or hdrs.get("HX-Location", "") or hdrs.get("Location", "") + print(f"HTTP {code}" + (f" -> {target}" if target else "")) + if target and "/pulls/" in target and "/new" not in target: + print("OK: pull created ->", (BASE + target) if target.startswith("/") else target) return - if code in (301, 302, 303) and ("/login" in loc or "oauth" in loc): - sys.exit("AUTH FAILED: session cookie expired/invalid — redo the one-time browser login " - "and update tools/.tangled-session") + if ("/login" in target or "oauth" in target.lower()): + sys.exit("AUTH FAILED: session cookie expired/invalid — refresh engine/.tangled-session " + "(scripts/get-tangled-cookie.py)") # otherwise surface whatever the page said (a Notice, etc.) snippet = " ".join(body.split())[:600] print(" no clear success redirect — response snippet:")