71 lines
2.0 KiB
Python
71 lines
2.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Review Gate — Poka-yoke for unreviewed merges.
|
|
Fails if the current PR has fewer than 1 approving review.
|
|
|
|
Usage in Gitea workflow:
|
|
- name: Review Approval Gate
|
|
run: python scripts/review_gate.py
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import subprocess
|
|
from urllib import request, error
|
|
|
|
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "")
|
|
GITEA_URL = os.environ.get("GITEA_URL", "https://forge.alexanderwhitestone.com")
|
|
REPO = os.environ.get("GITEA_REPO", "")
|
|
PR_NUMBER = os.environ.get("PR_NUMBER", "")
|
|
|
|
|
|
def api_call(method, path):
|
|
url = f"{GITEA_URL}/api/v1{path}"
|
|
headers = {"Authorization": f"token {GITEA_TOKEN}"}
|
|
req = request.Request(url, method=method, headers=headers)
|
|
try:
|
|
with request.urlopen(req, timeout=30) as resp:
|
|
return json.loads(resp.read().decode())
|
|
except error.HTTPError as e:
|
|
return {"error": e.read().decode(), "status": e.code}
|
|
|
|
|
|
def main():
|
|
if not GITEA_TOKEN:
|
|
print("ERROR: GITEA_TOKEN not set")
|
|
sys.exit(1)
|
|
|
|
if not REPO:
|
|
print("ERROR: GITEA_REPO not set")
|
|
sys.exit(1)
|
|
|
|
pr_number = PR_NUMBER
|
|
if not pr_number:
|
|
# Try to infer from Gitea Actions environment
|
|
pr_number = os.environ.get("GITEA_PULL_REQUEST_INDEX", "")
|
|
|
|
if not pr_number:
|
|
print("ERROR: Could not determine PR number")
|
|
sys.exit(1)
|
|
|
|
reviews = api_call("GET", f"/repos/{REPO}/pulls/{pr_number}/reviews")
|
|
if isinstance(reviews, dict) and "error" in reviews:
|
|
print(f"ERROR fetching reviews: {reviews}")
|
|
sys.exit(1)
|
|
|
|
approvals = [r for r in reviews if r.get("state") == "APPROVED"]
|
|
if len(approvals) >= 1:
|
|
print(f"OK: PR #{pr_number} has {len(approvals)} approving review(s).")
|
|
sys.exit(0)
|
|
else:
|
|
print(f"BLOCKED: PR #{pr_number} has no approving reviews.")
|
|
print("Merges are not permitted without at least one approval.")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|