feat(web): support ssh owner repo clone flow

- Add SSH-only owner/repo clone path for git.commumedia.org
- Preflight remote repository existence with git ls-remote before cloning
- Keep advanced URL paste fallback and blank repository creation
- Add focused backend and frontend coverage plus docs updates

Quality gates: python -m py_compile, vitest run src/components/repositories-settings-tab.test.tsx, npm run typecheck
This commit is contained in:
2026-05-22 19:07:04 +02:00
parent 5af4de0d7e
commit 2525c58471
9 changed files with 310 additions and 63 deletions
+29
View File
@@ -89,6 +89,32 @@ def _get_repo_path(user_id: uuid.UUID, project_id: uuid.UUID, name: str) -> str:
return os.path.join(base, str(user_id), str(project_id), f"{name}.git")
def _build_provider_clone_url(owner: str, repo: str) -> str:
"""Build the SSH clone URL for the fixed git provider."""
return f"git@git.commumedia.org:{owner}/{repo}.git"
def _preflight_remote_repository(remote_url: str) -> None:
"""Verify a remote repository is reachable before cloning."""
try:
result = subprocess.run(
["git", "ls-remote", remote_url],
capture_output=True,
text=True,
timeout=60,
)
except subprocess.TimeoutExpired:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="remote repository check timed out")
except FileNotFoundError:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="git command not found")
if result.returncode != 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="repository not found or inaccessible",
)
class GitRepositoryCreate(BaseModel):
name: str
remote_url: str | None = None
@@ -267,6 +293,9 @@ async def create_repository(
if parse_result["base_url"]:
remote_url = parse_result["base_url"]
if remote_url:
_preflight_remote_repository(remote_url)
repo_path = _get_repo_path(user_id, project_id, data.name)
# Ensure parent directory exists
@@ -0,0 +1,28 @@
from unittest.mock import Mock, patch
import pytest
from fastapi import HTTPException
from src.api.git_repositories import _build_provider_clone_url, _preflight_remote_repository
def test_build_provider_clone_url_uses_fixed_host() -> None:
assert _build_provider_clone_url("alice", "demo") == "git@git.commumedia.org:alice/demo.git"
def test_preflight_remote_repository_allows_accessible_repo() -> None:
completed = Mock(returncode=0)
with patch("src.api.git_repositories.subprocess.run", return_value=completed) as run_mock:
_preflight_remote_repository("git@git.commumedia.org:alice/demo.git")
run_mock.assert_called_once()
def test_preflight_remote_repository_rejects_missing_repo() -> None:
completed = Mock(returncode=128)
with patch("src.api.git_repositories.subprocess.run", return_value=completed):
with pytest.raises(HTTPException) as exc_info:
_preflight_remote_repository("git@git.commumedia.org:alice/missing.git")
assert exc_info.value.status_code == 400
assert exc_info.value.detail == "repository not found or inaccessible"