...
Renovate / renovate (push) Has been cancelled

This commit is contained in:
2025-10-23 14:23:49 -04:00
parent b1ee846e9e
commit 0f936a6047
3 changed files with 192 additions and 234 deletions
+46 -137
View File
@@ -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.
Its 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<<EOF" >> $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<<EOF" >> $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=<your_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
python git-auto-comment.py
```
## ⚠️ 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.
+49 -32
View File
@@ -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
+94 -62
View File
@@ -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":
# --- 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(" Gitea API URL required for Gitea platform", file=sys.stderr)
print(red("❌ API_URL required for Gitea platform."))
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)
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)
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."))