"""issue 附件上传至 GitCode 仓(Alice_zhou/issue)
将压测附件 zip(执行步骤说明+CPP+plog+执行日志)上传到 https://gitcode.com/Alice_zhou/issue 仓,
issue 正文中的附件下载链接直接使用 GitCode 仓的 raw 链接(替代 123.60.114.238 看板下载链接)。
API(已实测验证):
- POST {api_base}/repos/{owner}/{repo}/contents/{path} JSON {content(base64), message, branch} → 创建新文件
- PUT {api_base}/repos/{owner}/{repo}/contents/{path} JSON {content, message, branch, sha} → 更新已有文件
- GET {api_base}/repos/{owner}/{repo}/contents/{path}?ref=main → 获取文件信息(sha/html_url/download_url)
- 认证:PRIVATE-TOKEN 头(config/issue_tracker.json access_token)
用法:
python scripts/upload_to_issue_repo.py --file output/{api}/artifacts/{api}_error.zip
--path artifacts/{date}/{api}_error.zip
# 返回: https://raw.gitcode.com/Alice_zhou/issue/blobs/{sha}/artifacts/.../xxx.zip(可直接下载)
在 file_bug_issues.py 中自动调用:提单前先上传 zip → 获取 raw 链接 → 写入 issue 正文。
"""
import argparse
import base64
import json
import os
import sys
import urllib.error
import urllib.request
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
DEFAULT_CONFIG = "config/issue_tracker.json"
MAX_FILE_BYTES = 50 * 1024 * 1024
def log(tag, msg):
print("[{}] {}".format(tag, msg))
class IssueRepoClient:
"""GitCode 仓文件操作客户端(Alice_zhou/issue 附件存取)。"""
def __init__(self, config_path=DEFAULT_CONFIG):
cfg = json.load(open(config_path, encoding="utf-8"))
self.api_base = cfg.get("api_base", "https://gitcode.com/api/v5").rstrip("/")
self.token = cfg.get("access_token", "")
if self.token.startswith("${") and self.token.endswith("}"):
env = self.token[2:-1]
self.token = os.environ.get(env, "")
if not self.token:
raise RuntimeError("access_token 未配置")
self.repo_owner = cfg.get("issue_repo_owner", "Alice_zhou")
self.repo_name = cfg.get("issue_repo_name", "issue")
self.branch = cfg.get("issue_repo_branch", "main")
def _request(self, path, method="GET", data=None):
url = "{}/repos/{}/{}{}".format(self.api_base, self.repo_owner, self.repo_name,
"/contents/" + path)
body = json.dumps(data).encode("utf-8") if data else None
req = urllib.request.Request(url, data=body, method=method,
headers={"PRIVATE-TOKEN": self.token,
"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=120) as resp:
return resp.status, json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
try:
return exc.code, json.loads(exc.read().decode("utf-8"))
except Exception:
return exc.code, {}
except Exception as exc:
return -1, {"error_message": str(exc)}
def get_file(self, path):
"""获取文件信息(sha/html_url/download_url)。"""
return self._request(path + "?ref=" + self.branch)
def upload_file(self, local_path, repo_path, commit_msg=""):
"""上传本地文件到仓(POST 创建 / PUT 更新已有文件)。
返回 (ok, download_url, html_url)。
"""
if not os.path.isfile(local_path):
return False, "本地文件不存在: {}".format(local_path), ""
size = os.path.getsize(local_path)
if size > MAX_FILE_BYTES:
return False, "文件超过 {}MB 上限".format(MAX_FILE_BYTES // (1024 * 1024)), ""
with open(local_path, "rb") as fh:
content_b64 = base64.b64encode(fh.read()).decode("ascii")
if not commit_msg:
commit_msg = "artifact: upload {}".format(os.path.basename(repo_path))
code, info = self.get_file(repo_path)
if code == 200:
sha = info.get("sha", "")
code2, resp = self._request(repo_path, "PUT", {
"content": content_b64, "message": commit_msg + " (update)",
"branch": self.branch, "sha": sha})
else:
code2, resp = self._request(repo_path, "POST", {
"content": content_b64, "message": commit_msg,
"branch": self.branch})
if code2 in (200, 201):
code3, info2 = self.get_file(repo_path)
if code3 == 200:
dl = info2.get("download_url", "")
html = info2.get("html_url", "")
if dl:
return True, dl, html
return True, "", ""
return False, "上传失败 [{}]: {}".format(code2, json.dumps(resp, ensure_ascii=False)[:200]), ""
def main():
parser = argparse.ArgumentParser(description="issue 附件上传至 GitCode 仓(Alice_zhou/issue)")
parser.add_argument("--file", required=True, help="本地 zip 文件路径")
parser.add_argument("--path", required=True, help="仓内路径(如 artifacts/2026-10-22/aclnnXxx_error.zip)")
parser.add_argument("--config", default=DEFAULT_CONFIG)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
client = IssueRepoClient(args.config)
ok, dl_url, html_url = client.upload_file(args.file, args.path)
result = {"ok": ok, "download_url": dl_url, "html_url": html_url,
"repo": "{}/{}".format(client.repo_owner, client.repo_name)}
if args.json:
print(json.dumps(result, ensure_ascii=False))
else:
if ok:
log("UPLOAD", "成功 → {}".format(dl_url or html_url))
else:
log("ERROR", dl_url)
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())