mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 12:30:44 +00:00
修发行版重复附件:重发前先删同名
Gitea 的发行版复用逻辑有 bug:发现标签已有发行版就复用,
但**没有先删同名附件**,于是每跑一次就多传一份。
调试期间同一个文件在 EzHub 上堆了 3 份(共 8 个附件,实际只要 3 个)。
修法:复用已有发行版时,先列出它的附件,
把和本次要传的同名的删掉,再传。
配套加了:
* clean_ezhub_assets.py 清理已堆积的重复(同名只留最新,支持 --dry)
* verify_releases.py 对账两个平台的发行版(标题/说明/附件/
管理页字眼/两边说明是否一致)
已清理:EzHub 从 8 个附件降到 3 个。
This commit is contained in:
@@ -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())
|
||||||
@@ -196,6 +196,30 @@ def gitea_release(token: str, version: str, body: str,
|
|||||||
print(" ✗ 建发行版失败 HTTP %s: %s" % (exc.code, detail))
|
print(" ✗ 建发行版失败 HTTP %s: %s" % (exc.code, detail))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# 先把同名旧附件删掉。
|
||||||
|
# 不删的话,复用已有发行版时每跑一次就多传一份 ——
|
||||||
|
# 实测调试期间同一个文件堆了 3 份。
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
"%s/releases/%s" % (api, rid), headers=auth)
|
||||||
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
|
existing = json.loads(resp.read().decode("utf-8", "replace"))
|
||||||
|
wanted = {p.name for p in files}
|
||||||
|
for asset in (existing.get("assets") or []):
|
||||||
|
if asset.get("name") in wanted:
|
||||||
|
del_url = "%s/releases/%s/assets/%s" % (api, rid,
|
||||||
|
asset.get("id"))
|
||||||
|
dreq = urllib.request.Request(del_url, method="DELETE",
|
||||||
|
headers=auth)
|
||||||
|
try:
|
||||||
|
urllib.request.urlopen(dreq, timeout=60)
|
||||||
|
print(" • 删掉同名旧附件 %s" % asset.get("name"))
|
||||||
|
except urllib.error.HTTPError as exc:
|
||||||
|
print(" • 删 %s 失败 HTTP %s(继续)"
|
||||||
|
% (asset.get("name"), exc.code))
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
print(" • 清理旧附件时出错(继续): %s" % exc)
|
||||||
|
|
||||||
ok = 0
|
ok = 0
|
||||||
for path in files:
|
for path in files:
|
||||||
boundary = "----yk" + uuid.uuid4().hex
|
boundary = "----yk" + uuid.uuid4().hex
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
r'''验证两个平台的发行版:标签、说明、附件。'''
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
GITEE_TOKEN = (Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote"
|
||||||
|
r"\_build\.gitee_token").read_text(encoding="utf-8").strip())
|
||||||
|
EZHUB_TOKEN = (Path.home() / ".ykxmz" / "ezhub_token").read_text(
|
||||||
|
encoding="utf-8").strip()
|
||||||
|
|
||||||
|
TAG = "v1.8.0"
|
||||||
|
EXPECT = ["ykxmz-setup-1.8.0.exe", "ykxmz-server-1.8.0.zip",
|
||||||
|
"ykxmz-watch-1.8.0.apk"]
|
||||||
|
|
||||||
|
|
||||||
|
def fetch(url: str, token: str, kind: str):
|
||||||
|
headers = {"User-Agent": "ykxmz-verify"}
|
||||||
|
if kind == "gitee":
|
||||||
|
url += ("&" if "?" in url else "?") + "access_token=" + token
|
||||||
|
else:
|
||||||
|
headers["Authorization"] = "token " + token
|
||||||
|
with urllib.request.urlopen(
|
||||||
|
urllib.request.Request(url, headers=headers), timeout=60) as resp:
|
||||||
|
return json.loads(resp.read().decode("utf-8", "replace"))
|
||||||
|
|
||||||
|
|
||||||
|
def check(name: str, release: dict, web: str) -> None:
|
||||||
|
print(" ═══ %s ═══" % name)
|
||||||
|
print(" 标题: %s" % release.get("name"))
|
||||||
|
print(" 标签: %s" % release.get("tag_name"))
|
||||||
|
print(" 页面: %s" % (release.get("html_url") or web))
|
||||||
|
|
||||||
|
body = release.get("body") or ""
|
||||||
|
print(" 说明: %d 字符 / %d 行" % (len(body), body.count("\n") + 1))
|
||||||
|
first = (body.strip().splitlines() or [""])[0]
|
||||||
|
print(" 首行: %s" % first[:70])
|
||||||
|
|
||||||
|
# 对外口径:不许出现管理页字眼
|
||||||
|
bad = [w for w in ("zhongzhuanzhan", "/admin", "管理后台", "管理页")
|
||||||
|
if w in body]
|
||||||
|
print(" 管理页字眼: %s" % ("**有 " + str(bad) + "**" if bad else "无 ✓"))
|
||||||
|
|
||||||
|
assets = (release.get("assets") or release.get("attach_files") or [])
|
||||||
|
print(" 附件 %d 个:" % len(assets))
|
||||||
|
names = []
|
||||||
|
for a in assets:
|
||||||
|
aname = a.get("name") or a.get("filename") or "?"
|
||||||
|
size = a.get("size") or 0
|
||||||
|
names.append(aname)
|
||||||
|
print(" %-32s %8.2f MB" % (aname, size / 1024 / 1024))
|
||||||
|
missing = [e for e in EXPECT if e not in names]
|
||||||
|
print(" 三个产物齐全: %s" % ("**缺 " + str(missing) + "**"
|
||||||
|
if missing else "✓"))
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
print(" 一、Gitee")
|
||||||
|
try:
|
||||||
|
r = fetch("https://gitee.com/api/v5/repos/xmz7891/ykxmz"
|
||||||
|
"/releases/tags/" + TAG, GITEE_TOKEN, "gitee")
|
||||||
|
check("Gitee", r, "https://gitee.com/xmz7891/ykxmz/releases/tag/" + TAG)
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
print(" 查询失败: %s" % exc)
|
||||||
|
print()
|
||||||
|
|
||||||
|
print(" 二、EzHub")
|
||||||
|
try:
|
||||||
|
r = fetch("https://git.261826.xyz/api/v1/repos/xmz/ykxmz"
|
||||||
|
"/releases/tags/" + TAG, EZHUB_TOKEN, "gitea")
|
||||||
|
check("EzHub", r,
|
||||||
|
"https://git.261826.xyz/xmz/ykxmz/releases/tag/" + TAG)
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
print(" 查询失败: %s" % exc)
|
||||||
|
print()
|
||||||
|
|
||||||
|
print(" 三、两边的说明是否一致")
|
||||||
|
try:
|
||||||
|
g = fetch("https://gitee.com/api/v5/repos/xmz7891/ykxmz"
|
||||||
|
"/releases/tags/" + TAG, GITEE_TOKEN, "gitee").get("body")
|
||||||
|
e = fetch("https://git.261826.xyz/api/v1/repos/xmz/ykxmz"
|
||||||
|
"/releases/tags/" + TAG, EZHUB_TOKEN, "gitea").get("body")
|
||||||
|
same = (g or "").strip() == (e or "").strip()
|
||||||
|
print(" %s" % ("✓ 完全一致" if same else "✗ 不一致"))
|
||||||
|
if not same:
|
||||||
|
print(" Gitee %d 字符 / EzHub %d 字符"
|
||||||
|
% (len(g or ""), len(e or "")))
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
print(" 对比失败: %s" % exc)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user