From 8f32fb0bb4040f5c3085f37d110b4a381fce492c Mon Sep 17 00:00:00 2001 From: "Trez.One" Date: Sun, 19 Oct 2025 10:18:28 -0400 Subject: [PATCH] Initial commit. --- .gitea/workflows/renovate.yml | 66 +++++++++++++++++++++++++++ LICENSE | 21 +++++++++ README.md | 72 ++++++++++++++++++++++++++++++ action.yml | 64 ++++++++++++++++++++++++++ git-auto-comment.py | 84 +++++++++++++++++++++++++++++++++++ renovate.json | 5 +++ 6 files changed, 312 insertions(+) create mode 100644 .gitea/workflows/renovate.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 action.yml create mode 100755 git-auto-comment.py create mode 100644 renovate.json diff --git a/.gitea/workflows/renovate.yml b/.gitea/workflows/renovate.yml new file mode 100644 index 0000000..ea79d93 --- /dev/null +++ b/.gitea/workflows/renovate.yml @@ -0,0 +1,66 @@ +name: Renovate + +on: + schedule: + - cron: "0/30 * * * *" + workflow_dispatch: + +env: + RENOVATE_VERSION: "41.146.8" + +jobs: + renovate: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + + - name: Renovate Run + env: + DOCKER_HOST: tcp://dockerproxy:2375 + RENOVATE_PLATFORM: gitea + RENOVATE_ENDPOINT: https://git.trez.wtf/api/v1 + RENOVATE_TOKEN: ${{ secrets.RENOVATE_BOT_TOKEN }} + LOG_LEVEL: ${{ vars.RENOVATE_LOG_LEVEL }} + RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_TOKEN }} + RENOVATE_CONFIG_FILE: renovate.json + RENOVATE_REPOSITORIES: trez/ultima-ai + RENOVATE_HOST_RULES: | + [ + { + "description": "Docker Hub authentication", + "hostType": "docker", + "matchHost": "docker.io", + "username": "${{ secrets.DOCKERHUB_USER }}", + "password": "${{ secrets.DOCKERHUB_PASSWORD }}" + }, + { + "description": "GitHub Container Registry (GHCR)", + "hostType": "docker", + "matchHost": "ghcr.io", + "username": "${{ secrets.GHCR_USER }}", + "password": "${{ secrets.GHCR_LOGIN_TOKEN }}" + }, + { + "description": "Self-hosted Gitea Docker Registry", + "hostType": "docker", + "matchHost": "git.trez.wtf", + "username": "${{ secrets.BOT_GITEA_USER }}", + "password": "${{ secrets.BOT_GITEA_PASSWORD }}" + } + ] + run: | + docker run --rm \ + -e RENOVATE_PLATFORM \ + -e RENOVATE_ENDPOINT \ + -e RENOVATE_TOKEN \ + -e LOG_LEVEL \ + -e RENOVATE_GITHUB_COM_TOKEN \ + -e RENOVATE_CONFIG_FILE \ + -e RENOVATE_REPOSITORIES \ + -e RENOVATE_HOST_RULES \ + --volumes-from ${{ env.JOB_CONTAINER_NAME }} \ + -w ${GITHUB_WORKSPACE} \ + renovate/renovate:${{ env.RENOVATE_VERSION }}-full + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6f58f6c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Charish Patel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e7bd0e5 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# PR Commenter Diff-Aware Action + +A GitHub Action to automatically post comments to pull requests on **GitHub** or **Gitea**, based on a `git diff`. Supports: + +- Inline comments per changed line +- Multiline comment templates +- Cross-platform support (GitHub and Gitea) +- Diff-aware commenting, automatically detecting added lines +- Default comment template with optional overrides + +--- + +## Features + +- **GitHub & Gitea support**: Works seamlessly on both platforms. +- **Diff-aware**: Parses a `git diff` to determine which lines were added. +- **Inline comments**: Comments appear directly on changed lines in PRs. +- **Multiline templates**: Supports `{line}` for single line and `{lines}` for all added lines in a file. +- **Reusable**: Works as a composite action and can be called from any workflow. + +--- + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `platform` | Target platform: `github` or `gitea` | yes | `github` | +| `token` | API token for authentication | yes | - | +| `api_url` | Gitea API URL (only required for Gitea, e.g., `https://gitea.example.com/api/v1`) | no | - | +| `repo_owner` | Repository owner (user/org) | yes | - | +| `repo_name` | Repository name | yes | - | +| `pr_index` | PR number (GitHub) or index (Gitea) | yes | - | +| `diff` | Git diff to parse (generated via `git diff`) | yes | - | +| `comment_template` | Multiline template for comment body. Use `{line}` for a single line and `{lines}` for all added lines in the file | yes | `Auto-comment: changed line -> {line}` | + +--- + +## Example Usage + +```yaml +jobs: + comment-pr-diff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Generate git diff + id: gitdiff + run: | + git fetch origin main + git diff origin/main > diff.txt + + - name: Post diff-aware comments to GitHub PR + uses: ./ # path to this action + with: + platform: github + token: ${{ secrets.GITHUB_TOKEN }} + repo_owner: ${{ github.repository_owner }} + repo_name: ${{ github.event.repository.name }} + pr_index: ${{ github.event.pull_request.number }} + diff: ${{ steps.gitdiff.outputs.diff }} + comment_template: | + Auto-comment on file: + --- + Changed line: {line} + Full added lines in this file: + {lines} diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..0c94301 --- /dev/null +++ b/action.yml @@ -0,0 +1,64 @@ +name: "PR Commenter Diff-Aware" +description: "Post comments to changed lines in a PR based on a git diff, for GitHub or Gitea. Supports multiline templates." +author: "Trez.One" + +branding: + icon: "paperclip" + color: "blue" + +runs: + using: "composite" + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install dependencies + shell: bash + run: pip install requests + + - name: Run PR commenter + run: python git-auto-comment.py + shell: python + env: + PLATFORM: ${{ inputs.platform }} + TOKEN: ${{ inputs.token }} + API_URL: ${{ inputs.api_url }} + REPO_OWNER: ${{ inputs.repo_owner }} + REPO_NAME: ${{ inputs.repo_name }} + PR_INDEX: ${{ inputs.pr_index }} + DIFF: ${{ inputs.diff }} + COMMENT_TEMPLATE: ${{ inputs.comment_template }} + +inputs: + platform: + description: "Target platform: github or gitea" + required: true + default: "github" + token: + description: "API token for authentication" + required: true + api_url: + description: "Gitea API URL (only required for Gitea, e.g., https://gitea.example.com/api/v1)" + required: false + repo_owner: + description: "Repository owner (user/org)" + required: true + repo_name: + description: "Repository name" + required: true + pr_index: + description: "PR number (GitHub) or index (Gitea)" + required: true + diff: + description: "Git diff to parse, in standard format (generated via git diff)" + required: true + comment_template: + description: | + Multiline template for comment body. + Use placeholders: + - {line} → replaced by single changed line + - {lines} → replaced by all added lines in the file + required: true + default: "Auto-comment: changed line -> {line}" diff --git a/git-auto-comment.py b/git-auto-comment.py new file mode 100755 index 0000000..415e1f2 --- /dev/null +++ b/git-auto-comment.py @@ -0,0 +1,84 @@ +import os +import sys +import re +import json +import requests + +platform = os.environ.get("PLATFORM", "github").lower() +token = os.environ["TOKEN"] +owner = os.environ["REPO_OWNER"] +repo = os.environ["REPO_NAME"] +pr_index = os.environ["PR_INDEX"] +api_url = os.environ.get("API_URL") +diff_text = os.environ.get("DIFF") +comment_template = os.environ.get("COMMENT_TEMPLATE", "") + +if not diff_text: + print("No diff provided", file=sys.stderr) + sys.exit(1) + +# Parse diff +diff_files = {} +current_file = None +new_line_num = None + +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 comments +for file_path, lines in diff_files.items(): + if not lines: + continue + all_lines_content = "\n".join([line_content for _, line_content in lines]) + for line_number, line_content in lines: + body = comment_template.replace("{line}", line_content).replace("{lines}", all_lines_content) + + if platform == "github": + url = f"https://api.github.com/repos/{owner}/{repo}/issues/{pr_index}/comments" + payload = {"body": body} + if file_path and line_number: + url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_index}/comments" + payload.update({ + "body": body, + "path": file_path, + "position": line_number + }) + elif platform == "gitea": + if not api_url: + print("Gitea API URL required", file=sys.stderr) + sys.exit(1) + url = f"{api_url}/repos/{owner}/{repo}/pulls/{pr_index}/comments" + payload = {"body": body} + if file_path and line_number: + payload.update({ + "path": file_path, + "position": line_number + }) + else: + print(f"Unsupported platform: {platform}", file=sys.stderr) + sys.exit(1) + + headers = { + "Authorization": f"token {token}", + "Content-Type": "application/json", + } + + resp = requests.post(url, headers=headers, json=payload) + if resp.status_code in (201, 200): + print(f"Comment posted on {file_path}:{line_number}") + else: + print(f"Failed to post comment on {file_path}:{line_number} ({resp.status_code})", file=sys.stderr) + print(resp.text, file=sys.stderr) diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..4f1aeca --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["local>trez/renovate-config"], + "enabledManagers": ["python"] +}