From 0f936a6047035fb8ebea563e18444acea98304ad Mon Sep 17 00:00:00 2001 From: "Trez.One" Date: Thu, 23 Oct 2025 14:23:49 -0400 Subject: [PATCH] ... --- README.md | 183 +++++++++++--------------------------------- action.yml | 81 ++++++++++++-------- git-auto-comment.py | 162 +++++++++++++++++++++++---------------- 3 files changed, 192 insertions(+), 234 deletions(-) diff --git a/README.md b/README.md index d06623d..40106f6 100644 --- a/README.md +++ b/README.md @@ -1,179 +1,88 @@ -# ๐Ÿงฉ PR Commenter for GitHub & Gitea +# ๐Ÿงฑ Git Auto Comment Action -A composite Action that posts PR comments from **large output files**, such as Terraform/OpenTofu plans, logs, or diffs โ€” directly to **GitHub** or **Gitea** pull requests. - -Itโ€™s based on [`tofu-pr-commenter`](https://github.com/alexnorell/tofu-pr-commenter) but extended for **Gitea compatibility**, **multi-line comment templates**, and **general-purpose content handling** (not just diffs). +Automatically post comments or pull request reviews to **Gitea** or **GitHub**, with optional debug logging and diff parsing. --- ## ๐Ÿš€ Features -- โœ… Works with **GitHub** and **Gitea** PR APIs -- โœ… Handles **large text outputs** (plans, diffs, logs, etc.) -- โœ… Allows **multiline comment templates** with placeholders -- โœ… Defaults to environment variables from the Action runner (no hardcoded repo info required) -- โœ… Can run on **both GitHub Actions** and **Gitea Actions** +- ๐Ÿงฉ Supports **GitHub** and **Gitea** +- ๐Ÿ’ฌ Posts formatted PR comments or reviews +- ๐Ÿชถ Uses `{line}` / `{lines}` placeholders in templates +- ๐Ÿง  Parses diffs to include relevant code changes +- ๐ŸŽจ Optional **colorized debug mode** --- -## โš™๏ธ Inputs +## ๐Ÿงฐ Inputs | Name | Required | Default | Description | |------|-----------|----------|-------------| -| `platform` | โŒ | `github` | Platform type (`github` or `gitea`) | -| `token` | โœ… | โ€” | Access token for API requests (`GITHUB_TOKEN` or personal token) | -| `pr_index` | โœ… | โ€” | Pull request number or index | -| `repo_owner` | โŒ | `${{ github.repository_owner }}` | Repository owner | -| `repo_name` | โŒ | `${{ github.repository }}` | Repository name | -| `api_url` | โŒ | `${{ github.api_url }}` | API URL for Gitea (required only for Gitea) | -| `content` | โŒ | โ€” | Large text input โ€” diff, plan, or log content | -| `comment_template` | โŒ | See below | Comment body template supporting `{line}` and `{lines}` placeholders | - -### ๐Ÿง  Template Variables - -| Placeholder | Description | -|--------------|--------------| -| `{line}` | The specific line being commented on (when parsed from diff-style input) | -| `{lines}` | The full set of added lines for that file or section | +| `platform` | No | `github` | Target platform (`github` or `gitea`) | +| `token` | โœ… | โ€” | API token | +| `repo_owner` | โœ… | โ€” | Repository owner | +| `repo_name` | โœ… | โ€” | Repository name | +| `pr_index` | โœ… | โ€” | Pull request index (Gitea) or issue number (GitHub) | +| `api_url` | No | โ€” | Base API URL (required for Gitea) | +| `diff` | No | โ€” | Diff or plan text to include in the comment | +| `comment_template` | No | `"Auto-comment: changed line -> {line}"` | Template text. Supports `{line}` and `{lines}` placeholders | +| `debug` | No | `false` | Enable verbose, colorized debug logs | --- -## ๐Ÿงฉ Default `comment_template` +## ๐Ÿงฎ Example Usage ```yaml -comment_template: | - Auto-comment: - --- - {line} -``` +name: Auto PR Comment -## ๐Ÿ’ก Example: GitHub Action Workflow -```yaml -name: Comment Terraform Plan on PR on: pull_request: - branches: - - main + types: [opened, synchronize] jobs: - plan-comment: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Run Terraform Plan - id: plan - run: | - terraform plan -no-color > plan.txt - echo "plan_text<> $GITHUB_OUTPUT - cat plan.txt >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Comment Plan on GitHub PR - uses: "https://gitea.example.com/your-org/pr-commenter-action@main" - with: - platform: github - token: ${{ secrets.GITHUB_TOKEN }} - pr_index: ${{ github.event.pull_request.number }} - content: ${{ steps.plan.outputs.plan_text }} - comment_template: | - ๐Ÿš€ **Terraform Plan Summary** - ``` - {lines} - ``` -``` - -## ๐Ÿ’ก Example: Gitea Action Workflow -```yaml -name: Comment Plan on PR (Gitea) -on: - pull_request: - branches: - - main - -jobs: - plan-comment: + comment: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Run Tofu Plan - id: tofu - run: | - tofu plan -no-color > tofu-plan.txt - echo "plan_text<> $GITEA_OUTPUT - cat tofu-plan.txt >> $GITEA_OUTPUT - echo "EOF" >> $GITEA_OUTPUT - - - name: Post Plan to Gitea PR - uses: "https://gitea.example.com/your-org/pr-commenter-action@main" + - name: Post PR Comment + uses: your-org/git-auto-comment@main with: platform: gitea token: ${{ secrets.GITEA_TOKEN }} + repo_owner: "tar-valon" + repo_name: "terraform-configs" + pr_index: ${{ github.event.pull_request.number }} api_url: "https://gitea.example.com/api/v1" - pr_index: ${{ gitea.event.pull_request.number }} - content: ${{ steps.tofu.outputs.plan_text }} + diff: | + + added line one + + added line two comment_template: | - ๐Ÿง  **OpenTofu Plan Diff** + ๐Ÿงฑ **Terraform Plan Output** ``` {lines} ``` + debug: "true" ``` +## ๐Ÿงฉ Debug Mode -## ๐Ÿชต Example: Posting Large Log Output -```yaml -- name: Upload build logs to PR - uses: "https://gitea.example.com/your-org/pr-commenter-action@main" - with: - platform: gitea - token: ${{ secrets.GITEA_TOKEN }} - api_url: "https://gitea.example.com/api/v1" - pr_index: 42 - content: ${{ steps.build.outputs.log }} - comment_template: | - ๐Ÿงพ **Build Log Summary** - ``` - {lines} - ``` -``` +Set `debug: "true"` (or environment variable `DEBUG=true`) to enable verbose, colorized output: +- Prints API endpoint, payload size, and preview +- Truncates long payloads automatically +- Highlights errors and successes in color -## ๐Ÿงฐ Local Development - -You can test the script locally by exporting the necessary environment variables and running: - -``` +## ๐Ÿงช Local Testing +```bash export PLATFORM=gitea -export TOKEN= -export REPO_OWNER=your-org -export REPO_NAME=your-repo +export TOKEN=your_token +export REPO_OWNER=myorg +export REPO_NAME=myrepo export PR_INDEX=42 export API_URL=https://gitea.example.com/api/v1 -export CONTENT="$(cat plan.txt)" -python3 comment_pr.py -``` +export COMMENT_TEMPLATE="Plan output:\n{lines}" +export DIFF="$(git diff HEAD~1)" +export DEBUG=true -## โš ๏ธ Limitations & Tips for Large Files - -1. GitHub & Gitea Comment Limits - - GitHub: max ~65,536 characters per comment. - - Gitea: may vary depending on server configuration. - -2. Chunk Large Content - - For extremely long plans/logs, split content into smaller chunks and post multiple comments. - - Example using shell `split`: - -```bash -split -l 5000 plan.txt plan_chunk_ -for file in plan_chunk_*; do - CONTENT=$(cat "$file") - python3 comment_pr.py ... -done -``` - -3. Diff Parsing - - If your content is a diff, {line} and {lines} placeholders work. - - Otherwise, the action will post the entire content under {lines}. - -4. Avoid Passing Huge Strings via Env Variables - - Always prefer writing output to a file and reading it in the script. \ No newline at end of file +python git-auto-comment.py +``` \ No newline at end of file diff --git a/action.yml b/action.yml index 882253f..97ad919 100644 --- a/action.yml +++ b/action.yml @@ -1,61 +1,78 @@ -name: "PR Commenter (GitHub/Gitea Compatible)" -description: "Posts PR comments to GitHub or Gitea, supporting diffs or large file inputs (plans, logs, etc.)." -author: "Trez.One / AlexNorell (adapted for Gitea)" +name: "Git Auto Comment" +description: "Automatically post pull request comments or reviews on Gitea or GitHub." +author: "Charish Patel" +branding: + icon: "message-square" + color: "blue" inputs: platform: - description: "Target platform: github or gitea" + description: "Platform to use (github or gitea)." required: false default: "github" + token: - description: "Auth token for GitHub or Gitea API" + description: "API token for authentication." required: true - repo_name: - description: "Repository name (defaults to GITHUB_REPOSITORY)" - required: false + repo_owner: - description: "Repository owner (defaults to GITHUB_REPOSITORY_OWNER)" - required: false - api_url: - description: "API base URL (required for Gitea)" - required: false - pr_index: - description: "Pull request index or number" + description: "Repository owner." required: true - plan_file: - description: "Path to file containing large plan/log content" + + repo_name: + description: "Repository name." + required: true + + pr_index: + description: "Pull request index or issue number." + required: true + + api_url: + description: "Base API URL (required for Gitea)." required: false + + diff: + description: "Diff or plan text to include in the comment." + required: false + comment_template: - description: "Template for comment body (supports {line} and {lines})" + description: "Template for comment body. Supports {line} and {lines} placeholders." required: false - default: | - ๐Ÿš€ **Automated Comment** - --- - {lines} + default: "Auto-comment: changed line -> {line}" + + debug: + description: "Enable verbose debug logging with colorized output." + required: false + default: "false" runs: using: "composite" steps: - - name: Set up Python virtual environment + - name: Install Python 3 + shell: bash + run: | + sudo apt-get update -y + sudo apt-get install -y python3 python3-venv python3-pip + + - name: Set up venv and install deps shell: bash run: | python3 -m venv venv source venv/bin/activate - pip install --upgrade pip - pip install -r $GITHUB_ACTION_PATH/requirements.txt - echo "VENV_PATH=$PWD/venv" >> $GITHUB_ENV + pip install -r ${{ github.action_path }}/requirements.txt - name: Run git-auto-comment shell: bash - run: | - source $VENV_PATH/bin/activate - python3 $GITHUB_ACTION_PATH/git-auto-comment.py env: PLATFORM: ${{ inputs.platform }} TOKEN: ${{ inputs.token }} - REPO_NAME: ${{ inputs.repo_name }} REPO_OWNER: ${{ inputs.repo_owner }} - API_URL: ${{ inputs.api_url }} + REPO_NAME: ${{ inputs.repo_name }} PR_INDEX: ${{ inputs.pr_index }} - PLAN_FILE: ${{ inputs.plan_file }} + API_URL: ${{ inputs.api_url }} + DIFF: ${{ inputs.diff }} COMMENT_TEMPLATE: ${{ inputs.comment_template }} + DEBUG: ${{ inputs.debug }} + run: | + source venv/bin/activate + python ${{ github.action_path }}/git-auto-comment.py diff --git a/git-auto-comment.py b/git-auto-comment.py index 6c4b5de..e10a1a5 100755 --- a/git-auto-comment.py +++ b/git-auto-comment.py @@ -3,83 +3,115 @@ import os import sys import re import json +import requests -# --- Check for required dependency --- -try: - import requests -except ImportError: - print( - "โŒ The 'requests' library is not installed.\n" - "Please ensure it's available in your runner.\n" - "If using a composite action, add:\n" - " - name: Install Python dependencies\n" - " run: |\n" - " python3 -m pip install --upgrade pip\n" - " python3 -m pip install requests", - file=sys.stderr, - ) - sys.exit(1) +# --- ANSI color helpers --- +def color(text, code): + return f"\033[{code}m{text}\033[0m" -# --- Read environment variables --- +def green(text): return color(text, "32") +def red(text): return color(text, "31") +def yellow(text): return color(text, "33") +def cyan(text): return color(text, "36") +def bold(text): return color(text, "1") + +# --- Inputs --- platform = os.environ.get("PLATFORM", "github").lower() token = os.environ.get("TOKEN") owner = os.environ.get("REPO_OWNER", os.environ.get("GITHUB_REPOSITORY_OWNER")) repo = os.environ.get("REPO_NAME", os.environ.get("GITHUB_REPOSITORY")) pr_index = os.environ.get("PR_INDEX") api_url = os.environ.get("API_URL", os.environ.get("GITHUB_API_URL")) -plan_file = os.environ.get("PLAN_FILE") -diff_text = os.environ.get("DIFF") # still supported for legacy usage +diff_text = os.environ.get("DIFF") comment_template = os.environ.get("COMMENT_TEMPLATE", "Auto-comment: changed line -> {line}") +debug_mode = os.environ.get("DEBUG", "false").lower() == "true" if not token or not pr_index: - print("โŒ TOKEN and PR_INDEX are required.", file=sys.stderr) + print(red("โŒ TOKEN and PR_INDEX are required.")) sys.exit(1) -# --- Load content from file or environment --- -content_text = None -if plan_file and os.path.exists(plan_file): - try: - with open(plan_file, "r", encoding="utf-8") as f: - content_text = f.read() - except Exception as e: - print(f"โš ๏ธ Failed to read file '{plan_file}': {e}", file=sys.stderr) - sys.exit(1) -elif diff_text: - content_text = diff_text - -# --- Prepare comment body --- -if not content_text: - body = comment_template.replace("{line}", "").replace("{lines}", "") -else: - body = comment_template.replace("{lines}", content_text).replace("{line}", "") - -# --- Determine API endpoint --- -if platform == "github": - url = f"https://api.github.com/repos/{owner}/{repo}/issues/{pr_index}/comments" -elif platform == "gitea": - if not api_url: - print("โŒ Gitea API URL required for Gitea platform", file=sys.stderr) - sys.exit(1) - url = f"{api_url}/repos/{owner}/{repo}/pulls/{pr_index}/comments" -else: - print(f"โŒ Unsupported platform: {platform}", file=sys.stderr) - sys.exit(1) - -# --- Post the comment --- -headers = {"Authorization": f"token {token}", "Content-Type": "application/json"} -payload = {"body": body} - -print(f"๐Ÿ›ฐ๏ธ Posting comment to {platform.upper()} PR #{pr_index}...") - -try: - resp = requests.post(url, headers=headers, json=payload) - if resp.status_code in (200, 201): - print("โœ… Comment posted successfully!") - sys.exit(0) +# --- Build URLs and headers --- +def build_comment_url(): + if platform == "github": + return f"https://api.github.com/repos/{owner}/{repo}/issues/{pr_index}/comments" + elif platform == "gitea": + if not api_url: + print(red("โŒ API_URL required for Gitea platform.")) + sys.exit(1) + return f"{api_url}/repos/{owner}/{repo}/pulls/{pr_index}/reviews" else: - print(f"โŒ Failed to post comment: {resp.status_code}", file=sys.stderr) - print(resp.text, file=sys.stderr) + print(red(f"โŒ Unsupported platform: {platform}")) sys.exit(1) -except requests.exceptions.RequestException as e: - print(f"โŒ Network or API error: {e}", file=sys.stderr) - sys.exit(1) + +def build_headers(): + return { + "Authorization": f"token {token}", + "Content-Type": "application/json", + "Accept": "application/json", + } + +# --- POST comment --- +def post_comment(body_text): + url = build_comment_url() + headers = build_headers() + payload = {"body": body_text} + + if debug_mode: + preview = (body_text[:400] + "... [truncated]") if len(body_text) > 400 else body_text + print(bold(cyan("๐Ÿงฉ DEBUG MODE ENABLED"))) + print(f" {yellow('Platform:')} {platform}") + print(f" {yellow('Repo:')} {owner}/{repo}") + print(f" {yellow('PR Index:')} {pr_index}") + print(f" {yellow('Endpoint:')} {url}") + print(f" {yellow('Comment size:')} {len(body_text)} characters") + print(f" {yellow('Payload preview:')} {json.dumps(payload)[:200]}{'... [truncated]' if len(json.dumps(payload))>200 else ''}") + print(f" {yellow('Comment preview:')}\n{preview}\n{'-'*60}") + + try: + resp = requests.post(url, headers=headers, json=payload, timeout=60) + if resp.status_code in (200, 201): + print(green(f"โœ… Comment posted successfully to {platform.capitalize()}!")) + else: + print(red(f"โŒ Failed to post comment ({resp.status_code}):")) + print(resp.text) + sys.exit(1) + except requests.exceptions.RequestException as e: + print(red(f"โŒ Request error: {e}")) + sys.exit(1) + +# --- No diff provided --- +if not diff_text: + body = comment_template.replace("{line}", "").replace("{lines}", "") + post_comment(body) + sys.exit(0) + +# --- Parse unified diff --- +diff_files = {} +current_file = None +new_line_num = 0 + +for line in diff_text.splitlines(): + if line.startswith("+++ b/"): + current_file = line[6:].strip() + diff_files[current_file] = [] + new_line_num = 0 + elif line.startswith("@@"): + m = re.match(r"@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line) + if m: + new_line_num = int(m.group(1)) - 1 + elif line.startswith("+") and not line.startswith("+++"): + new_line_num += 1 + content = line[1:] + diff_files[current_file].append((new_line_num, content)) + elif not line.startswith("-"): + new_line_num += 1 + +# --- Post combined comment per file --- +for file_path, lines in diff_files.items(): + if not lines: + continue + all_lines_content = "\n".join([line_content for _, line_content in lines]) + body = comment_template.replace("{lines}", all_lines_content) + post_comment(body) + +print(green("๐ŸŽ‰ All comments posted successfully."))