2026-03-12 17:23:08 +08:00

167 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""List, create, and comment on Gitea pull requests."""
from __future__ import annotations
import argparse
import json
import sys
from common import (
api_base,
current_branch,
load_token,
remote_default_branch,
request_json,
resolve_repo,
)
from push_gitea import compute_push_plan, execute_push
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="List, create, and comment on Gitea pull requests."
)
parser.add_argument("--repo-url", help="Explicit target repo URL.")
parser.add_argument("--repo", help="Shorthand owner/repo. Requires GITEA_BASE_URL.")
parser.add_argument("--remote", default="origin", help="Git remote name. Default: origin")
subparsers = parser.add_subparsers(dest="command", required=True)
list_parser = subparsers.add_parser("list", help="List pull requests.")
list_parser.add_argument(
"--state",
default="open",
choices=("open", "closed", "all"),
help="Pull request state filter.",
)
list_parser.add_argument(
"--limit",
default=20,
type=int,
help="Maximum number of pull requests to return.",
)
create_parser = subparsers.add_parser("create", help="Create a pull request.")
create_parser.add_argument("--base", help="Base branch. Default: remote default branch.")
create_parser.add_argument("--head", help="Head branch. Default: current branch.")
create_parser.add_argument("--title", required=True, help="Pull request title.")
create_parser.add_argument("--body", default="", help="Pull request body.")
comment_parser = subparsers.add_parser("comment", help="Comment on a pull request.")
comment_parser.add_argument("pr", type=int, help="Pull request number.")
comment_parser.add_argument("--body", required=True, help="Comment body.")
return parser.parse_args()
def summarize_pull_request(item: dict) -> dict:
user = item.get("user") or {}
head = item.get("head") or {}
base = item.get("base") or {}
return {
"number": item.get("number"),
"title": item.get("title"),
"state": item.get("state"),
"html_url": item.get("html_url"),
"author": user.get("full_name") or user.get("login"),
"head": head.get("ref"),
"base": base.get("ref"),
"created_at": item.get("created_at"),
"updated_at": item.get("updated_at"),
}
def list_pull_requests(args: argparse.Namespace) -> dict:
context = resolve_repo(repo_url=args.repo_url, repo=args.repo, remote=args.remote)
token = load_token(required=True)
url = f"{api_base(context)}/pulls?state={args.state}&page=1&limit={args.limit}"
data = request_json(url, token)
if not isinstance(data, list):
raise SystemExit("Unexpected pull request list response.")
return {
"action": "list",
"repo_url": context.repo_url,
"state": args.state,
"limit": args.limit,
"count": len(data),
"pull_requests": [summarize_pull_request(item) for item in data],
}
def create_pull_request(args: argparse.Namespace) -> dict:
context = resolve_repo(repo_url=args.repo_url, repo=args.repo, remote=args.remote)
token = load_token(required=True)
head = args.head or current_branch()
base = args.base or remote_default_branch(args.remote)
push_plan = compute_push_plan(
repo_url=args.repo_url,
repo=args.repo,
remote=args.remote,
branch=head,
force=False,
)
push_result = None
if push_plan["needs_push"]:
push_result = execute_push(push_plan, force=False)
elif push_plan["status"] == "blocked":
raise SystemExit(
"Cannot create PR because the current branch has diverged from the remote branch."
)
payload = {"base": base, "head": head, "title": args.title, "body": args.body}
created = request_json(f"{api_base(context)}/pulls", token, method="POST", payload=payload)
if not isinstance(created, dict):
raise SystemExit("Unexpected pull request creation response.")
return {
"action": "create",
"repo_url": context.repo_url,
"base": base,
"head": head,
"push": push_result,
"pull_request": summarize_pull_request(created),
}
def comment_pull_request(args: argparse.Namespace) -> dict:
context = resolve_repo(repo_url=args.repo_url, repo=args.repo, remote=args.remote)
token = load_token(required=True)
payload = {"body": args.body}
created = request_json(
f"{api_base(context)}/issues/{args.pr}/comments",
token,
method="POST",
payload=payload,
)
if not isinstance(created, dict):
raise SystemExit("Unexpected PR comment response.")
user = created.get("user") or {}
return {
"action": "comment",
"repo_url": context.repo_url,
"pull_request": args.pr,
"comment": {
"id": created.get("id"),
"html_url": created.get("html_url"),
"author": user.get("full_name") or user.get("login"),
"created_at": created.get("created_at"),
},
}
def main() -> int:
args = parse_args()
if args.command == "list":
payload = list_pull_requests(args)
elif args.command == "create":
payload = create_pull_request(args)
else:
payload = comment_pull_request(args)
print(json.dumps(payload, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())