修发行版重复附件:重发前先删同名

Gitea 的发行版复用逻辑有 bug:发现标签已有发行版就复用,
但**没有先删同名附件**,于是每跑一次就多传一份。
调试期间同一个文件在 EzHub 上堆了 3 份(共 8 个附件,实际只要 3 个)。

修法:复用已有发行版时,先列出它的附件,
把和本次要传的同名的删掉,再传。

配套加了:
  * clean_ezhub_assets.py  清理已堆积的重复(同名只留最新,支持 --dry)
  * verify_releases.py     对账两个平台的发行版(标题/说明/附件/
                           管理页字眼/两边说明是否一致)

已清理:EzHub 从 8 个附件降到 3 个。
This commit is contained in:
xmz7891
2026-10-07 12:36:35 +08:00
parent 4b545ea324
commit 2425f8694e
3 changed files with 229 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
r'''清理 EzHub 发行版里重复的附件。
为什么会重复:
release_all.py 发现标签已有发行版时会"复用它",
但**没有先删同名附件**,于是每跑一次就多传一遍。
我调试时跑了几次,同一个文件就堆了 2~3 份。
这个脚本:同名附件只保留最新的一份,其余删掉。
(Gitea 的附件用 id 区分,创建时间看 created_at。)
'''
from __future__ import annotations
import json
import sys
import urllib.error
import urllib.request
from collections import defaultdict
from pathlib import Path
TOKEN = (Path.home() / ".ykxmz" / "ezhub_token").read_text(
encoding="utf-8").strip()
API = "https://git.261826.xyz/api/v1/repos/xmz/ykxmz"
TAG = "v1.8.0"
HEAD = {"Authorization": "token " + TOKEN, "User-Agent": "ykxmz-clean"}
def call(url: str, method: str = "GET"):
req = urllib.request.Request(url, method=method, headers=HEAD)
try:
with urllib.request.urlopen(req, timeout=60) as resp:
text = resp.read().decode("utf-8", "replace")
return resp.status, (json.loads(text) if text.strip() else {})
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", "replace")[:200]
return exc.code, body
def main() -> int:
dry = "--dry" in sys.argv
status, release = call("%s/releases/tags/%s" % (API, TAG))
if status != 200:
print(" [失败] 取发行版 HTTP %s: %s" % (status, release))
return 1
rid = release["id"]
assets = release.get("assets") or []
print(" 发行版 id=%s,附件 %d 个" % (rid, len(assets)))
# 按文件名分组
groups = defaultdict(list)
for a in assets:
groups[a.get("name") or "?"].append(a)
print()
print(" 分组情况:")
to_delete = []
for name, items in sorted(groups.items()):
# 新的排在前面(created_at 降序)
items.sort(key=lambda x: x.get("created_at") or "", reverse=True)
keep = items[0]
print(" %-32s 共 %d 份,保留 id=%s(%s)"
% (name, len(items), keep.get("id"),
(keep.get("created_at") or "")[:19]))
for extra in items[1:]:
print(" 删 id=%s(%s)" % (extra.get("id"),
(extra.get("created_at")
or "")[:19]))
to_delete.append(extra)
if not to_delete:
print()
print(" ✓ 没有重复,不用清理")
return 0
print()
if dry:
print(" 演练:会删 %d 个(去掉 --dry 真删)" % len(to_delete))
return 0
print(" 开始删除 %d 个重复附件…" % len(to_delete))
ok = 0
for asset in to_delete:
status, body = call("%s/releases/%s/assets/%s"
% (API, rid, asset.get("id")), method="DELETE")
if status in (200, 204):
print(" ✓ 删了 id=%s %s" % (asset.get("id"),
asset.get("name")))
ok += 1
else:
print(" ✗ id=%s HTTP %s %s" % (asset.get("id"), status, body))
print()
print(" 删除 %d/%d" % (ok, len(to_delete)))
# 复查
status, release = call("%s/releases/tags/%s" % (API, TAG))
assets = release.get("assets") or []
print()
print(" 现在剩 %d 个附件:" % len(assets))
for a in assets:
print(" %-32s %8.2f MB"
% (a.get("name"), (a.get("size") or 0) / 1024 / 1024))
return 0
if __name__ == "__main__":
sys.exit(main())