37ccaa4fdc
Service organization (19 files moved into 6 subpackages): - services/instance/ — event_bus, health_monitor, lifecycle_hooks - services/config/ — config_profile_resolver - services/git/ — clone, git_operations, git_service - services/build/ — docker_build, manifest_compiler - services/terminal/ — terminal_manager, terminal_session - services/shared/ — correlation, file_service, notification_service, permission_fixer, readiness_probe, ssh_keys, tunnel, workspace_manager API router organization (16 files moved into 6 subpackages): - api/tool/ — tool_instances, tool_types, tool_definitions, tool_types_validation, sessions (extracted from tool_instances) - api/config/ — config_profiles, user_config - api/workspace/ — workspaces, workspace_files, workspace_git, workspace_instances - api/user/ — users, auth, ssh_keys - api/project/ — projects, git_repositories - api/system/ — health, events, notifications, dashboard, terminal, instance_proxy Updated main.py imports and all __init__.py re-exports. Sessions router extracted from tool_instances.py into api/tool/sessions.py. Quality gates: py_compile passed, ruff passed.
204 lines
5.8 KiB
Python
204 lines
5.8 KiB
Python
"""Workspace git API endpoints."""
|
|
|
|
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.auth.dependencies import get_current_user_id, get_db_session
|
|
from src.models import Workspace
|
|
from src.services.git.git_operations import GitOperations
|
|
|
|
router = APIRouter(prefix="/workspaces/{workspace_id}/git")
|
|
|
|
|
|
async def _get_workspace(
|
|
session: AsyncSession,
|
|
workspace_id: uuid.UUID,
|
|
user_id: uuid.UUID,
|
|
) -> Workspace:
|
|
from sqlalchemy import select
|
|
|
|
result = await session.execute(
|
|
select(Workspace).where(
|
|
Workspace.id == workspace_id,
|
|
Workspace.user_id == user_id,
|
|
)
|
|
)
|
|
workspace = result.scalar_one_or_none()
|
|
if not workspace:
|
|
raise HTTPException(status_code=404, detail="Workspace not found")
|
|
return workspace
|
|
|
|
|
|
@router.get("/status")
|
|
async def git_status(
|
|
workspace_id: uuid.UUID,
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> dict:
|
|
"""Get git status for the workspace."""
|
|
workspace = await _get_workspace(session, workspace_id, user_id)
|
|
git = GitOperations(workspace)
|
|
try:
|
|
status = await git.status()
|
|
except RuntimeError as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
return {
|
|
"branch": status.branch,
|
|
"modified": status.modified,
|
|
"added": status.added,
|
|
"deleted": status.deleted,
|
|
"untracked": status.untracked,
|
|
"ahead": status.ahead,
|
|
"behind": status.behind,
|
|
}
|
|
|
|
|
|
@router.get("/branches")
|
|
async def git_branches(
|
|
workspace_id: uuid.UUID,
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> dict:
|
|
"""List branches for the workspace."""
|
|
workspace = await _get_workspace(session, workspace_id, user_id)
|
|
git = GitOperations(workspace)
|
|
try:
|
|
branches, current = await git.branches()
|
|
except RuntimeError as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
return {
|
|
"branches": branches,
|
|
"current_branch": current,
|
|
}
|
|
|
|
|
|
@router.post("/commit")
|
|
async def git_commit(
|
|
workspace_id: uuid.UUID,
|
|
data: dict,
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> dict:
|
|
"""Stage all changes and commit."""
|
|
workspace = await _get_workspace(session, workspace_id, user_id)
|
|
message = data.get("message", "").strip()
|
|
if not message:
|
|
raise HTTPException(status_code=400, detail="Commit message is required")
|
|
|
|
git = GitOperations(workspace)
|
|
try:
|
|
await git.commit(message)
|
|
except RuntimeError as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
return {"status": "committed"}
|
|
|
|
|
|
@router.post("/push")
|
|
async def git_push(
|
|
workspace_id: uuid.UUID,
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> dict:
|
|
"""Push current branch."""
|
|
workspace = await _get_workspace(session, workspace_id, user_id)
|
|
git = GitOperations(workspace)
|
|
try:
|
|
await git.push()
|
|
except RuntimeError as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
return {"status": "pushed"}
|
|
|
|
|
|
@router.post("/pull")
|
|
async def git_pull(
|
|
workspace_id: uuid.UUID,
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> dict:
|
|
"""Pull current branch."""
|
|
workspace = await _get_workspace(session, workspace_id, user_id)
|
|
git = GitOperations(workspace)
|
|
try:
|
|
await git.pull()
|
|
except RuntimeError as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
return {"status": "pulled"}
|
|
|
|
|
|
@router.post("/fetch")
|
|
async def git_fetch(
|
|
workspace_id: uuid.UUID,
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> dict:
|
|
"""Fetch from origin."""
|
|
workspace = await _get_workspace(session, workspace_id, user_id)
|
|
git = GitOperations(workspace)
|
|
try:
|
|
await git.fetch()
|
|
except RuntimeError as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
return {"status": "fetched"}
|
|
|
|
|
|
@router.post("/checkout")
|
|
async def git_checkout(
|
|
workspace_id: uuid.UUID,
|
|
data: dict,
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> dict:
|
|
"""Checkout a branch."""
|
|
workspace = await _get_workspace(session, workspace_id, user_id)
|
|
branch = data.get("branch", "").strip()
|
|
if not branch:
|
|
raise HTTPException(status_code=400, detail="Branch name is required")
|
|
|
|
git = GitOperations(workspace)
|
|
try:
|
|
await git.checkout(branch)
|
|
except RuntimeError as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
workspace.branch = branch
|
|
await session.commit()
|
|
|
|
return {"status": "checked_out", "branch": branch}
|
|
|
|
|
|
@router.get("/history")
|
|
async def git_history(
|
|
workspace_id: uuid.UUID,
|
|
path: str | None = None,
|
|
limit: int = 50,
|
|
user_id: uuid.UUID = Depends(get_current_user_id),
|
|
session: AsyncSession = Depends(get_db_session),
|
|
) -> dict:
|
|
"""Get commit history."""
|
|
workspace = await _get_workspace(session, workspace_id, user_id)
|
|
git = GitOperations(workspace)
|
|
try:
|
|
commits = await git.history(path, limit)
|
|
except RuntimeError as exc:
|
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
|
|
|
return {
|
|
"commits": [
|
|
{
|
|
"hash": c.hash,
|
|
"message": c.message,
|
|
"author": c.author,
|
|
"date": c.date,
|
|
}
|
|
for c in commits
|
|
],
|
|
}
|