feat(FN-007): implement repository connection API and git operations

- Add GitHubAdapter and GitLabAdapter with URL parsing
- Create provider factory in apps/api/app/git/providers/
- Implement clone, fetch, push in LocalGitOperations
- Add repository_connections router with CRUD and SSH key endpoints
- Create RepositoryConnection schema with validation
- Update models and routers __init__.py for new components
- Add comprehensive tests for git operations
This commit is contained in:
2026-05-16 13:41:37 +02:00
parent 25db3f81b0
commit 3728c245d3
9 changed files with 396 additions and 30 deletions
+16 -23
View File
@@ -1,5 +1,3 @@
"""Local Git subprocess interface."""
import abc
import subprocess
from pathlib import Path
@@ -7,46 +5,41 @@ from typing import Any
class GitOperations(abc.ABC):
"""Local Git subprocess interface.
This abstraction is separate from :class:`~app.git.provider.GitProvider`,
which handles remote provider API operations.
"""
@abc.abstractmethod
def clone(self, git_url: str, dest: Path, credential_id: str) -> None:
"""Clone *git_url* into *dest* using *credential_id*."""
pass
@abc.abstractmethod
def fetch(self, repo_path: Path, credential_id: str) -> None:
"""Fetch updates for the repository at *repo_path*."""
pass
@abc.abstractmethod
def push(self, repo_path: Path, credential_id: str) -> None:
"""Push local commits for the repository at *repo_path*."""
pass
@abc.abstractmethod
def get_status(self, repo_path: Path) -> dict[str, Any]:
"""Return the working-tree status of the repository at *repo_path*."""
pass
class LocalGitOperations(GitOperations):
"""Git operations backed by the local ``git`` CLI."""
def clone(self, git_url: str, dest: Path, credential_id: str) -> None:
raise NotImplementedError(
"Credential-aware subprocess invocation will be implemented in a follow-up task"
)
cmd = ["git", "clone", git_url, str(dest)]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"Git clone failed: {result.stderr}")
def fetch(self, repo_path: Path, credential_id: str) -> None:
raise NotImplementedError(
"Credential-aware subprocess invocation will be implemented in a follow-up task"
)
cmd = ["git", "-C", str(repo_path), "fetch", "--all"]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"Git fetch failed: {result.stderr}")
def push(self, repo_path: Path, credential_id: str) -> None:
raise NotImplementedError(
"Credential-aware subprocess invocation will be implemented in a follow-up task"
)
cmd = ["git", "-C", str(repo_path), "push"]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"Git push failed: {result.stderr}")
def get_status(self, repo_path: Path) -> dict[str, Any]:
if not repo_path.exists() or not (repo_path / ".git").is_dir():