#!/usr/bin/env python3 """Auto-merge PR by extracting task ID from branch and validating PR title. Usage: GITEA_TOKEN= python3 scripts/auto_merge.py """ import argparse import os import re import sys import requests GITEA_API = "https://git.oblachno.oblachno.fyi/api/v1" TASK_ID_RE = re.compile(r"GRM-\d+") CONVENTIONAL_RE = re.compile( r"^(feat|fix|chore|docs|style|refactor|perf|test|ci|build|revert|BREAKING CHANGE)(\(.+\))?: .+" ) def extract_task_id(branch: str) -> str: """Extract GRM-N task identifier from branch name.""" match = TASK_ID_RE.search(branch) return match.group(0) if match else "" def validate_pr_title(pr_title: str) -> None: """Raise SystemExit if PR title does not follow conventional commits.""" if not CONVENTIONAL_RE.match(pr_title): print("ERROR: PR title must follow conventional commit format.") print(" Expected: : ") print(f" Got: {pr_title}") sys.exit(1) def merge_pr(token: str, repo: str, pr_number: str, merge_title: str) -> None: """Call Gitea API to squash-merge the PR.""" url = f"{GITEA_API}/repos/{repo}/pulls/{pr_number}/merge" headers = { "Authorization": f"token {token}", "Content-Type": "application/json", } payload = {"Do": "squash", "MergeTitleField": merge_title} response = requests.post(url, headers=headers, json=payload, timeout=30) response.raise_for_status() def main(args: list[str] | None = None) -> None: # pragma: no cover argv = args if args is not None else sys.argv parser = argparse.ArgumentParser(description="Auto-merge a PR with task ID") parser.add_argument("branch", help="Source branch name") parser.add_argument("pr_title", help="Pull request title") parser.add_argument("repo", help="Repository full name (owner/repo)") parser.add_argument("pr_number", help="Pull request number") parsed = parser.parse_args(argv[1:]) token = os.environ.get("GITEA_TOKEN", "") if not token: print("ERROR: GITEA_TOKEN is not set.", file=sys.stderr) sys.exit(1) task_id = extract_task_id(parsed.branch) if not task_id: print(f"ERROR: No task ID (GRM-N) found in branch name '{parsed.branch}'") sys.exit(1) validate_pr_title(parsed.pr_title) merge_title = f"{task_id}: {parsed.pr_title}" merge_pr(token, parsed.repo, parsed.pr_number, merge_title) print(f"PR #{parsed.pr_number} squash-merged with title: {merge_title}") if __name__ == "__main__": # pragma: no cover main()