"""Bounded helpers for destructive Gitea pull-request lifecycle cleanup.""" from __future__ import annotations import re from typing import Any, Protocol from packages.connector_kit import GiteaError, RegisteredRepository _VIRTUAL_PULL_HEAD = re.compile(r"refs/pull/[1-9][0-9]*/head") class PullRequestReader(Protocol): def pull_requests( self, repository: RegisteredRepository, page: int, limit: int ) -> list[dict[str, Any]]: ... def cleanup_basis( client: PullRequestReader, repository: RegisteredRepository, branch: str, expected_head_sha: str, ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]: """Return the merged/closed PR proving one exact branch is cleanup-eligible.""" related: list[dict[str, Any]] = [] for page_number in range(1, 11): pulls = client.pull_requests(repository, page_number, 100) for item in pulls: head = item.get("head") or {} head_ref = str(head.get("ref", "")) head_sha = str(head.get("sha", "")) if head_ref == branch or ( head_sha == expected_head_sha and _VIRTUAL_PULL_HEAD.fullmatch(head_ref) ): related.append(item) if len(pulls) < 100: break if any(str(item.get("state", "")) == "open" for item in related): raise GiteaError( "GITEA_TARGET_INVALID", "branch is still referenced by an open pull request; close or merge it first", ) merged = next((item for item in related if bool(item.get("merged", False))), None) closed = next( ( item for item in related if str(item.get("state", "")) == "closed" and not bool(item.get("merged", False)) ), None, ) if merged is None and closed is None: raise GiteaError( "GITEA_TARGET_INVALID", "only a change branch from a merged or closed pull request can be deleted", ) return merged, closed