"""
Fetch GitCode Pull Request information.
Usage:
python fetch_pr_info.py --owner OWNER --repo REPO --pull-number NUMBER [--token TOKEN] [--output-dir DIR]
"""
import argparse
import json
import os
import subprocess
import sys
import urllib.request
import urllib.error
from pathlib import Path
from typing import Optional
API_BASE = "https://api.gitcode.com/api/v5"
def get_gitcode_token(token: Optional[str] = None) -> str:
"""Get GitCode token from argument, environment, or git config."""
if token:
return token
env_token = os.environ.get("GITCODE_TOKEN")
if env_token:
return env_token
try:
result = subprocess.run(
["git", "config", "--global", "gitcode.token"],
capture_output=True,
text=True,
check=True
)
if result.stdout.strip():
return result.stdout.strip()
except subprocess.CalledProcessError:
pass
raise ValueError(
"GitCode token not found. Please set it via:\n"
"1. --token argument\n"
"2. GITCODE_TOKEN environment variable\n"
"3. git config --global gitcode.token <token>"
)
def make_api_request(url: str, token: str) -> dict:
"""Make an API request with authentication."""
headers = {
"PRIVATE-TOKEN": token,
"Accept": "application/json",
"User-Agent": "gitcode-code-reviewer/1.0"
}
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=30) as response:
return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as e:
if e.code == 401:
raise RuntimeError("Authentication failed. Please check your GitCode token.")
elif e.code == 404:
raise RuntimeError(f"Resource not found: {url}")
else:
error_body = e.read().decode("utf-8")[:500]
raise RuntimeError(f"API error: {e.code} - {e.reason}\n{error_body}")
except urllib.error.URLError as e:
raise RuntimeError(f"Network error: {e.reason}")
def fetch_pr_metadata(owner: str, repo: str, pull_number: str, token: str) -> dict:
"""Fetch PR metadata using GitCode API."""
url = f"{API_BASE}/repos/{owner}/{repo}/pulls/{pull_number}"
return make_api_request(url, token)
def fetch_pr_files(owner: str, repo: str, pull_number: str, token: str) -> list:
"""Fetch PR changed files using GitCode API."""
url = f"{API_BASE}/repos/{owner}/{repo}/pulls/{pull_number}/files"
return make_api_request(url, token)
def fetch_pr_diff(owner: str, repo: str, pull_number: str, token: str) -> str:
"""Fetch PR diff using GitCode API."""
url = f"{API_BASE}/repos/{owner}/{repo}/pulls/{pull_number}/diff"
headers = {
"PRIVATE-TOKEN": token,
"Accept": "application/vnd.github.v3.diff",
"User-Agent": "gitcode-code-reviewer/1.0"
}
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=30) as response:
return response.read().decode("utf-8")
except urllib.error.HTTPError as e:
raise RuntimeError(f"API error fetching diff: {e.code} - {e.reason}")
except urllib.error.URLError as e:
raise RuntimeError(f"Network error: {e.reason}")
def fetch_pr_comments(owner: str, repo: str, pull_number: str, token: str) -> list:
"""Fetch PR comments using GitCode API."""
url = f"{API_BASE}/repos/{owner}/{repo}/pulls/{pull_number}/comments"
return make_api_request(url, token)
def main():
parser = argparse.ArgumentParser(description="Fetch GitCode PR information")
parser.add_argument("--owner", required=True, help="Repository owner/namespace")
parser.add_argument("--repo", required=True, help="Repository name")
parser.add_argument("--pull-number", required=True, help="Pull request number")
parser.add_argument("--token", help="GitCode access token")
parser.add_argument("--output-dir", default=".", help="Output directory for fetched data")
parser.add_argument("--include-comments", action="store_true", help="Include existing PR comments")
args = parser.parse_args()
try:
token = get_gitcode_token(args.token)
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
output_dir = Path(args.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
print(f"Fetching PR #{args.pull_number} from {args.owner}/{args.repo}...")
print()
try:
print(" Fetching PR metadata...")
pr_metadata = fetch_pr_metadata(args.owner, args.repo, args.pull_number, token)
metadata_file = output_dir / "pr_metadata.json"
with open(metadata_file, "w", encoding="utf-8") as f:
json.dump(pr_metadata, f, indent=2, ensure_ascii=False)
print(f" ✓ Saved to {metadata_file}")
print(" Fetching changed files...")
pr_files = fetch_pr_files(args.owner, args.repo, args.pull_number, token)
files_file = output_dir / "pr_files.json"
with open(files_file, "w", encoding="utf-8") as f:
json.dump(pr_files, f, indent=2, ensure_ascii=False)
print(f" ✓ Saved to {files_file}")
print(" Fetching PR diff...")
try:
pr_diff = fetch_pr_diff(args.owner, args.repo, args.pull_number, token)
diff_file = output_dir / "pr_diff.patch"
with open(diff_file, "w", encoding="utf-8") as f:
f.write(pr_diff)
print(f" ✓ Saved to {diff_file}")
except RuntimeError as e:
print(f" ⚠ Warning: Could not fetch diff - {e}")
if args.include_comments:
print(" Fetching PR comments...")
try:
pr_comments = fetch_pr_comments(args.owner, args.repo, args.pull_number, token)
comments_file = output_dir / "pr_comments.json"
with open(comments_file, "w", encoding="utf-8") as f:
json.dump(pr_comments, f, indent=2, ensure_ascii=False)
print(f" ✓ Saved to {comments_file}")
except RuntimeError as e:
print(f" ⚠ Warning: Could not fetch comments - {e}")
summary = {
"owner": args.owner,
"repo": args.repo,
"pull_number": args.pull_number,
"pr_url": pr_metadata.get("html_url", ""),
"title": pr_metadata.get("title", ""),
"author": pr_metadata.get("user", {}).get("login", ""),
"state": pr_metadata.get("state", ""),
"files_changed": len(pr_files),
"additions": sum(f.get("additions", 0) for f in pr_files),
"deletions": sum(f.get("deletions", 0) for f in pr_files),
"base_branch": pr_metadata.get("base", {}).get("ref", ""),
"head_branch": pr_metadata.get("head", {}).get("ref", ""),
"head_sha": pr_metadata.get("head", {}).get("sha", ""),
"base_sha": pr_metadata.get("base", {}).get("sha", "")
}
summary_file = output_dir / "summary.json"
with open(summary_file, "w", encoding="utf-8") as f:
json.dump(summary, f, indent=2, ensure_ascii=False)
print()
print("=" * 50)
print("PR Summary:")
print("=" * 50)
print(f" Title: {summary['title']}")
print(f" Author: @{summary['author']}")
print(f" State: {summary['state']}")
print(f" Changes: +{summary['additions']}/-{summary['deletions']} in {summary['files_changed']} files")
print(f" Head SHA: {summary['head_sha'][:8] if summary['head_sha'] else 'N/A'}")
print(f" Base SHA: {summary['base_sha'][:8] if summary['base_sha'] else 'N/A'}")
print()
print(f"All data saved to: {output_dir.absolute()}")
except RuntimeError as e:
print(f"\nError: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()