#!/usr/bin/env python3 """Review and sign-off on a Gitea pull request. Supports submitting a review (APPROVE, COMMENT, REQUEST_CHANGES) and optionally merging the pull request in one command. Usage: review_pr.py --pr-number 12 --event APPROVE --body "Approved and signed off" --merge """ import os import sys import json import base64 import argparse import urllib.request import urllib.error # Auto-execute using the project's local virtual environment Python PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) venv_python = os.path.join(PROJECT_ROOT, "venv", "bin", "python3") if os.path.exists(venv_python) and sys.executable != venv_python: os.execv(venv_python, [venv_python] + sys.argv) from gitea_auth import get_auth_header, resolve_remote, add_remote_args, api_request, repo_api_url def main(argv=None): parser = argparse.ArgumentParser(description="Review and sign-off on a Gitea pull request.") add_remote_args(parser) parser.add_argument("--pr-number", type=int, required=True, help="PR number/index to review.") parser.add_argument("--event", choices=["APPROVE", "COMMENT", "REQUEST_CHANGES"], default="APPROVE", help="Review event/action type (default: APPROVE).") parser.add_argument("--body", default="", help="Review body/comment text.") parser.add_argument("--body-file", help="Read review body from this file ('-' for stdin).") parser.add_argument("--merge", action="store_true", help="Automatically merge the PR if approved.") parser.add_argument("--merge-method", choices=["merge", "squash", "rebase"], default="merge", help="Merge method/style to use if merging (default: merge).") args = parser.parse_args(argv) host, org, repo = resolve_remote(args) body = args.body if args.body_file: if args.body_file == "-": body = sys.stdin.read() else: with open(args.body_file, "r", encoding="utf-8") as fh: body = fh.read() auth = get_auth_header(host) if not auth: print(f"Could not get credentials or token for {host}.", file=sys.stderr) return 1 # 1. Fetch PR to get the latest head commit SHA (required for review validation) pr_url = f"{repo_api_url(host, org, repo)}/pulls/{args.pr_number}" try: pr_data = api_request("GET", pr_url, auth) except Exception as e: print(f"Error fetching PR #{args.pr_number}: {e}", file=sys.stderr) return 1 commit_sha = pr_data.get("head", {}).get("sha") if not commit_sha: print(f"Could not find head commit SHA for PR #{args.pr_number}.", file=sys.stderr) return 1 # 2. Submit the PR review review_url = f"{repo_api_url(host, org, repo)}/pulls/{args.pr_number}/reviews" payload = { "body": body, "event": args.event, "commit_id": commit_sha } try: api_request("POST", review_url, auth, payload) print(f"Successfully submitted review for PR #{args.pr_number}: event={args.event}") except Exception as e: print(f"Error submitting review: {e}", file=sys.stderr) return 1 # 3. Merge PR if --merge is requested and event is APPROVE if args.merge: if args.event != "APPROVE": print("Warning: Skipping merge because review event is not 'APPROVE'.", file=sys.stderr) return 0 merge_url = f"{repo_api_url(host, org, repo)}/pulls/{args.pr_number}/merge" merge_payload = { "Do": args.merge_method, "force_merge": False } try: api_request("POST", merge_url, auth, merge_payload) print(f"Successfully merged PR #{args.pr_number} using '{args.merge_method}' method.") except Exception as e: print(f"Error merging PR: {e}", file=sys.stderr) return 1 return 0 if __name__ == "__main__": sys.exit(main())