Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:36:21 +02:00
commit cb5be898ea
663 files changed
+82674

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
"""Gitea read-only connector package."""
from .changesets import DulwichChangesetWorkspace
from .fake import FakeGiteaServer
from .server import GITEA_RESOURCES, GITEA_TOOLS, GiteaConnector
__all__ = [
"DulwichChangesetWorkspace",
"FakeGiteaServer",
"GITEA_RESOURCES",
"GITEA_TOOLS",
"GiteaConnector",
]
+341
View File
@@ -0,0 +1,341 @@
"""Bounded patching and single-commit Git workspaces for Gitea changesets."""
from __future__ import annotations
import io
import os
import re
import tempfile
from collections.abc import Iterator, Mapping, Sequence
from contextlib import contextmanager
from pathlib import Path
from threading import Lock
from typing import Any, Protocol
from dulwich import porcelain
from dulwich.repo import Repo
from packages.connector_kit import GiteaError, RegisteredRepository
from packages.connector_kit.gitea import TokenProvider
_HUNK = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(?: .*)?$")
_SHA = re.compile(r"^[0-9a-fA-F]{40,64}$")
_MAX_CHANGESET_BYTES = 1_000_000
_MAX_OPERATIONS = 50
_MAX_WORKSPACE_BYTES = 256 * 1024 * 1024
_DULWICH_IDENTITY_LOCK = Lock()
_DULWICH_IDENTITY = {"USER": "mcp-agent", "EMAIL": "mcp-agent@example.com"}
@contextmanager
def _dulwich_clone_identity() -> Iterator[None]:
"""Give Dulwich a bounded reflog identity without mutating process state concurrently."""
with _DULWICH_IDENTITY_LOCK:
previous = {name: os.environ.get(name) for name in _DULWICH_IDENTITY}
os.environ.update(_DULWICH_IDENTITY)
try:
yield
finally:
for name, value in previous.items():
if value is None:
os.environ.pop(name, None)
else:
os.environ[name] = value
def _hex_object_id(value: bytes) -> str:
try:
decoded = value.decode("ascii")
except UnicodeDecodeError:
return value.hex()
return decoded.casefold() if _SHA.fullmatch(decoded) else value.hex()
class ChangesetWorkspace(Protocol):
def apply(
self,
repository: RegisteredRepository,
branch: str,
message: str,
operations: Sequence[Mapping[str, Any]],
) -> dict[str, Any]: ...
def apply_unified_patch(original: str, patch: str) -> str:
"""Apply a single-file unified diff with exact context matching."""
if not patch or len(patch.encode("utf-8")) > 300_000 or "\x00" in patch:
raise GiteaError("GITEA_PATCH_INVALID", "patch is outside the connector budget")
lines = patch.splitlines(keepends=True)
index = 0
while index < len(lines) and not lines[index].startswith("@@ "):
if lines[index].startswith(("GIT binary patch", "Binary files ")):
raise GiteaError("GITEA_PATCH_INVALID", "binary patches are not supported")
index += 1
if index == len(lines):
raise GiteaError("GITEA_PATCH_INVALID", "patch contains no unified diff hunk")
source = original.splitlines(keepends=True)
output: list[str] = []
source_index = 0
seen_hunk = False
while index < len(lines):
header = lines[index].rstrip("\r\n")
match = _HUNK.fullmatch(header)
if match is None:
raise GiteaError("GITEA_PATCH_INVALID", "patch contains an invalid hunk header")
seen_hunk = True
old_start = int(match.group(1))
old_count = int(match.group(2) or "1")
new_count = int(match.group(4) or "1")
target_index = max(old_start - 1, 0)
if target_index < source_index or target_index > len(source):
raise GiteaError("GITEA_PATCH_CONFLICT", "patch hunks overlap or exceed the file")
output.extend(source[source_index:target_index])
source_index = target_index
index += 1
consumed = 0
produced = 0
while index < len(lines) and not lines[index].startswith("@@ "):
line = lines[index]
if line.startswith("\\ No newline at end of file"):
index += 1
continue
if not line or line[0] not in {" ", "+", "-"}:
raise GiteaError("GITEA_PATCH_INVALID", "patch contains an invalid hunk line")
marker, value = line[0], line[1:]
if marker in {" ", "-"}:
if source_index >= len(source) or source[source_index] != value:
raise GiteaError("GITEA_PATCH_CONFLICT", "patch context does not match")
source_index += 1
consumed += 1
if marker in {" ", "+"}:
output.append(value)
produced += 1
index += 1
if consumed != old_count or produced != new_count:
raise GiteaError("GITEA_PATCH_INVALID", "patch hunk counts do not match")
if not seen_hunk:
raise GiteaError("GITEA_PATCH_INVALID", "patch contains no hunks")
output.extend(source[source_index:])
result = "".join(output)
if len(result.encode("utf-8")) > 200_000:
raise GiteaError("GITEA_INPUT_INVALID", "patched file exceeds the connector budget")
return result
def validate_operations(operations: Sequence[Mapping[str, Any]]) -> tuple[dict[str, Any], ...]:
if not 1 <= len(operations) <= _MAX_OPERATIONS:
raise GiteaError("GITEA_CHANGESET_INVALID", "changeset operation count is invalid")
total = 0
normalized: list[dict[str, Any]] = []
touched: set[str] = set()
for raw in operations:
kind = str(raw.get("kind", ""))
if kind not in {"create", "update", "delete", "move", "patch"}:
raise GiteaError("GITEA_CHANGESET_INVALID", "changeset operation kind is invalid")
path = str(raw.get("path", ""))
if not path or path in touched:
raise GiteaError("GITEA_CHANGESET_INVALID", "changeset paths must be unique")
touched.add(path)
operation: dict[str, Any] = {"kind": kind, "path": path}
for field in ("content", "patch", "expectedSha", "destinationPath"):
if field in raw and raw[field] is not None:
operation[field] = str(raw[field])
if kind in {"update", "delete", "move", "patch"} and not _SHA.fullmatch(
operation.get("expectedSha", "")
):
raise GiteaError("GITEA_CHANGESET_INVALID", "expectedSha is required and invalid")
if kind in {"create", "update"} and "content" not in operation:
raise GiteaError("GITEA_CHANGESET_INVALID", "content is required")
if kind == "patch" and "patch" not in operation:
raise GiteaError("GITEA_CHANGESET_INVALID", "patch text is required")
if kind == "move" and not operation.get("destinationPath"):
raise GiteaError("GITEA_CHANGESET_INVALID", "destinationPath is required")
destination = operation.get("destinationPath")
if destination is not None:
if destination in touched:
raise GiteaError("GITEA_CHANGESET_INVALID", "changeset paths must be unique")
touched.add(destination)
total += sum(len(str(value).encode("utf-8")) for value in operation.values())
normalized.append(operation)
if total > _MAX_CHANGESET_BYTES:
raise GiteaError("GITEA_CHANGESET_INVALID", "changeset exceeds the connector budget")
return tuple(normalized)
class DulwichChangesetWorkspace:
"""Apply an already-validated changeset through a fixed, isolated Git workflow."""
def __init__(self, base_url: str, token_provider: TokenProvider, token_reference: str) -> None:
self._base_url = base_url.rstrip("/")
self._token_provider = token_provider
self._token_reference = token_reference
@staticmethod
def _relative(root: Path, value: str) -> Path:
if not value or value.startswith(("/", "\\")) or "\\" in value:
raise GiteaError("GITEA_PATH_INVALID", "repository file path is invalid")
parts = value.split("/")
if any(not part or part in {".", ".."} or "\x00" in part for part in parts):
raise GiteaError("GITEA_PATH_INVALID", "repository file path is invalid")
target = root.joinpath(*parts)
try:
target.resolve(strict=False).relative_to(root.resolve())
except ValueError as exc:
raise GiteaError("GITEA_PATH_INVALID", "repository file path is invalid") from exc
return target
@staticmethod
def _blob_sha(repo: Repo, relative: str) -> str | None:
try:
entry = repo.open_index()[relative.encode("utf-8")]
except KeyError:
return None
sha = getattr(entry, "sha", None)
return _hex_object_id(sha) if sha is not None else None
def apply(
self,
repository: RegisteredRepository,
branch: str,
message: str,
operations: Sequence[Mapping[str, Any]],
) -> dict[str, Any]:
normalized = validate_operations(operations)
token = self._token_provider.get_token(self._token_reference)
remote = f"{self._base_url}/{repository.owner}/{repository.name}.git"
authenticated = remote.startswith(("https://", "http://"))
stderr = io.BytesIO()
with tempfile.TemporaryDirectory(prefix="itworx-gitea-") as temporary:
root = Path(temporary) / "repository"
repo: Repo | None = None
try:
with _dulwich_clone_identity():
if authenticated:
repo = porcelain.clone(
remote,
root,
branch=branch,
depth=1,
errstream=stderr,
username="oauth2",
password=token,
)
else:
repo = porcelain.clone(
remote, root, branch=branch, depth=1, errstream=stderr
)
workspace_bytes = sum(
entry.stat(follow_symlinks=False).st_size
for entry in root.rglob("*")
if entry.is_file() and not entry.is_symlink()
)
if workspace_bytes > _MAX_WORKSPACE_BYTES:
raise GiteaError(
"GITEA_CHANGESET_TOO_LARGE",
"repository exceeds the changeset workspace budget",
)
staged: list[str] = []
removed: list[str] = []
for operation in normalized:
kind = operation["kind"]
relative = operation["path"]
target = self._relative(root, relative)
actual_sha = self._blob_sha(repo, relative)
expected_sha = operation.get("expectedSha")
if expected_sha is not None and actual_sha != expected_sha.casefold():
raise GiteaError(
"GITEA_CHANGESET_CONFLICT", f"stale expectedSha for {relative}"
)
if kind == "create":
if actual_sha is not None or target.exists():
raise GiteaError(
"GITEA_CHANGESET_CONFLICT", f"file already exists: {relative}"
)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(operation["content"], encoding="utf-8")
staged.append(relative)
elif kind == "update":
if actual_sha is None:
raise GiteaError(
"GITEA_CHANGESET_CONFLICT", f"file does not exist: {relative}"
)
target.write_text(operation["content"], encoding="utf-8")
staged.append(relative)
elif kind == "patch":
if actual_sha is None:
raise GiteaError(
"GITEA_CHANGESET_CONFLICT", f"file does not exist: {relative}"
)
original = target.read_text(encoding="utf-8")
target.write_text(
apply_unified_patch(original, operation["patch"]), encoding="utf-8"
)
staged.append(relative)
elif kind == "delete":
if actual_sha is None:
raise GiteaError(
"GITEA_CHANGESET_CONFLICT", f"file does not exist: {relative}"
)
target.unlink()
removed.append(relative)
else:
destination = str(operation["destinationPath"])
destination_target = self._relative(root, destination)
if actual_sha is None or destination_target.exists():
raise GiteaError(
"GITEA_CHANGESET_CONFLICT", "move source/destination is invalid"
)
destination_target.parent.mkdir(parents=True, exist_ok=True)
os.replace(target, destination_target)
removed.append(relative)
staged.append(destination)
if removed:
porcelain.rm(repo, removed)
if staged:
porcelain.add(repo, staged)
status = porcelain.status(repo)
if not status.staged and not status.unstaged and not status.untracked:
raise GiteaError("GITEA_CHANGESET_EMPTY", "changeset produces no change")
commit_sha = _hex_object_id(
porcelain.commit(
repo,
message=message.encode("utf-8"),
author=b"ChatGPT MCP <mcp-agent@example.com>",
committer=b"ChatGPT MCP <mcp-agent@example.com>",
)
)
if authenticated:
porcelain.push(
repo,
remote,
refspecs=f"refs/heads/{branch}:refs/heads/{branch}",
force=False,
errstream=stderr,
username="oauth2",
password=token,
)
else:
porcelain.push(
repo,
remote,
refspecs=f"refs/heads/{branch}:refs/heads/{branch}",
force=False,
errstream=stderr,
)
except GiteaError:
raise
except Exception as exc:
raise GiteaError(
"GITEA_CHANGESET_FAILED", "bounded Git changeset could not be committed"
) from exc
finally:
if repo is not None:
repo.close()
return {
"commitSha": commit_sha,
"branch": branch,
"operationCount": len(normalized),
"atomic": True,
}
+675
View File
@@ -0,0 +1,675 @@
"""Deterministic in-memory Gitea HTTP transport for contract tests."""
from __future__ import annotations
import base64
import json
from typing import Any
from urllib.parse import parse_qs, unquote, urlparse
import httpx
from .managed_validation import (
MANAGED_VALIDATION_CONTEXT,
MANAGED_VALIDATION_PATH,
MANAGED_VALIDATION_WORKFLOW,
)
class FakeGiteaServer:
"""A tiny bounded Gitea API fixture with observable requests and no network."""
def __init__(
self,
*,
unsupported_search: bool = False,
deny_user_repositories: bool = False,
deny_protected_workflow_writes: bool = False,
redirect_to: str | None = None,
gitea_version: str = "1.26.4",
deny_merge_transition: bool = False,
) -> None:
self.unsupported_search = unsupported_search
self.deny_user_repositories = deny_user_repositories
self.deny_protected_workflow_writes = deny_protected_workflow_writes
self.redirect_to = redirect_to
self.gitea_version = gitea_version
self.deny_merge_transition = deny_merge_transition
self.recorded_methods: list[str] = []
self.recorded_authorizations: list[str] = []
self.recorded_paths: list[str] = []
self.recorded_bodies: list[dict[str, Any]] = []
self._repos = {
"acme/hub": {
"owner": {"login": "acme"},
"name": "hub",
"full_name": "acme/hub",
"description": "ITWorx MCP Hub",
"private": True,
"default_branch": "main",
"updated_at": "2026-08-01T10:00:00Z",
"token": "fixture-token-must-not-leak",
}
}
self._branches: list[dict[str, Any]] = [
{"name": "main", "commit": {"id": "a" * 40}, "protected": True},
{"name": "release", "commit": {"id": "b" * 40}, "protected": False},
{"name": "feature", "commit": {"id": "c" * 40}, "protected": False},
]
self._branch_protections: dict[str, list[dict[str, Any]]] = {
"acme/hub": [
{
"rule_name": "main",
"branch_name": "main",
"priority": 1,
"enable_push": False,
"enable_push_whitelist": False,
"push_whitelist_usernames": [],
"push_whitelist_teams": [],
"push_whitelist_deploy_keys": False,
"enable_force_push": False,
"enable_force_push_allowlist": False,
"force_push_allowlist_usernames": [],
"force_push_allowlist_teams": [],
"force_push_allowlist_deploy_keys": False,
"enable_merge_whitelist": False,
"merge_whitelist_usernames": [],
"merge_whitelist_teams": [],
"enable_bypass_allowlist": False,
"bypass_allowlist_usernames": [],
"bypass_allowlist_teams": [],
"enable_status_check": True,
"status_check_contexts": [MANAGED_VALIDATION_CONTEXT],
"required_approvals": 0,
"enable_approvals_whitelist": False,
"approvals_whitelist_username": [],
"approvals_whitelist_teams": [],
"block_on_rejected_reviews": True,
"block_on_official_review_requests": False,
"block_on_outdated_branch": True,
"dismiss_stale_approvals": True,
"ignore_stale_approvals": False,
"require_signed_commits": False,
"protected_file_patterns": "",
"unprotected_file_patterns": "",
"block_admin_merge_override": True,
}
]
}
self._managed_workflows: dict[str, str] = {"acme/hub": MANAGED_VALIDATION_WORKFLOW}
self._commits = [
{
"sha": "a" * 40,
"commit": {"message": "Add bounded connector", "author": {"name": "Ada"}},
},
{"sha": "b" * 40, "commit": {"message": "Document the API", "author": {"name": "Lin"}}},
{"sha": "c" * 40, "commit": {"message": "Add tests", "author": {"name": "Kai"}}},
]
self._compare_commits = [self._commits[0]]
self._reverse_compare_commits: list[dict[str, Any]] = []
self._releases = [
{"tag_name": "v1.0.0", "name": "First release", "draft": False, "prerelease": False}
]
self._issues = [
{
"number": 1,
"title": "Bounded read API",
"state": "open",
"html_url": "https://gitea.example/acme/hub/issues/1",
}
]
self._pulls = [
{
"number": 7,
"title": "Add governed Codex workflows",
"state": "open",
"draft": False,
"mergeable": True,
"html_url": "https://gitea.example/acme/hub/pulls/7",
"head": {"ref": "codex/workflows", "sha": "e" * 40},
"base": {"ref": "main", "sha": "a" * 40},
"updated_at": "2026-08-21T20:00:00Z",
}
]
self._pull_files = [
{
"filename": "services/runtime/mcp_server.py",
"status": "modified",
"additions": 42,
"deletions": 2,
}
]
self._statuses = [
{
"status": "success",
"context": MANAGED_VALIDATION_CONTEXT,
"description": "All checks passed",
"target_url": "https://gitea.example/acme/hub/actions/runs/42",
"created_at": "2026-08-21T20:05:00Z",
}
]
self._file = "# ITWorx MCP Hub\n\nThis is a safe fixture.\n"
self._reviews: list[dict[str, Any]] = []
self._review_comments = [
{"id": 12, "path": "README.md", "new_position": 1, "body": "Clarify this."}
]
self._action_run = {
"id": 42,
"name": "ChatGPT validation",
"status": "completed",
"conclusion": "success",
"event": "pull_request",
"head_branch": "codex/workflows",
"head_sha": "e" * 40,
"workflow_id": "managed-validation.yml",
"html_url": "https://gitea.example/acme/hub/actions/runs/42",
}
self._action_job = {
"id": 43,
"name": "full",
"status": "completed",
"conclusion": "success",
}
self._action_job_logs = "all checks passed\n"
def transport(self) -> httpx.MockTransport:
return httpx.MockTransport(self._handle)
def _handle(self, request: httpx.Request) -> httpx.Response:
self.recorded_methods.append(request.method)
self.recorded_authorizations.append(request.headers.get("authorization", ""))
if self.redirect_to is not None:
return httpx.Response(302, headers={"location": self.redirect_to}, request=request)
parsed = urlparse(str(request.url))
self.recorded_paths.append(parsed.path)
if not parsed.path.startswith("/api/v1/"):
return self._json(404, {"message": "API prefix missing"}, request)
path = parsed.path.removeprefix("/api/v1/")
query = parse_qs(parsed.query)
body: dict[str, Any] = {}
if request.content:
decoded = json.loads(request.content.decode("utf-8"))
body = decoded if isinstance(decoded, dict) else {}
self.recorded_bodies.append(body)
if path == "version" and request.method == "GET":
return self._json(200, {"version": self.gitea_version}, request)
if path == "repos/search":
if self.unsupported_search:
return self._json(501, {"message": "unsupported"}, request)
if "q" not in query:
repositories = list(self._repos.values())
if query.get("private") == ["true"]:
repositories = [item for item in repositories if item.get("private") is True]
return self._json(200, {"ok": True, "data": repositories}, request)
return self._json(
200,
{
"ok": True,
"data": [{"repository": {"full_name": "acme/hub"}, "path": "README.md"}],
},
request,
)
if path == "user" and request.method == "GET":
return self._json(200, {"login": "acme"}, request)
if path == "user/repos" and request.method == "GET":
if self.deny_user_repositories:
return self._json(403, {"message": "denied"}, request)
return self._paged(list(self._repos.values()), query, request)
if path == "user/repos" and request.method == "POST":
name = str(body.get("name", ""))
qualified_name = f"acme/{name}"
if qualified_name in self._repos:
return self._json(409, {"message": "repository already exists"}, request)
auto_init = bool(body.get("auto_init", True))
repository = {
"owner": {"login": "acme"},
"name": name,
"full_name": qualified_name,
"description": str(body.get("description", "")),
"private": bool(body.get("private", True)),
"empty": not auto_init,
"default_branch": str(body.get("default_branch", "main")) if auto_init else "",
"html_url": f"https://gitea.example/{qualified_name}",
"updated_at": "2026-08-26T15:00:00Z",
}
self._repos[qualified_name] = repository
return self._json(201, repository, request)
if path == "repos/issues/search" and request.method == "GET":
state = str(query.get("state", ["open"])[0])
issues = [
{
**item,
"repository": {
"full_name": "acme/hub",
"name": "hub",
"owner": {"login": "acme"},
},
"updated_at": "2026-08-01T10:00:00Z",
"labels": [],
}
for item in self._issues
if state == "all" or item.get("state") == state
]
return self._paged(issues, query, request)
if path.startswith("repos/"):
path = path.removeprefix("repos/")
parts = path.split("/")
qualified_name = "/".join(parts[:2])
if len(parts) >= 2 and qualified_name not in self._repos:
return self._json(404, {"message": "not found"}, request)
if len(parts) == 2:
if request.method == "DELETE":
self._repos.pop(qualified_name, None)
self._branch_protections.pop(qualified_name, None)
self._managed_workflows.pop(qualified_name, None)
return httpx.Response(204, request=request)
if request.method != "GET":
return self._json(405, {"message": "method not allowed"}, request)
return self._json(200, self._repos[qualified_name], request, leaked_header=True)
if parts[2] == "branch_protections":
protections = self._branch_protections.setdefault(qualified_name, [])
if len(parts) == 3:
if request.method == "POST":
created = dict(body)
created.setdefault("rule_name", str(body.get("branch_name", "")))
created.setdefault("branch_name", str(body.get("rule_name", "")))
protections.append(created)
branch_name = str(created.get("rule_name") or created.get("branch_name"))
for branch in self._branches:
if branch.get("name") == branch_name:
branch["protected"] = True
return self._json(201, created, request)
return self._json(200, protections, request)
rule_name = unquote(parts[3])
protection = next(
(
item
for item in protections
if item.get("rule_name") == rule_name or item.get("branch_name") == rule_name
),
None,
)
if protection is None:
return self._json(404, {"message": "not found"}, request)
if request.method == "PATCH":
protection.update(body)
return self._json(200, protection, request)
if request.method == "DELETE":
protections.remove(protection)
return httpx.Response(204, request=request)
return self._json(200, protection, request)
if parts[2] == "branches":
if request.method == "POST":
created = {
"name": body.get("new_branch_name", ""),
"commit": {"id": "f" * 40},
"protected": False,
}
self._branches.append(created)
return self._json(201, created, request)
if len(parts) == 4:
branch_name = unquote(parts[3])
matched_branch: dict[str, object] | None = None
for item in self._branches:
if item.get("name") == branch_name:
matched_branch = item
break
if matched_branch is None:
return self._json(404, {"message": "not found"}, request)
if request.method == "DELETE":
self._branches.remove(matched_branch)
return httpx.Response(204, request=request)
return self._json(200, matched_branch, request)
return self._paged(self._branches, query, request)
if parts[2] == "commits" and len(parts) == 3:
return self._paged(self._commits, query, request)
if parts[2:4] == ["git", "commits"] and len(parts) == 5:
return self._json(
200,
{
**self._commits[0],
"sha": unquote(parts[4]),
"verification": {"verified": True, "reason": "gpg"},
"stats": {"additions": 42, "deletions": 2, "total": 44},
"files": [{"filename": "README.md", "status": "modified"}],
"html_url": "https://gitea.example/acme/hub/commit/example",
},
request,
)
if parts[2] == "compare" and len(parts) == 4:
compare_range = unquote(parts[3])
reverse_ranges: set[str] = set()
for pull in self._pulls:
head = pull.get("head")
base = pull.get("base")
if isinstance(head, dict) and isinstance(base, dict):
reverse_ranges.add(f"{head.get('sha', '')}...{base.get('sha', '')}")
compare_commits = (
self._reverse_compare_commits
if compare_range in reverse_ranges
else self._compare_commits
)
return self._json(
200,
{
"total_commits": len(compare_commits),
"commits": compare_commits,
"files": [{"filename": "README.md", "status": "modified"}],
},
request,
)
if parts[2] == "releases":
return self._paged(self._releases, query, request)
if parts[2] == "issues":
if request.method == "POST" and len(parts) == 3:
created_issue = {
"number": len(self._issues) + 1,
"title": body.get("title", ""),
"state": "open",
"html_url": f"https://gitea.example/acme/hub/issues/{len(self._issues) + 1}",
}
self._issues.append(created_issue)
return self._json(201, created_issue, request)
if len(parts) == 4:
issue_number = int(parts[3])
issue = next(
(item for item in self._issues if item.get("number") == issue_number), None
)
if issue is None:
return self._json(404, {"message": "not found"}, request)
if request.method == "PATCH":
issue.update(
{
key: value
for key, value in body.items()
if key in {"title", "body", "state"}
}
)
return self._json(200, issue, request)
if request.method == "POST" and len(parts) == 5 and parts[4] == "comments":
return self._json(
201,
{
"id": 99,
"body": body.get("body", ""),
"html_url": f"https://gitea.example/acme/hub/issues/{parts[3]}#issuecomment-99",
},
request,
)
return self._paged(self._issues, query, request)
if parts[2] == "pulls" and len(parts) == 3:
if request.method == "POST":
created_pull = {
"number": len(self._pulls) + 8,
"title": body.get("title", ""),
"state": "open",
"draft": False,
"mergeable": True,
"html_url": "https://gitea.example/acme/hub/pulls/8",
"head": {"ref": body.get("head", ""), "sha": "f" * 40},
"base": {"ref": body.get("base", ""), "sha": "a" * 40},
"updated_at": "2026-08-24T20:00:00Z",
}
self._pulls.append(created_pull)
return self._json(201, created_pull, request)
return self._paged(self._pulls, query, request)
if parts[2] == "pulls" and len(parts) == 4 and parts[3] == "7":
if request.method == "PATCH":
self._pulls[0].update(
{key: value for key, value in body.items() if key in {"title", "body", "state"}}
)
if body.get("state") == "open":
self._action_run.update(
event="pull_request", status="queued", conclusion="",
head_sha=self._pulls[0]["head"]["sha"],
)
return self._json(200, self._pulls[0], request)
if parts[2] == "pulls" and len(parts) == 5 and parts[3:] == ["7", "update"]:
if query.get("style") not in [["merge"], ["rebase"]]:
return self._json(422, {"message": "invalid style"}, request)
head = self._pulls[0].get("head")
base = self._pulls[0].get("base")
if not isinstance(head, dict) or not isinstance(base, dict):
return self._json(500, {"message": "invalid fake pull request"}, request)
head["sha"] = "d" * 40
base["sha"] = "a" * 40
return self._json(200, {}, request)
if parts[2] == "pulls" and len(parts) == 5 and parts[3:] == ["7", "merge"]:
if self.deny_merge_transition:
return self._json(405, {"message": "merge blocked by repository policy"}, request)
strategy_key = "Do" if self.gitea_version.startswith(("1.14.", "1.25.")) else "do"
if body.get(strategy_key) not in {
"merge",
"rebase",
"rebase-merge",
"squash",
"fast-forward-only",
}:
return self._json(405, {"message": "unsupported merge strategy"}, request)
self._pulls[0].update({"state": "closed", "merged": True, "merge_commit_sha": "6" * 40})
for branch in self._branches:
if branch.get("name") == "main":
branch["commit"]["id"] = "6" * 40
return self._json(200, {}, request)
if parts[2] == "pulls" and len(parts) == 5 and parts[3:] == ["7", "files"]:
return self._paged(self._pull_files, query, request)
if parts[2] == "pulls" and len(parts) == 5 and parts[3:] == ["7", "reviews"]:
return self._paged(self._reviews, query, request)
if (
parts[2] == "pulls"
and len(parts) == 7
and parts[3:5] == ["7", "reviews"]
and parts[6] == "comments"
):
return self._json(200, self._review_comments, request)
if parts[2] == "commits" and len(parts) == 5 and parts[4] == "statuses":
return self._paged(self._statuses, query, request)
if parts[2:4] == ["git", "trees"] and len(parts) == 5:
if self.unsupported_search:
return self._json(501, {"message": "unsupported"}, request)
return self._json(
200,
{
"sha": "a" * 40,
"tree": [
{
"path": "README.md",
"type": "blob",
"sha": "d" * 40,
"size": len(self._file.encode()),
}
],
"truncated": False,
},
request,
)
if parts[2:4] == ["git", "blobs"] and len(parts) == 5:
return self._json(
200,
{
"sha": parts[4],
"encoding": "base64",
"content": base64.b64encode(self._file.encode()).decode(),
"size": len(self._file.encode()),
},
request,
)
if parts[2:4] == ["actions", "workflows"] and len(parts) == 4:
workflows = []
if qualified_name in self._managed_workflows:
workflows.append(
{
"id": "managed-validation.yml",
"name": "Managed validation",
"path": MANAGED_VALIDATION_PATH,
}
)
return self._json(
200,
{"workflows": workflows},
request,
)
if parts[2:4] == ["actions", "workflows"] and len(parts) == 6 and parts[5] == "dispatches":
raw_inputs = body.get("inputs")
inputs: dict[str, Any] = raw_inputs if isinstance(raw_inputs, dict) else {}
profile = str(inputs.get("profile", "full"))
ref = str(body.get("ref", ""))
dispatch_branch: dict[str, Any] | None = next(
(item for item in self._branches if item.get("name") == ref), None
)
head_sha = str(
(dispatch_branch or {}).get("commit", {}).get("id", self._action_run["head_sha"])
)
self._action_run.update(
{
"event": "workflow_dispatch",
"head_branch": ref,
"head_sha": head_sha,
"workflow_id": unquote(parts[4]),
"display_title": f"Managed validation / {profile}",
}
)
self._action_job["name"] = "full"
return self._json(200, self._action_run, request)
if parts[2:4] == ["actions", "runs"] and len(parts) == 4:
return self._json(200, {"workflow_runs": [self._action_run]}, request)
if parts[2:4] == ["actions", "runs"] and len(parts) == 6 and parts[5] == "rerun":
self._action_run.update(status="queued", conclusion="")
return httpx.Response(204, request=request)
if parts[2:4] == ["actions", "runs"] and len(parts) == 5:
return self._json(200, self._action_run, request)
if parts[2:4] == ["actions", "runs"] and len(parts) == 6 and parts[5] == "jobs":
return self._json(200, {"jobs": [self._action_job]}, request)
if parts[2:4] == ["actions", "jobs"] and len(parts) == 6 and parts[5] == "logs":
return httpx.Response(
200,
headers={"content-type": "text/plain"},
content=self._action_job_logs.encode(),
request=request,
)
if parts[2] == "contents" and len(parts) == 3:
return self._json(
200,
[
{
"name": "README.md",
"path": "README.md",
"type": "file",
"size": len(self._file.encode()),
"sha": "d" * 40,
}
],
request,
)
if (
parts[2] == "contents"
and len(parts) >= 4
and "/".join(parts[3:]) == MANAGED_VALIDATION_PATH
):
existing = self._managed_workflows.get(qualified_name)
protection = next(iter(self._branch_protections.get(qualified_name, [])), None)
temporary_push_allowed = bool(
protection
and protection.get("enable_push") is True
and protection.get("enable_push_whitelist") is True
and "acme" in protection.get("push_whitelist_usernames", [])
)
if (
request.method in {"POST", "PUT"}
and self.deny_protected_workflow_writes
and protection is not None
and not temporary_push_allowed
):
return self._json(403, {"message": "protected branch"}, request)
if request.method == "POST":
if existing is not None:
return self._json(422, {"message": "already exists"}, request)
self._managed_workflows[qualified_name] = base64.b64decode(
str(body.get("content", ""))
).decode()
return self._json(
201,
{"content": {"path": MANAGED_VALIDATION_PATH, "sha": "9" * 40}},
request,
)
if existing is None:
return self._json(404, {"message": "not found"}, request)
if request.method == "PUT":
self._managed_workflows[qualified_name] = base64.b64decode(
str(body.get("content", ""))
).decode()
return self._json(
200,
{"content": {"path": MANAGED_VALIDATION_PATH, "sha": "9" * 40}},
request,
)
if request.method == "DELETE":
self._managed_workflows.pop(qualified_name, None)
return self._json(200, {"commit": {"sha": "7" * 40}}, request)
return self._json(
200,
{
"name": "managed-validation.yml",
"path": MANAGED_VALIDATION_PATH,
"sha": "d" * 40,
"content": base64.b64encode(existing.encode()).decode(),
"encoding": "base64",
},
request,
)
if parts[2] == "contents" and len(parts) >= 4 and "/".join(parts[3:]) == "README.md":
if request.method == "DELETE":
return self._json(200, {"commit": {"sha": "7" * 40}}, request)
if request.method in {"POST", "PUT"}:
if request.method == "POST" and self._repos[qualified_name].get("empty"):
branch_name = str(body.get("branch", "main"))
self._repos[qualified_name]["empty"] = False
self._repos[qualified_name]["default_branch"] = branch_name
if not any(item.get("name") == branch_name for item in self._branches):
self._branches.append(
{
"name": branch_name,
"commit": {"id": "8" * 40},
"protected": False,
}
)
return self._json(
201 if request.method == "POST" else 200,
{
"content": {"path": "README.md", "sha": "9" * 40},
"commit": {"sha": "8" * 40},
},
request,
)
return self._json(
200,
{
"name": "README.md",
"path": "README.md",
"sha": "d" * 40,
"content": base64.b64encode(self._file.encode()).decode(),
"encoding": "base64",
"token": "fixture-token-must-not-leak",
},
request,
)
return self._json(404, {"message": "not found"}, request)
def _paged(
self, items: list[dict[str, Any]], query: dict[str, list[str]], request: httpx.Request
) -> httpx.Response:
page = max(int(query.get("page", ["1"])[0]), 1)
limit = max(int(query.get("limit", ["100"])[0]), 1)
start = (page - 1) * limit
return self._json(200, items[start : start + limit], request)
@staticmethod
def _json(
status: int, payload: object, request: httpx.Request, *, leaked_header: bool = False
) -> httpx.Response:
headers = {"content-type": "application/json"}
if leaked_header:
headers["x-fixture-token"] = "fixture-token-must-not-leak"
return httpx.Response(
status, headers=headers, content=json.dumps(payload).encode(), request=request
)
+59
View File
@@ -0,0 +1,59 @@
"""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
+153
View File
@@ -0,0 +1,153 @@
"""Stable managed-validation contract used by Gitea governance tools."""
from __future__ import annotations
import hashlib
MANAGED_VALIDATION_PATH = ".gitea/workflows/managed-validation.yml"
MANAGED_VALIDATION_CONTEXT = "Managed validation / full (pull_request)"
# Keep this workflow pull-request/dispatch only. Bulk governance must never create
# a push-triggered Actions storm across the portfolio.
MANAGED_VALIDATION_WORKFLOW = """name: Managed validation
run-name: "Managed validation / ${{ inputs.profile || 'full' }}"
on:
pull_request:
workflow_dispatch:
inputs:
profile:
description: Allowlisted validation profile
required: true
default: full
type: choice
options: [test, lint, typecheck, build, security, full]
permissions:
contents: read
concurrency:
group: managed-validation-${{ gitea.repository }}-${{ gitea.ref }}
cancel-in-progress: true
jobs:
full:
name: full
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install .NET 8 for solution repositories
if: ${{ hashFiles('*.sln') != '' }}
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: '8.0.x'
- name: Validate repository with a bounded profile
shell: bash
env:
REQUESTED_PROFILE: ${{ inputs.profile }}
run: |
set -euo pipefail
profile="${REQUESTED_PROFILE:-full}"
case "${profile}" in
test|lint|typecheck|build|security|full) ;;
*) echo "Profile is not allowlisted" >&2; exit 2 ;;
esac
python_tests_ran=0
git diff --check
if git grep -nE '^(<<<<<<< |=======$|>>>>>>> )' -- . ':!*.lock' ':!*.patch'; then
echo "Unresolved merge markers detected" >&2
exit 1
fi
if [[ -f pyproject.toml || -f requirements.txt ]]; then
# Compile only tracked Python sources. Running compileall after a
# Node install would otherwise traverse node_modules and turn a
# lightweight baseline into a large runner workload.
git ls-files -z '*.py' | xargs -0 -r python -m py_compile
if [[ -f uv.lock ]]; then
python -m venv "${RUNNER_TEMP}/managed-uv"
uv_python="${RUNNER_TEMP}/managed-uv/bin/python"
"${uv_python}" -m pip install --disable-pip-version-check uv==0.10.0
managed_uv="${RUNNER_TEMP}/managed-uv/bin/uv"
export UV_PROJECT_ENVIRONMENT="${RUNNER_TEMP}/managed-project-venv"
"${managed_uv}" sync --locked
export PATH="${UV_PROJECT_ENVIRONMENT}/bin:${PATH}"
if [[ "${profile}" == test || "${profile}" == full ]]; then
if "${managed_uv}" run python -c 'import pytest' 2>/dev/null; then
"${managed_uv}" run python -m pytest
python_tests_ran=1
fi
fi
if [[ "${profile}" == lint || "${profile}" == full ]]; then
if "${managed_uv}" run python -c 'import ruff' 2>/dev/null; then
"${managed_uv}" run python -m ruff check .
fi
fi
elif [[ -f requirements.txt ]]; then
python -m venv "${RUNNER_TEMP}/managed-python"
managed_python="${RUNNER_TEMP}/managed-python/bin/python"
"${managed_python}" -m pip install --disable-pip-version-check -r requirements.txt
export PATH="${RUNNER_TEMP}/managed-python/bin:${PATH}"
if [[ "${profile}" == test || "${profile}" == full ]]; then
if "${managed_python}" -c 'import pytest' 2>/dev/null; then
"${managed_python}" -m pytest
python_tests_ran=1
fi
fi
fi
fi
# Prepare Python before invoking Node scripts. Polyglot repositories
# commonly delegate their test script to Python and need the managed
# virtual environment to be active first.
if [[ -f package.json ]]; then
corepack enable
run_node_test=1
if [[ "${python_tests_ran}" == 1 ]] && node -e '
const script = String(require("./package.json").scripts?.test || "");
const delegated = /^\\s*python(?:3)?\\s+-m\\s+(?:pytest|unittest)\\b/.test(script);
process.exit(delegated ? 0 : 1);
'; then
run_node_test=0
fi
if [[ -f pnpm-lock.yaml ]]; then
pnpm install --frozen-lockfile
if [[ "${run_node_test}" == 1 ]]; then
[[ "${profile}" == test || "${profile}" == full ]] && pnpm --if-present test
fi
[[ "${profile}" == lint || "${profile}" == full ]] && pnpm --if-present lint
[[ "${profile}" == typecheck || "${profile}" == full ]] && pnpm --if-present typecheck
[[ "${profile}" == build || "${profile}" == full ]] && pnpm --if-present build
elif [[ -f package-lock.json ]]; then
npm ci
if [[ "${run_node_test}" == 1 ]]; then
[[ "${profile}" == test || "${profile}" == full ]] && npm run --if-present test
fi
[[ "${profile}" == lint || "${profile}" == full ]] && npm run --if-present lint
if [[ "${profile}" == typecheck || "${profile}" == full ]]; then
npm run --if-present typecheck
fi
[[ "${profile}" == build || "${profile}" == full ]] && npm run --if-present build
fi
fi
if [[ -f go.mod ]]; then
if [[ "${profile}" == test || "${profile}" == build || "${profile}" == full ]]; then
go test ./...
fi
fi
if [[ -f Cargo.toml ]]; then
if [[ "${profile}" == test || "${profile}" == build || "${profile}" == full ]]; then
cargo test --locked
fi
fi
if compgen -G '*.sln' >/dev/null; then
if [[ "${profile}" == test || "${profile}" == build || "${profile}" == full ]]; then
dotnet test --configuration Release
fi
fi
"""
MANAGED_VALIDATION_DIGEST = hashlib.sha256(MANAGED_VALIDATION_WORKFLOW.encode("utf-8")).hexdigest()
File diff suppressed because it is too large. Load diff