Files
grm/scripts/auto_merge.py
T
emil 0fadb7c504
Post-merge Vikunja update / vikunja (push) Failing after 5s
CI / quality (push) Successful in 1m3s
CI / molecule-tests (0) (push) Failing after 3m35s
CI / molecule-tests (1) (push) Failing after 5m53s
CI / molecule-tests (2) (push) Failing after 5m59s
GRM-27: fix: CI workflows for rootless runner compatibility
2026-06-20 17:01:55 +00:00

103 lines
3.2 KiB
Python

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