diff --git a/maipulse/tools/release.py b/maipulse/tools/release.py index 6b97ed1..db6b41d 100644 --- a/maipulse/tools/release.py +++ b/maipulse/tools/release.py @@ -242,18 +242,33 @@ def token() -> str: raise SystemExit("没有 token:设置环境变量 GITEA_TOKEN,或写 tools/.release-token") -def api(path: str, method: str = "GET", body=None): +def api(path: str, method: str = "GET", body=None, tries: int = 5): + """Gitea 走 Cloudflare,偶发 [SSL: UNEXPECTED_EOF] 直接断连,重试几次就好。""" + import ssl + import time url = "%s/api/v1/repos/%s/%s%s" % (SITE, OWNER, REPO, path) data = json.dumps(body, ensure_ascii=False).encode("utf-8") if body else None headers = {"Authorization": "token " + token(), "User-Agent": UA, "Accept": "application/json", "Content-Type": "application/json"} - req = urllib.request.Request(url, data=data, method=method, headers=headers) - try: - resp = urllib.request.urlopen(req, timeout=60) - txt = resp.read().decode("utf-8") - return json.loads(txt) if txt.strip() else {} - except urllib.error.HTTPError as exc: - raise SystemExit("%s %s -> HTTP %s %s" % (method, path, exc.code, exc.read()[:300])) + last = None + for attempt in range(tries): + req = urllib.request.Request(url, data=data, method=method, headers=headers) + try: + resp = urllib.request.urlopen(req, timeout=90) + txt = resp.read().decode("utf-8") + return json.loads(txt) if txt.strip() else {} + except urllib.error.HTTPError as exc: + detail = exc.read()[:400] + # 4xx 是请求本身的问题,重试没意义,直接抛 + if 400 <= exc.code < 500: + raise SystemExit("%s %s -> HTTP %s %s" % (method, path, exc.code, detail)) + last = "HTTP %s %s" % (exc.code, detail) + except (urllib.error.URLError, ssl.SSLError, OSError) as exc: + last = "%s: %s" % (type(exc).__name__, exc) + wait = 2 * (attempt + 1) + print(" 第 %d 次失败(%s),%ds 后重试" % (attempt + 1, last, wait)) + time.sleep(wait) + raise SystemExit("%s %s 连续 %d 次失败:%s" % (method, path, tries, last)) def tag_exists(tag: str) -> bool: @@ -307,11 +322,13 @@ def run(cmd, cwd=None, check=True): def git_commit_push(where, msg): run(["git", "add", "-A"], cwd=where) r = run(["git", "status", "--porcelain"], cwd=where, check=False) - if not (r.stdout or "").strip(): - print(" 没有变更,跳过提交") - return False - run(["git", "-c", "user.name=maipulse", "-c", "user.email=maipulse@example.com", - "commit", "-m", msg], cwd=where, check=False) + if (r.stdout or "").strip(): + run(["git", "-c", "user.name=maipulse", "-c", "user.email=maipulse@example.com", + "commit", "-m", msg], cwd=where, check=False) + else: + print(" 工作区无变更") + # 不管这次有没有新提交,都推一次: + # 上一次可能 commit 了但 push 失败,工作区是干净的,只看 status 会误判成"没变化"。 run(["git", "push", "origin", "HEAD:main"], cwd=where) return True @@ -393,11 +410,24 @@ def main(): payload = head + blob + ("\r\n--%s--\r\n" % boundary).encode("utf-8") url = "%s/api/v1/repos/%s/%s/releases/%s/assets?name=maipulse-%s.apk" % ( SITE, OWNER, REPO, rel.get("id"), new_ver) - req = urllib.request.Request(url, data=payload, method="POST", headers={ - "Authorization": "token " + token(), "User-Agent": UA, - "Content-Type": "multipart/form-data; boundary=" + boundary, - }) - asset = json.loads(urllib.request.urlopen(req, timeout=600).read().decode("utf-8")) + asset = None + import ssl + import time + for attempt in range(5): + req = urllib.request.Request(url, data=payload, method="POST", headers={ + "Authorization": "token " + token(), "User-Agent": UA, + "Content-Type": "multipart/form-data; boundary=" + boundary, + }) + try: + asset = json.loads(urllib.request.urlopen(req, timeout=600).read().decode("utf-8")) + break + except urllib.error.HTTPError as exc: + raise SystemExit("上传失败 HTTP %s %s" % (exc.code, exc.read()[:400])) + except (urllib.error.URLError, ssl.SSLError, OSError) as exc: + print(" 第 %d 次上传失败(%s),重试" % (attempt + 1, exc)) + time.sleep(3 * (attempt + 1)) + if asset is None: + raise SystemExit("上传 APK 连续失败") print(" %s %d B" % (asset.get("name"), asset.get("size"))) print("\n发版完成")