#!/usr/bin/env python3 """Auto-merge PR by extracting task ID from branch and validating PR title. Usage: REPO_TOKEN= python3 scripts/auto_merge.py [label_name] """ import os import click from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType] from gitea_runner_manager.api_clients import GiteaClient from gitea_runner_manager.config import CONVENTIONAL_RE, GITEA_API_URL, TASK_ID_RE from gitea_runner_manager.exceptions import APIError from gitea_runner_manager.i18n import _ READY_TO_MERGE = "ready-to-merge" load_dotenv(override=True) 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 ClickException if PR title does not follow conventional commits.""" if not CONVENTIONAL_RE.match(pr_title): raise click.ClickException( _( "Oops! PR title must follow conventional commit format.\n" " Expected: : \n" " Got: {pr_title}", pr_title=pr_title, ) ) def has_ready_to_merge_label(client: GiteaClient, pr_number: str) -> bool: """Check whether the PR has the ready-to-merge label via the API.""" labels = client.get_pr_labels(pr_number) return any(label.get("name") == READY_TO_MERGE for label in labels) @click.command() @click.argument("branch") @click.argument("pr_title") @click.argument("repo") @click.argument("pr_number") @click.argument("label_name", required=False, default="") def main(branch: str, pr_title: str, repo: str, pr_number: str, label_name: str) -> None: token = os.environ.get("REPO_TOKEN", "") if not token: raise click.ClickException(_("ERROR: REPO_TOKEN is not set.")) owner, repo_name = repo.split("/") client = GiteaClient(GITEA_API_URL, token, owner, repo_name) # Gitea Actions may not populate github.event.label.name; fall back to API check. if label_name != READY_TO_MERGE and not has_ready_to_merge_label(client, pr_number): click.echo(_("Label '{label}' is not '{rtm}', skipping.", label=label_name, rtm=READY_TO_MERGE)) return task_id = extract_task_id(branch) if not task_id: raise click.ClickException( _( "Oops! No task ID (GRM-N) found in branch name '{branch}'.", branch=branch, ) ) validate_pr_title(pr_title) merge_title = f"{task_id}: {pr_title}" try: client.merge_pr(pr_number, merge_title) except APIError as e: raise click.ClickException( _( "Merge failed with HTTP {status}: {message}\n" "Please check the PR is ready and you have merge rights.", status=e.status, message=e.message, ) ) from None click.echo( _( "Nice! PR #{pr_number} squash-merged with title: {merge_title}", pr_number=pr_number, merge_title=merge_title, ) ) if __name__ == "__main__": # pragma: no cover main()