# -*- coding: utf-8 -*- r'''带重试的增量推送 —— 把 44 个提交逐个推到 EzHub。 为什么逐个推: 一次性推 44 个提交 = 117 MB,EzHub 的 nginx 返回 413。 但 Git 推送是"增量"的:第一个提交推成功后, 后面每个提交只需要传自己新增的对象。 逐个推,每包只有几十 KB ~ 几 MB,就不会撞上限。 为什么带重试: 这台网络(4G + 家宽)会偶发连不上 git.261826.xyz。 实测推第 5 个提交时报 "Failed to connect" —— 纯网络抖动, 重试就好。不加重试会白费前面推成功的部分(虽然它们已经进仓库了, 但脚本会误以为整体失败)。 已经推上去的提交会自动跳过(push 会说 Everything up-to-date)。 ''' from __future__ import annotations import subprocess import sys import time from pathlib import Path ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") GIT = r"C:\Program Files\Git\bin\git.exe" TOKEN_FILE = Path.home() / ".ykxmz" / "ezhub_token" OWNER, REPO, HOST = "xmz", "ykxmz", "git.261826.xyz" MAX_TRIES = 4 RETRY_WAIT = 6 # 秒,每次重试前等这么久 def git(*args: str) -> subprocess.CompletedProcess: return subprocess.run([GIT] + list(args), cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8", errors="replace") def remote_main(url: str) -> str: """取远端 main 指向哪个提交(空仓库返回空串)。""" r = subprocess.run([GIT, "ls-remote", url, "refs/heads/main"], cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=120) line = (r.stdout or "").strip() return line.split()[0] if line else "" def push_one(url: str, sha: str, token: str) -> tuple: """推一个提交。返回 (成功?, 说明)。 ⚠ 如果远端已经超过这个提交了,再推它就是"回退", Git 会拒绝(non-fast-forward)。这种情况视为成功(跳过), 因为它的内容已经在远端的历史里了。 """ last = "" for attempt in range(1, MAX_TRIES + 1): r = subprocess.run( [GIT, "push", url, "%s:refs/heads/main" % sha], cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=900) err = (r.stderr or r.stdout or "").replace(token, "***") if r.returncode == 0: if "Everything up-to-date" in err: return True, "已是最新" return True, "ok" last = [ln for ln in err.strip().splitlines() if ln.strip()] last = last[-1][:110] if last else "?" # 远端已经往前走了 -> 这个提交已经在历史里,跳过 if "non-fast-forward" in err or "fast-forwards" in err: return True, "远端已包含(跳过)" # 只对"能重试"的错误重试:网络抖动 / 5xx retryable = any(k in last for k in ( "Failed to connect", "Connection reset", "timed out", "Could not resolve", "RPC failed", "unexpected disconnect", " 500", " 502", " 503", " 504")) if not retryable or attempt == MAX_TRIES: return False, "第 %d 次: %s" % (attempt, last) print(" 重试 %d/%d(%s)" % (attempt, MAX_TRIES, last[:50])) time.sleep(RETRY_WAIT) return False, last def main() -> int: if not TOKEN_FILE.is_file(): print(" [失败] 没有令牌,先跑 ezHub_push.py <令牌>") return 1 token = TOKEN_FILE.read_text(encoding="utf-8").strip() url = "https://%s:%s@%s/%s/%s.git" % (OWNER, token, HOST, OWNER, REPO) out = git("rev-list", "--reverse", "HEAD").stdout or "" commits = [c for c in out.splitlines() if c.strip()] total = len(commits) head = remote_main(url) if head: # 远端已经有的那些直接跳过 ahead = git("rev-list", "--reverse", "%s..HEAD" % head).stdout or "" todo = [c for c in ahead.splitlines() if c.strip()] print(" 远端 main 已在 %s" % head[:12]) print(" 共 %d 个提交,其中 %d 个已经在远端,需要推 %d 个" % (total, total - len(todo), len(todo))) else: todo = commits print(" 远端还是空的") print(" 共 %d 个提交,需要全部推" % total) print() if not todo: print(" ✓ 远端已经是最新,不用推") done = 0 for index, sha in enumerate(todo, 1): ok, why = push_one(url, sha, token) if ok: done = index if index % 5 == 0 or index == len(todo): print(" %3d/%d ✓" % (index, len(todo))) else: print(" %3d/%d ✗ %s" % (index, len(todo), why)) r = git("show", "--stat", "--oneline", sha) print(" 这个提交改了什么:") for line in (r.stdout or "").strip().splitlines()[:8]: print(" " + line[:95]) print() print(" 这一轮推到第 %d 个,还剩 %d 个" % (done, len(todo) - done)) print(" 再跑一次本脚本会从断的地方继续。") return 1 print() print(" ✓ 这一轮全部推完" % ()) # 确认 remote_sha = remote_main(url) local_sha = git("rev-parse", "HEAD").stdout.strip() print(" 远端 main: %s" % remote_sha[:12]) print(" 本地 HEAD: %s" % local_sha[:12]) print(" %s" % ("✓ 一致" if remote_sha == local_sha else "✗ 不一致")) return 0 if remote_sha == local_sha else 1 if __name__ == "__main__": sys.exit(main())