#!/bin/bash # pr-review.sh - Review a pull request on GitHub or Gitea # Usage: pr-review.sh -n -a [-c ] set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=packages/mosaic/framework/tools/git/detect-platform.sh source "$SCRIPT_DIR/detect-platform.sh" # Parse arguments PR_NUMBER="" ACTION="" COMMENT="" while [[ $# -gt 0 ]]; do case $1 in -n|--number) PR_NUMBER="$2" shift 2 ;; -a|--action) ACTION="$2" shift 2 ;; -c|--comment) COMMENT="$2" shift 2 ;; -h|--help) echo "Usage: pr-review.sh -n -a [-c ]" echo "" echo "Options:" echo " -n, --number PR number (required)" echo " -a, --action Review action: approve, request-changes, comment (required)" echo " -c, --comment Review comment (required for request-changes)" echo " -h, --help Show this help" exit 0 ;; *) echo "Unknown option: $1" exit 1 ;; esac done if [[ -z "$PR_NUMBER" ]]; then echo "Error: PR number is required (-n)" exit 1 fi if [[ -z "$ACTION" ]]; then echo "Error: Action is required (-a): approve, request-changes, comment" exit 1 fi detect_platform >/dev/null if [[ "$PLATFORM" == "github" ]]; then case $ACTION in approve) gh pr review "$PR_NUMBER" --approve ${COMMENT:+--body "$COMMENT"} echo "Approved GitHub PR #$PR_NUMBER" ;; request-changes) if [[ -z "$COMMENT" ]]; then echo "Error: Comment required for request-changes" exit 1 fi gh pr review "$PR_NUMBER" --request-changes --body "$COMMENT" echo "Requested changes on GitHub PR #$PR_NUMBER" ;; comment) if [[ -z "$COMMENT" ]]; then echo "Error: Comment required" exit 1 fi gh pr review "$PR_NUMBER" --comment --body "$COMMENT" echo "Added review comment to GitHub PR #$PR_NUMBER" ;; *) echo "Error: Unknown action: $ACTION" exit 1 ;; esac elif [[ "$PLATFORM" == "gitea" ]]; then case $ACTION in approve) repo=$(get_repo_slug) host=$(get_remote_host) login=$(get_gitea_login_for_host "$host") tea pr approve "$PR_NUMBER" --repo "$repo" --login "$login" ${COMMENT:+--comment "$COMMENT"} echo "Approved Gitea PR #$PR_NUMBER" ;; request-changes) if [[ -z "$COMMENT" ]]; then echo "Error: Comment required for request-changes" exit 1 fi repo=$(get_repo_slug) host=$(get_remote_host) login=$(get_gitea_login_for_host "$host") tea pr reject "$PR_NUMBER" --repo "$repo" --login "$login" --comment "$COMMENT" echo "Requested changes on Gitea PR #$PR_NUMBER" ;; comment) if [[ -z "$COMMENT" ]]; then echo "Error: Comment required" exit 1 fi host=$(get_remote_host) token=$(get_gitea_token "$host") || { echo "Error: Gitea token not found for comment persistence" >&2 exit 1 } configured_url=$(get_gitea_url_for_host "$host") || { echo "Error: Configured Gitea URL not found for comment persistence" >&2 exit 1 } repo=$(get_gitea_repo_slug_for_url "$configured_url") || { echo "Error: Could not resolve Gitea owner/repository relative to configured URL" >&2 exit 1 } api_base="${configured_url%/}/api/v1/repos/$repo" payload=$(COMMENT_BODY="$COMMENT" python3 -c ' import json import os print(json.dumps({"body": os.environ["COMMENT_BODY"]})) ') write_response_file=$(mktemp "${TMPDIR:-/tmp}/mosaic-pr-review-write.XXXXXX") readback_response_file=$(mktemp "${TMPDIR:-/tmp}/mosaic-pr-review-readback.XXXXXX") trap 'rm -f "$write_response_file" "$readback_response_file"' EXIT if ! write_status=$(curl -sS -o "$write_response_file" -w '%{http_code}' \ -X POST \ -H "Authorization: token $token" \ -H 'Content-Type: application/json' \ -d "$payload" \ "$api_base/issues/$PR_NUMBER/comments"); then echo "Error: Gitea comment write transport failed" >&2 exit 1 fi if [[ "$write_status" != "201" ]]; then echo "Error: Gitea comment write failed with HTTP $write_status" >&2 exit 1 fi comment_id=$(python3 - "$write_response_file" <<'PY' import json import sys try: with open(sys.argv[1], encoding="utf-8") as response: comment = json.load(response) comment_id = comment.get("id") if isinstance(comment, dict) else None if not isinstance(comment_id, int) or comment_id <= 0: raise ValueError("missing positive comment id") except (OSError, json.JSONDecodeError, ValueError) as error: print(f"Error: could not identify created Gitea comment: {error}", file=sys.stderr) raise SystemExit(1) print(comment_id) PY ) if ! readback_status=$(curl -sS -o "$readback_response_file" -w '%{http_code}' \ -H "Authorization: token $token" \ "$api_base/issues/comments/$comment_id"); then echo "Error: Gitea comment read-back transport failed" >&2 exit 1 fi if [[ "$readback_status" != "200" ]]; then echo "Error: Gitea comment read-back failed with HTTP $readback_status" >&2 exit 1 fi EXPECTED_COMMENT_ID="$comment_id" EXPECTED_COMMENT_BODY="$COMMENT" EXPECTED_REPO="$repo" EXPECTED_PR_NUMBER="$PR_NUMBER" \ python3 - "$readback_response_file" <<'PY' import json import os import sys from urllib.parse import urlparse try: with open(sys.argv[1], encoding="utf-8") as response: comment = json.load(response) if not isinstance(comment, dict): raise ValueError("response is not a comment object") expected_id = int(os.environ["EXPECTED_COMMENT_ID"]) expected_body = os.environ["EXPECTED_COMMENT_BODY"] expected_repo = os.environ["EXPECTED_REPO"] expected_pr = os.environ["EXPECTED_PR_NUMBER"] issue_path = urlparse(comment.get("issue_url", "")).path.rstrip("/") expected_suffix = f"/repos/{expected_repo}/issues/{expected_pr}" if comment.get("id") != expected_id: raise ValueError("comment id mismatch") if comment.get("body") != expected_body: raise ValueError("comment body mismatch") if not issue_path.endswith(expected_suffix): raise ValueError("repository or PR mismatch") except (OSError, json.JSONDecodeError, KeyError, TypeError, ValueError) as error: print(f"Error: Gitea comment persistence verification failed: {error}", file=sys.stderr) raise SystemExit(1) PY echo "Added and verified comment on Gitea PR #$PR_NUMBER (comment ID $comment_id)" ;; *) echo "Error: Unknown action: $ACTION" exit 1 ;; esac else echo "Error: Unknown platform" exit 1 fi