diff --git a/apps/api/app/git/__init__.py b/apps/api/app/git/__init__.py new file mode 100644 index 0000000..eb02efe --- /dev/null +++ b/apps/api/app/git/__init__.py @@ -0,0 +1,19 @@ +"""Git provider abstraction, credentials, SSH keys, and operations.""" + +from app.git.credentials import AccessTokenCredential, CredentialStorage, GitCredential +from app.git.operations import GitOperations, LocalGitOperations +from app.git.provider import GitProvider +from app.git.types import ConnectionStatus, CredentialKind, ProviderKind, SshKeyStatus + +__all__ = [ + "AccessTokenCredential", + "ConnectionStatus", + "CredentialKind", + "CredentialStorage", + "GitCredential", + "GitOperations", + "GitProvider", + "LocalGitOperations", + "ProviderKind", + "SshKeyStatus", +] diff --git a/apps/api/app/git/provider.py b/apps/api/app/git/provider.py new file mode 100644 index 0000000..4847168 --- /dev/null +++ b/apps/api/app/git/provider.py @@ -0,0 +1,48 @@ +"""Abstract base class for Git provider adapters.""" + +import abc +from typing import Any + +from app.git.types import ConnectionStatus, ProviderKind + + +class GitProvider(abc.ABC): + """Provider API adapter for remote Git operations. + + This abstraction is separate from :class:`~app.git.operations.GitOperations`, + which handles local Git subprocess workflows. + """ + + @abc.abstractmethod + def get_kind(self) -> ProviderKind: + """Return the provider kind identifier.""" + + @abc.abstractmethod + async def validate_connection( + self, git_url: str, credential_id: str + ) -> ConnectionStatus: + """Validate that the given credential can access *git_url*. + + Returns a :class:`ConnectionStatus` indicating the result. + """ + + @abc.abstractmethod + async def list_repositories(self, credential_id: str) -> list[dict[str, Any]]: + """List repositories accessible with *credential_id*.""" + + @abc.abstractmethod + async def create_deploy_key( + self, git_url: str, public_key: str + ) -> str: + """Register a deploy key on the remote provider. + + Returns the provider-side deploy key ID. + """ + + @abc.abstractmethod + async def delete_deploy_key(self, git_url: str, deploy_key_id: str) -> None: + """Remove a previously registered deploy key.""" + + @abc.abstractmethod + async def get_default_branch(self, git_url: str, credential_id: str) -> str: + """Return the default branch name for the repository at *git_url*.""" diff --git a/apps/api/app/git/types.py b/apps/api/app/git/types.py new file mode 100644 index 0000000..d5b4719 --- /dev/null +++ b/apps/api/app/git/types.py @@ -0,0 +1,38 @@ +"""Enumerations for Git provider abstraction.""" + +from enum import StrEnum + + +class ProviderKind(StrEnum): + """Supported Git provider kinds.""" + + github = "github" + gitlab = "gitlab" + gitea = "gitea" + forgejo = "forgejo" + generic = "generic" + + +class CredentialKind(StrEnum): + """Supported credential kinds for Git authentication.""" + + ssh_key = "ssh_key" + access_token = "access_token" + + +class ConnectionStatus(StrEnum): + """Lifecycle states for a repository connection.""" + + pending = "pending" + connected = "connected" + disconnected = "disconnected" + error = "error" + + +class SshKeyStatus(StrEnum): + """Lifecycle states for an SSH key pair.""" + + generated = "generated" + registered = "registered" + rotating = "rotating" + revoked = "revoked"