AAlice_zhouupdate
974c2af8Add on 23 days agoHistory
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""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  # GitCode 单文件 50MB 上限


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 未配置")
        # issue 附件仓配置(缺省 Alice_zhou/issue)
        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))

        # 先检查是否已存在(获取 sha 用于 PUT)
        code, info = self.get_file(repo_path)
        if code == 200:
            sha = info.get("sha", "")
            # PUT 更新
            code2, resp = self._request(repo_path, "PUT", {
                "content": content_b64, "message": commit_msg + " (update)",
                "branch": self.branch, "sha": sha})
        else:
            # POST 创建
            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", "")
                # download_url 可能带 blob sha,改为稳定 raw 链接
                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())