Provider-agnostische LLM-Anbindung (Anthropic jetzt, Jetson/Ollama später per Env-Var umschaltbar). Stdlib-only Skripte + Beispiel-Workflows. GitLab-Duo-Parität für self-hosted Gitea + act_runner. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
2.6 KiB
Python
71 lines
2.6 KiB
Python
"""Schlanke Gitea-API-Helfer (stdlib only).
|
|
|
|
- PR-Diff holen
|
|
- Kommentare IDEMPOTENT posten: ein versteckter HTML-Marker identifiziert den
|
|
Bot-Kommentar; existiert er schon, wird er editiert statt neu angelegt
|
|
(kein Zuspammen bei jedem Push).
|
|
|
|
Env-Vars:
|
|
GITEA_API Basis-URL der API, Default https://git.eulernest.eu/api/v1
|
|
GITEA_TOKEN Token fürs Kommentieren (Actions-Auto-Token ODER PAT).
|
|
Fallback: GITEA_BOT_TOKEN.
|
|
"""
|
|
import json
|
|
import os
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
_BASE = os.environ.get("GITEA_API", "https://git.eulernest.eu/api/v1").rstrip("/")
|
|
_TOKEN = os.environ.get("GITEA_TOKEN") or os.environ.get("GITEA_BOT_TOKEN", "")
|
|
|
|
|
|
def _request(method: str, path: str, *, data=None, raw: bool = False):
|
|
url = f"{_BASE}{path}"
|
|
headers = {}
|
|
if _TOKEN:
|
|
headers["Authorization"] = f"token {_TOKEN}"
|
|
body = None
|
|
if data is not None:
|
|
body = json.dumps(data).encode("utf-8")
|
|
headers["Content-Type"] = "application/json"
|
|
req = urllib.request.Request(url, data=body, headers=headers, method=method)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
content = resp.read().decode("utf-8", "replace")
|
|
if raw:
|
|
return content
|
|
return json.loads(content) if content.strip() else None
|
|
except urllib.error.HTTPError as exc:
|
|
detail = exc.read().decode("utf-8", "replace")
|
|
raise SystemExit(f"[gitea] {method} {path} -> HTTP {exc.code}: {detail[:400]}")
|
|
except urllib.error.URLError as exc:
|
|
raise SystemExit(f"[gitea] {method} {path} fehlgeschlagen: {exc.reason}")
|
|
|
|
|
|
def get_pr_diff(owner: str, repo: str, index: int) -> str:
|
|
"""Unified Diff eines PRs (Gitea unterstützt den .diff-Suffix)."""
|
|
return _request("GET", f"/repos/{owner}/{repo}/pulls/{index}.diff", raw=True)
|
|
|
|
|
|
def upsert_comment(owner: str, repo: str, index: int, marker: str, body: str) -> int:
|
|
"""Legt einen Kommentar an ODER editiert den vorhandenen mit gleichem Marker.
|
|
|
|
PR-Kommentare sind in Gitea Issue-Kommentare (gleicher Endpunkt).
|
|
"""
|
|
full = f"{body}\n\n{marker}"
|
|
existing = _request("GET", f"/repos/{owner}/{repo}/issues/{index}/comments") or []
|
|
for comment in existing:
|
|
if marker in (comment.get("body") or ""):
|
|
_request(
|
|
"PATCH",
|
|
f"/repos/{owner}/{repo}/issues/comments/{comment['id']}",
|
|
data={"body": full},
|
|
)
|
|
return comment["id"]
|
|
created = _request(
|
|
"POST",
|
|
f"/repos/{owner}/{repo}/issues/{index}/comments",
|
|
data={"body": full},
|
|
)
|
|
return created["id"]
|