修 Gitee 附件清理:assets 字段没 id,要用 attach_files 接口

Gitee 的坑:GET /releases/{id} 返回的 assets 里
**只有 name 和 download_url,没有 id**,
照着它删是删不掉的(之前静默失败,附件一路堆到 11 个)。

必须换接口才拿得到 id:
    GET    /releases/{id}/attach_files        -> [{id, name, size}]
    DELETE /releases/{id}/attach_files/{id}

已清理:Gitee 11 -> 5 个(含 2 个自动生成的源码包),
EzHub 5 -> 3 个。清理动作也加了重试(网络会抖)。
This commit is contained in:
xmz7891
2026-10-07 12:54:33 +08:00
parent ae7e1cf269
commit 9692ef1f32
3 changed files with 254 additions and 12 deletions
+158
View File
@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
r'''统一清理两个平台发行版里的重复附件。
背景:
重发时如果只"复用已有发行版",附件会一次次堆积。
Gitee 尤其绕:`GET /releases/tags/{tag}` 返回的 `assets`
**只有 name 和 download_url,没有 id**,所以没法删。
但另一个接口有 id:
GET /repos/{o}/{r}/releases/{id}/attach_files -> 带 id
DELETE /repos/{o}/{r}/releases/{id}/attach_files/{attach_id}
Gitea/EzHub 则直接有 assets[].id。
策略:同名附件只保留最新的一份,其余删掉。
支持 --dry 先看要删什么。
'''
from __future__ import annotations
import json
import sys
import time
import urllib.error
import urllib.request
from collections import defaultdict
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
GT = (ROOT / "_build" / ".gitee_token").read_text(encoding="utf-8").strip()
ET = (Path.home() / ".ykxmz" / "ezhub_token").read_text(
encoding="utf-8").strip()
TAG = "v1.8.0"
# 这两个是 Gitee 自动生成的源码包,不是我们传的,不要动
KEEP_ALWAYS = {"v1.8.0.zip", "v1.8.0.tar.gz"}
def http(url: str, method: str = "GET", kind: str = "gitea",
token: str = "", tries: int = 3):
"""带重试的请求。网络会抖,不重试会误判。"""
headers = {"User-Agent": "ykxmz-clean"}
if kind == "gitee":
sep = "&" if "?" in url else "?"
url = url + sep + "access_token=" + token
else:
headers["Authorization"] = "token " + token
last = None
for attempt in range(1, tries + 1):
try:
req = urllib.request.Request(url, method=method, headers=headers)
with urllib.request.urlopen(req, timeout=90) 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:
return exc.code, exc.read().decode("utf-8", "replace")[:200]
except Exception as exc: # noqa: BLE001
last = exc
if attempt < tries:
time.sleep(4)
return 0, str(last)
def list_gitee_assets(rid: int) -> list:
"""Gitee 要单独调 attach_files 才拿得到 id。"""
status, data = http(
"https://gitee.com/api/v5/repos/xmz7891/ykxmz/releases/%s"
"/attach_files" % rid, kind="gitee", token=GT)
if status != 200 or not isinstance(data, list):
print(" [失败] 取 Gitee 附件列表 HTTP %s: %s" % (status, data))
return []
return data
def list_gitea_assets(rid: int) -> list:
status, data = http(
"https://git.261826.xyz/api/v1/repos/xmz/ykxmz/releases/%s"
% rid, kind="gitea", token=ET)
if status != 200:
print(" [失败] 取 EzHub 发行版 HTTP %s" % status)
return []
return data.get("assets") or []
def clean(platform: str, rid: int, assets: list, dry: bool) -> int:
groups = defaultdict(list)
for a in assets:
name = a.get("name") or "?"
if name in KEEP_ALWAYS:
continue
groups[name].append(a)
removed = 0
for name, items in sorted(groups.items()):
if len(items) <= 1:
continue
# 新的排前面
items.sort(key=lambda x: str(x.get("created_at") or ""), reverse=True)
keep = items[0]
print(" %-32s %d 份 -> 保留 id=%s" % (name, len(items),
keep.get("id")))
for extra in items[1:]:
aid = extra.get("id")
if dry:
print(" (演练)会删 id=%s" % aid)
removed += 1
continue
if platform == "gitee":
url = ("https://gitee.com/api/v5/repos/xmz7891/ykxmz"
"/releases/%s/attach_files/%s" % (rid, aid))
else:
url = ("https://git.261826.xyz/api/v1/repos/xmz/ykxmz"
"/releases/%s/assets/%s" % (rid, aid))
status, body = http(url, method="DELETE",
kind=("gitee" if platform == "gitee"
else "gitea"),
token=(GT if platform == "gitee" else ET))
if status in (200, 204):
print(" ✓ 删了 id=%s" % aid)
removed += 1
else:
print(" ✗ id=%s HTTP %s %s"
% (aid, status, str(body)[:80]))
return removed
def main() -> int:
dry = "--dry" in sys.argv
total = 0
print(" ═══ Gitee ═══")
status, rel = http("https://gitee.com/api/v5/repos/xmz7891/ykxmz"
"/releases/tags/" + TAG, kind="gitee", token=GT)
if status == 200:
rid = rel.get("id")
assets = list_gitee_assets(rid)
print(" 发行版 id=%s,可删附件 %d 个" % (rid, len(assets)))
total += clean("gitee", rid, assets, dry)
else:
print(" [失败] HTTP %s" % status)
print()
print(" ═══ EzHub ═══")
status, rel = http("https://git.261826.xyz/api/v1/repos/xmz/ykxmz"
"/releases/tags/" + TAG, kind="gitea", token=ET)
if status == 200:
rid = rel.get("id")
assets = list_gitea_assets(rid)
print(" 发行版 id=%s,附件 %d 个" % (rid, len(assets)))
total += clean("gitea", rid, assets, dry)
else:
print(" [失败] HTTP %s" % status)
print()
print(" %s %d 个" % ("演练:会删" if dry else "共删", total))
return 0
if __name__ == "__main__":
sys.exit(main())
+70
View File
@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
r'''列出两个平台发行版的全部附件(含 ID),并试删一个看接口通不通。'''
from __future__ import annotations
import json
import sys
import urllib.error
import urllib.request
from collections import defaultdict
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
GT = (ROOT / "_build" / ".gitee_token").read_text(encoding="utf-8").strip()
ET = (Path.home() / ".ykxmz" / "ezhub_token").read_text(
encoding="utf-8").strip()
TAG = "v1.8.0"
def get(url: str, token: str, kind: str):
headers = {"User-Agent": "ykxmz"}
if kind == "gitee":
sep = "&" if "?" in url else "?"
url = url + sep + "access_token=" + token
else:
headers["Authorization"] = "token " + token
with urllib.request.urlopen(
urllib.request.Request(url, headers=headers), timeout=60) as r:
return json.loads(r.read().decode("utf-8", "replace"))
def dump(name: str, url: str, token: str, kind: str) -> None:
print(" ═══ %s ═══" % name)
try:
rel = get(url, token, kind)
except Exception as exc: # noqa: BLE001
print(" 查询失败: %s" % exc)
return
rid = rel.get("id")
print(" release id = %s" % rid)
assets = (rel.get("assets") or rel.get("attach_files") or [])
print(" 附件 %d 个:" % len(assets))
groups = defaultdict(list)
for a in assets:
aname = a.get("name") or a.get("filename") or "?"
aid = a.get("id")
when = a.get("created_at") or a.get("created_at") or ""
size = a.get("size") or 0
groups[aname].append((aid, when, size))
print(" id=%-10s %-32s %8.2f MB %s"
% (aid, aname, size / 1024 / 1024, str(when)[:19]))
print()
print(" 重复情况:")
for aname, items in sorted(groups.items()):
if len(items) > 1:
print(" %-32s %d 份" % (aname, len(items)))
print()
def main() -> int:
dump("Gitee",
"https://gitee.com/api/v5/repos/xmz7891/ykxmz/releases/tags/" + TAG,
GT, "gitee")
dump("EzHub",
"https://git.261826.xyz/api/v1/repos/xmz/ykxmz/releases/tags/" + TAG,
ET, "gitea")
return 0
if __name__ == "__main__":
sys.exit(main())
+23 -9
View File
@@ -133,27 +133,41 @@ def gitee_release(token: str, version: str, body: str,
return False
# 复用已有发行版时先把同名旧附件删掉,否则每跑一次多一份。
#
# ⚠ Gitee 的坑:`GET /releases/{id}` 返回的 `assets` **只有 name 和
# download_url,没有 id**,照着它删是删不掉的。
# 必须换成单独这个接口才带 id:
# GET /releases/{id}/attach_files -> [{id, name, size, ...}]
# 删也是:DELETE /releases/{id}/attach_files/{attach_id}
try:
with urllib.request.urlopen(
"%s/releases/%s?access_token=%s" % (api, rid, token),
timeout=60) as resp:
existing = json.loads(resp.read().decode("utf-8", "replace"))
"%s/releases/%s/attach_files?access_token=%s"
% (api, rid, token), timeout=90) as resp:
old_assets = json.loads(resp.read().decode("utf-8", "replace"))
if not isinstance(old_assets, list):
old_assets = []
wanted = {p.name for p in files}
# Gitee 叫 attach_files,Gitea 叫 assets
old_assets = (existing.get("assets")
or existing.get("attach_files") or [])
for asset in old_assets:
name = asset.get("name") or asset.get("filename")
name = asset.get("name")
aid = asset.get("id")
if name in wanted and aid:
del_url = ("%s/releases/%s/attach_files/%s"
"?access_token=%s" % (api, rid, aid, token))
dreq = urllib.request.Request(del_url, method="DELETE")
for attempt in range(1, 4):
try:
urllib.request.urlopen(dreq, timeout=60)
urllib.request.urlopen(dreq, timeout=90)
print(" • 删掉同名旧附件 %s" % name)
break
except urllib.error.HTTPError as exc:
print(" • 删 %s 失败 HTTP %s(继续)" % (name, exc.code))
print(" • 删 %s 失败 HTTP %s(继续)"
% (name, exc.code))
break
except Exception: # noqa: BLE001
if attempt == 3:
print(" • 删 %s 网络失败,跳过" % name)
else:
time.sleep(4)
except Exception as exc: # noqa: BLE001
print(" • 清理旧附件时出错(继续): %s" % exc)