Files
grm/scripts/auto_merge.py
T

76 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Auto-merge PR by extracting task ID from branch and validating PR title.
Usage:
GITEA_TOKEN=<token> python3 scripts/auto_merge.py <branch> <pr_title> <repo> <pr_number>
"""
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: <type>: <description>")
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()