60 lines
2.0 KiB
Python
60 lines
2.0 KiB
Python
"""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
|