"""Compatibility hardening for Gitea validation discovery and request latency. Some Gitea versions expose pull-request check results as commit statuses while returning an empty branch-filtered Actions run wrapper. The base client treats collections strictly; this subclass preserves that strictness while translating a fully-green exact-head status set into one synthetic completed validation run. Large repositories can require longer than the historical 15-second client budget to commit files, apply changesets, or materialize a server-side squash merge. The resilient client therefore uses the already allowlisted 60-second maximum from ``GiteaSettings``. All HTTP method allowlisting, redirect handling, response budgets, access checks and non-forcing merge behavior remain in the base client and are not duplicated here. """ from __future__ import annotations from typing import Any import httpx from .gitea import GiteaClient as BaseGiteaClient from .gitea import GiteaError, GiteaSettings, RegisteredRepository, TokenProvider GITEA_REQUEST_TIMEOUT_SECONDS = 60.0 class GiteaClient(BaseGiteaClient): """Gitea client with exact-head validation fallback and bounded latency.""" def __init__( self, settings: GiteaSettings, token_provider: TokenProvider, *, transport: httpx.BaseTransport | None = None, ) -> None: resilient_settings = settings.model_copy( update={"timeout_seconds": GITEA_REQUEST_TIMEOUT_SECONDS} ) super().__init__(resilient_settings, token_provider, transport=transport) def action_runs( self, repository: RegisteredRepository, *, branch: str, page: int, limit: int, ) -> list[dict[str, Any]]: payload = self._request( self._path("repos", repository.owner, repository.name, "actions", "runs"), {"branch": branch, "page": page, "limit": limit}, ) if isinstance(payload, dict): if "workflow_runs" in payload: payload = payload.get("workflow_runs") elif "runs" in payload: payload = payload.get("runs") else: payload = None runs = self._as_list(payload) if runs: return runs # Only an actually empty Actions result can fall back. Malformed # collections still fail above, preserving strict response validation. try: branch_payload = self.branch(repository, branch) except GiteaError: return [] commit = branch_payload.get("commit") if not isinstance(commit, dict): return [] head_sha = str(commit.get("id") or commit.get("sha") or "") if len(head_sha) != 40 or any( character not in "0123456789abcdefABCDEF" for character in head_sha ): return [] statuses = self.commit_statuses(repository, head_sha, 1, 100) if not statuses: return [] latest_by_context: dict[str, dict[str, Any]] = {} for item in statuses: status_context = str(item.get("context") or "__default__") previous = latest_by_context.get(status_context) if previous is None or str(item.get("created_at") or "") > str( previous.get("created_at") or "" ): latest_by_context[status_context] = item latest_states = { str(item.get("status") or "").casefold() for item in latest_by_context.values() } # Gitea can omit ``head_branch`` for pull_request runs, causing the # branch-filtered run list above to be empty even though the exact-head # commit statuses are available. Skipped jobs are neutral evidence; # require at least one success and reject every non-terminal state. if "success" not in latest_states or not latest_states <= {"success", "skipped"}: return [] return [ { "id": 0, "name": "Exact-head commit status validation", "status": "completed", "conclusion": "success", "event": "commit_status_fallback", "head_branch": branch, "head_sha": head_sha, "html_url": "", "validation_evidence": "commit-statuses", } ]