82 lines
2.7 KiB
Python
82 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Sync branch protection rules from .gitea/branch-protection/*.yml to Gitea.
|
|
Correctly uses the Gitea 1.25+ API (not GitHub-style).
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import urllib.request
|
|
import yaml
|
|
|
|
GITEA_URL = os.getenv("GITEA_URL", "https://forge.alexanderwhitestone.com")
|
|
GITEA_TOKEN = os.getenv("GITEA_TOKEN", "")
|
|
ORG = "Timmy_Foundation"
|
|
CONFIG_DIR = ".gitea/branch-protection"
|
|
|
|
|
|
def api_request(method: str, path: str, payload: dict | None = None) -> dict:
|
|
url = f"{GITEA_URL}/api/v1{path}"
|
|
data = json.dumps(payload).encode() if payload else None
|
|
req = urllib.request.Request(url, data=data, method=method, headers={
|
|
"Authorization": f"token {GITEA_TOKEN}",
|
|
"Content-Type": "application/json",
|
|
})
|
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
return json.loads(resp.read().decode())
|
|
|
|
|
|
def apply_protection(repo: str, rules: dict) -> bool:
|
|
branch = rules.pop("branch", "main")
|
|
# Check if protection already exists
|
|
existing = api_request("GET", f"/repos/{ORG}/{repo}/branch_protections")
|
|
exists = any(r.get("branch_name") == branch for r in existing)
|
|
|
|
payload = {
|
|
"branch_name": branch,
|
|
"rule_name": branch,
|
|
"required_approvals": rules.get("required_approvals", 1),
|
|
"block_on_rejected_reviews": rules.get("block_on_rejected_reviews", True),
|
|
"dismiss_stale_approvals": rules.get("dismiss_stale_approvals", True),
|
|
"block_deletions": rules.get("block_deletions", True),
|
|
"block_force_push": rules.get("block_force_push", True),
|
|
"block_admin_merge_override": rules.get("block_admin_merge_override", True),
|
|
"enable_status_check": rules.get("require_ci_to_merge", False),
|
|
"status_check_contexts": rules.get("status_check_contexts", []),
|
|
}
|
|
|
|
try:
|
|
if exists:
|
|
api_request("PATCH", f"/repos/{ORG}/{repo}/branch_protections/{branch}", payload)
|
|
else:
|
|
api_request("POST", f"/repos/{ORG}/{repo}/branch_protections", payload)
|
|
print(f"✅ {repo}:{branch} synced")
|
|
return True
|
|
except Exception as e:
|
|
print(f"❌ {repo}:{branch} failed: {e}")
|
|
return False
|
|
|
|
|
|
def main() -> int:
|
|
if not GITEA_TOKEN:
|
|
print("ERROR: GITEA_TOKEN not set")
|
|
return 1
|
|
|
|
ok = 0
|
|
for fname in os.listdir(CONFIG_DIR):
|
|
if not fname.endswith(".yml"):
|
|
continue
|
|
repo = fname[:-4]
|
|
with open(os.path.join(CONFIG_DIR, fname)) as f:
|
|
cfg = yaml.safe_load(f)
|
|
if apply_protection(repo, cfg.get("rules", {})):
|
|
ok += 1
|
|
|
|
print(f"\nSynced {ok} repo(s)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|