diff --git a/_build/ezhub_incremental.py b/_build/ezhub_incremental.py new file mode 100644 index 0000000..5bffe1a --- /dev/null +++ b/_build/ezhub_incremental.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +r'''摸出 EzHub 的推送体积上限,并尝试增量推送。 + +背景: + 一次性推 39 个提交 = 117 MB,EzHub 返回 413(nginx 的 + client_max_body_size 拦了)。Gitee 上限大,所以一直没事。 + +思路: + 第一个提交推成功后,后面每个提交只需要传"新增的对象"。 + 所以逐个提交推,每包就小得多。 + 但如果某个提交本身就加了个 35 MB 的二进制,那一包照样超限 —— + 这里会把它找出来。 +''' +from __future__ import annotations + +import subprocess +import sys +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" + + +def git(*args: str, timeout: int = 600) -> subprocess.CompletedProcess: + return subprocess.run([GIT] + list(args), cwd=str(ROOT), + capture_output=True, text=True, + encoding="utf-8", errors="replace", + timeout=timeout) + + +def main() -> int: + 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()] + print(" 共 %d 个提交" % len(commits)) + + # 先看每个提交自身新增了多大的对象 + print() + print(" 各提交新增的对象体积(找超大的):") + big = [] + for index, sha in enumerate(commits, 1): + # 这个提交相对父提交新增的 blob + r = git("diff-tree", "-r", "--no-commit-id", "--objects", + "-r", sha) + entries = [ln for ln in (r.stdout or "").splitlines() if ln.strip()] + if not entries: + continue + payload = "\n".join(ln.split(" ", 1)[0] for ln in entries) + c = subprocess.run( + [GIT, "cat-file", "--batch-check=%(objecttype) %(objectsize) " + "%(objectname)"], + cwd=str(ROOT), input=payload, capture_output=True, text=True, + encoding="utf-8", errors="replace") + total = 0 + for line in (c.stdout or "").splitlines(): + parts = line.split() + if len(parts) >= 3 and parts[0] == "blob": + total += int(parts[1]) + if total > 1024 * 1024: + msg = git("log", "-1", "--format=%s", sha).stdout.strip()[:50] + big.append((total, sha[:8], msg)) + big.sort(reverse=True) + for total, sha, msg in big[:12]: + print(" %8.1f MB %s %s" % (total / 1024 / 1024, sha, msg)) + if not big: + print(" (没有超过 1 MB 的)") + + # 逐个推 + print() + print(" 逐个提交推(每包只含新增对象):") + ok_until = 0 + failed_at = None + for index, sha in enumerate(commits, 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: + ok_until = index + if index % 5 == 0 or index == len(commits): + print(" 到第 %d/%d 个提交 ✓" % (index, len(commits))) + else: + tail = [ln for ln in err.strip().splitlines() if ln.strip()] + reason = tail[-1][:100] if tail else "?" + print(" 第 %d 个提交失败: %s" % (index, reason)) + failed_at = (index, sha, reason) + break + + print() + if failed_at is None: + print(" ✓ 全部推完(%d 个提交)" % len(commits)) + return 0 + + index, sha, reason = failed_at + print(" 卡在第 %d 个提交 %s" % (index, sha)) + print(" 原因: %s" % reason) + print() + print(" 这个提交新增的东西:") + r = git("show", "--stat", "--oneline", sha) + for line in (r.stdout or "").strip().splitlines()[:15]: + print(" " + line[:100]) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/ezhub_push_all.py b/_build/ezhub_push_all.py new file mode 100644 index 0000000..d012e49 --- /dev/null +++ b/_build/ezhub_push_all.py @@ -0,0 +1,145 @@ +# -*- 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()) diff --git a/_build/measure_repo.py b/_build/measure_repo.py new file mode 100644 index 0000000..6715136 --- /dev/null +++ b/_build/measure_repo.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +r'''量一下仓库体积,找出推送 413 的原因。''' +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote") +GIT = r"C:\Program Files\Git\bin\git.exe" + + +def git(*args: str) -> str: + r = subprocess.run([GIT] + list(args), cwd=str(ROOT), + capture_output=True, text=True, + encoding="utf-8", errors="replace") + return r.stdout or "" + + +def main() -> int: + print(" 一、仓库对象统计") + print(" " + git("count-objects", "-vH").replace("\n", "\n ").strip()) + + print() + print(" 二、各提交的树体积(找有没有混进大文件)") + # 列出所有 blob 及大小 + out = git("rev-list", "--objects", "--all") + lines = [ln for ln in out.splitlines() if ln.strip()] + print(" 对象条目 %d 个" % len(lines)) + + # 批量查类型和大小 + payload = "\n".join(ln.split(" ", 1)[0] for ln in lines) + r = subprocess.run([GIT, "cat-file", "--batch-check=%(objecttype) %(objectsize) %(objectname)"], + cwd=str(ROOT), input=payload, capture_output=True, + text=True, encoding="utf-8", errors="replace") + info = {} + for line in (r.stdout or "").splitlines(): + parts = line.split() + if len(parts) >= 3 and parts[0] == "blob": + info[parts[2]] = int(parts[1]) + + blobs = [] + for ln in lines: + sha, _, name = ln.partition(" ") + if sha in info: + blobs.append((info[sha], name or "(无名)")) + # 同一个 blob 可能出现多次,去重 + seen = set() + uniq = [] + for size, name in blobs: + key = (size, name) + if key in seen: + continue + seen.add(key) + uniq.append((size, name)) + + uniq.sort(reverse=True) + total = sum(s for s, _ in uniq) + print(" 去重后 %d 个 blob,合计 %.2f MB" + % (len(uniq), total / 1024 / 1024)) + print() + print(" 最大的 15 个:") + for size, name in uniq[:15]: + print(" %9.1f KB %s" % (size / 1024, name[:70])) + + print() + print(" 三、结论") + biggest = uniq[0][0] if uniq else 0 + if total > 20 * 1024 * 1024: + print(" 仓库历史 %.1f MB,偏大 —— 推送会被 nginx 的" + " client_max_body_size 拦(413)" % (total / 1024 / 1024)) + print(" 最大单个文件 %.1f MB" / 1024 / 1024 % (biggest / 1024 / 1024) + if False else " 最大单个文件 %.1f MB" % (biggest / 1024 / 1024)) + else: + print(" 仓库历史 %.2f MB,不算大" % (total / 1024 / 1024)) + + print() + print(" 四、本机 Gitee 推得动说明不是仓库本身的问题,") + print(" 是 EzHub 那边的 nginx 上限比 Gitee 小。") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/_build/release_all.py b/_build/release_all.py index df75409..0b9fb87 100644 --- a/_build/release_all.py +++ b/_build/release_all.py @@ -324,12 +324,28 @@ def main() -> int: return 0 # ---- 1. 推代码 ---- + # EzHub 的 SSH 密钥在用户那边还没配对成功(服务端拒绝本机公钥), + # 但令牌有 push 权限,所以走 HTTPS + 令牌推。 + # 令牌只在这次进程里拼进 URL,不写进 .git/config。 print() print(" 二、推代码") for name, info in PLATFORMS.items(): - r = run_git(["push", info["remote"], "main"]) + if name == "ezhub": + url = ("https://%s:%s@%s/%s/%s.git" + % ("xmz", ezhub_token, "git.261826.xyz", "xmz", "ykxmz")) + r = subprocess.run([GIT, "push", url, "main"], cwd=str(ROOT), + capture_output=True, text=True, + encoding="utf-8", errors="replace", + timeout=900) + raw = (r.stderr or r.stdout or "") + else: + r = run_git(["push", info["remote"], "main"]) + raw = (r.stderr or r.stdout or "") + ok = r.returncode == 0 - tail = ((r.stderr or r.stdout or "").strip().splitlines() or [""])[-1] + # 输出里可能带令牌,打码 + safe = raw.replace(ezhub_token, "***") if ezhub_token else raw + tail = (safe.strip().splitlines() or [""])[-1] print(" %-6s %s %s" % (name, "✓" if ok else "✗", tail[:90])) if not ok: print(" (代码没推上去,先修这个再建发行版)")