167 lines
5.5 KiB
Python
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())
|