#!/usr/bin/env python3 """GitHub target repo 只讀存在性探測。 此工具使用 `git ls-remote --heads` 檢查候選 GitHub repo 是否可讀。 它不 clone、不 fetch、不 push,也不寫入任何 remote。 """ from __future__ import annotations import argparse import json import subprocess from pathlib import Path def probe_candidate(candidate: str, timeout: int) -> dict[str, object]: url = f"https://github.com/{candidate}.git" try: result = subprocess.run( ["git", "ls-remote", "--heads", url], check=False, capture_output=True, text=True, timeout=timeout, ) except subprocess.TimeoutExpired: return { "github_repo": candidate, "url_redacted": url, "status": "timeout", "head_count": 0, "heads": [], "error_summary": "git ls-remote timeout", } heads = [] if result.returncode == 0: for line in result.stdout.splitlines(): parts = line.split() if len(parts) != 2 or not parts[1].startswith("refs/heads/"): continue heads.append( { "name": parts[1].removeprefix("refs/heads/"), "sha": parts[0], } ) status = "exists" if heads else "exists_empty_or_no_heads" error_summary = "" else: stderr = result.stderr.strip() if "Repository not found" in stderr: status = "not_found_or_private" error_summary = "GitHub 回應 repository not found;可能未建立或為 private 且未授權" else: status = "error" error_summary = stderr.splitlines()[-1] if stderr else "git ls-remote failed" return { "github_repo": candidate, "url_redacted": url, "status": status, "head_count": len(heads), "heads": heads, "error_summary": error_summary, } def write_markdown(payload: dict[str, object], path: Path) -> None: lines = [ "# GitHub Target Probe 快照", "", "| 項目 | 值 |", "|------|----|", f"| 狀態 | `{payload['status']}` |", f"| 候選 repo 數 | `{payload['candidate_count']}` |", f"| exists | `{payload['exists_count']}` |", f"| not found or private | `{payload['not_found_or_private_count']}` |", "", "## 候選 Repo", "", "| GitHub repo | status | heads | error |", "|-------------|--------|-------|-------|", ] for candidate in payload.get("candidates", []): if not isinstance(candidate, dict): continue lines.append( "| " + " | ".join( [ f"`{candidate.get('github_repo', '')}`", f"`{candidate.get('status', '')}`", f"`{candidate.get('head_count', 0)}`", str(candidate.get("error_summary", "") or "無"), ] ) + " |" ) lines.extend( [ "", "> 注意:`not_found_or_private` 只代表未授權 read-only probe 看不到,不等同確認不存在。", "", ] ) path.write_text("\n".join(lines), encoding="utf-8") def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--candidate", action="append", required=True) parser.add_argument("--output-json", required=True) parser.add_argument("--output-md", required=True) parser.add_argument("--timeout", type=int, default=10) args = parser.parse_args() candidates = [probe_candidate(candidate, args.timeout) for candidate in args.candidate] exists_count = sum(1 for item in candidates if item["status"].startswith("exists")) not_found_count = sum(1 for item in candidates if item["status"] == "not_found_or_private") payload = { "schema_version": "github_target_probe_v1", "status": "ok", "candidate_count": len(candidates), "exists_count": exists_count, "not_found_or_private_count": not_found_count, "candidates": candidates, } Path(args.output_json).write_text( json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) write_markdown(payload, Path(args.output_md)) return 0 if __name__ == "__main__": raise SystemExit(main())