cccf4379d8
- Create InstanceList.module.css, AppShell.module.css, SettingsTabLayout.module.css - Create CommitPanel.module.css, FileViewer.module.css - Create page CSS files: sessions, repo-workspace, dashboard, projects, git-history, ssh-keys, settings - Update components to import and use CSS modules - Delete monolithic styles.css (2,255 lines) - Update main.tsx to import page CSS and new modules Quality gates: tsc (pass), eslint (pass), build (pass) Refs: repo-restructure Task 2.3
197 lines
5.8 KiB
Python
197 lines
5.8 KiB
Python
"""Git control operations with repo validation."""
|
|
|
|
import logging
|
|
import os
|
|
import uuid
|
|
|
|
from fastapi import HTTPException, status
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.models.git_repository import GitRepository
|
|
from src.models.user import User
|
|
from src.schemas.git_repository import (
|
|
BranchCreateRequest,
|
|
CheckoutRequest,
|
|
CommitRequest,
|
|
FetchResponse,
|
|
MergeRequest,
|
|
MergeResponse,
|
|
PullResponse,
|
|
PushResponse,
|
|
StatusResponse,
|
|
)
|
|
from src.services.git.repository import ensure_repo_on_disk, get_repo_and_validate
|
|
from src.utils.git_control import (
|
|
checkout_branch,
|
|
commit_changes,
|
|
create_branch,
|
|
delete_branch,
|
|
fetch,
|
|
get_status,
|
|
merge,
|
|
pull,
|
|
push,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def get_status_with_validation(
|
|
session: AsyncSession,
|
|
project_id: uuid.UUID,
|
|
repo_id: uuid.UUID,
|
|
) -> StatusResponse:
|
|
repo = await get_repo_and_validate(session, repo_id, project_id)
|
|
ensure_repo_on_disk(repo)
|
|
try:
|
|
result = get_status(repo.path)
|
|
return StatusResponse(
|
|
branch=result.branch,
|
|
modified=result.modified,
|
|
added=result.added,
|
|
deleted=result.deleted,
|
|
untracked=result.untracked,
|
|
renamed=result.renamed,
|
|
ahead=result.ahead,
|
|
behind=result.behind,
|
|
)
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
async def create_branch_with_validation(
|
|
session: AsyncSession,
|
|
project_id: uuid.UUID,
|
|
repo_id: uuid.UUID,
|
|
data: BranchCreateRequest,
|
|
) -> dict:
|
|
repo = await get_repo_and_validate(session, repo_id, project_id)
|
|
ensure_repo_on_disk(repo)
|
|
try:
|
|
create_branch(repo.path, data.name, data.base_branch)
|
|
return {"message": f"Branch '{data.name}' created", "branch": data.name}
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
async def delete_branch_with_validation(
|
|
session: AsyncSession,
|
|
project_id: uuid.UUID,
|
|
repo_id: uuid.UUID,
|
|
branch_name: str,
|
|
force: bool = False,
|
|
) -> dict:
|
|
repo = await get_repo_and_validate(session, repo_id, project_id)
|
|
ensure_repo_on_disk(repo)
|
|
try:
|
|
delete_branch(repo.path, branch_name, force)
|
|
return {"message": f"Branch '{branch_name}' deleted"}
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
async def checkout_branch_with_validation(
|
|
session: AsyncSession,
|
|
project_id: uuid.UUID,
|
|
repo_id: uuid.UUID,
|
|
data: CheckoutRequest,
|
|
) -> dict:
|
|
repo = await get_repo_and_validate(session, repo_id, project_id)
|
|
ensure_repo_on_disk(repo)
|
|
try:
|
|
checkout_branch(repo.path, data.branch)
|
|
return {"message": f"Checked out branch '{data.branch}'", "branch": data.branch}
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
async def commit_changes_with_validation(
|
|
session: AsyncSession,
|
|
project_id: uuid.UUID,
|
|
repo_id: uuid.UUID,
|
|
data: CommitRequest,
|
|
user: User,
|
|
) -> dict:
|
|
repo = await get_repo_and_validate(session, repo_id, project_id)
|
|
ensure_repo_on_disk(repo)
|
|
author_name = user.name or "Unknown"
|
|
author_email = user.email or "unknown@example.com"
|
|
try:
|
|
commit_hash = commit_changes(
|
|
repo_path=repo.path,
|
|
message=data.message,
|
|
author_name=author_name,
|
|
author_email=author_email,
|
|
files=data.files,
|
|
)
|
|
return {"commit_hash": commit_hash, "message": data.message}
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
async def fetch_with_validation(
|
|
session: AsyncSession,
|
|
project_id: uuid.UUID,
|
|
repo_id: uuid.UUID,
|
|
) -> FetchResponse:
|
|
repo = await get_repo_and_validate(session, repo_id, project_id)
|
|
ensure_repo_on_disk(repo)
|
|
try:
|
|
fetch(repo.path)
|
|
return FetchResponse(message="Fetched from remote")
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
async def pull_with_validation(
|
|
session: AsyncSession,
|
|
project_id: uuid.UUID,
|
|
repo_id: uuid.UUID,
|
|
branch: str | None = None,
|
|
) -> PullResponse:
|
|
repo = await get_repo_and_validate(session, repo_id, project_id)
|
|
ensure_repo_on_disk(repo)
|
|
try:
|
|
pull(repo.path, branch)
|
|
return PullResponse(message="Pulled from remote")
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
async def push_with_validation(
|
|
session: AsyncSession,
|
|
project_id: uuid.UUID,
|
|
repo_id: uuid.UUID,
|
|
branch: str | None = None,
|
|
) -> PushResponse:
|
|
repo = await get_repo_and_validate(session, repo_id, project_id)
|
|
ensure_repo_on_disk(repo)
|
|
try:
|
|
push(repo.path, branch)
|
|
return PushResponse(message="Pushed to remote")
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
|
|
|
|
async def merge_with_validation(
|
|
session: AsyncSession,
|
|
project_id: uuid.UUID,
|
|
repo_id: uuid.UUID,
|
|
data: MergeRequest,
|
|
) -> MergeResponse:
|
|
repo = await get_repo_and_validate(session, repo_id, project_id)
|
|
ensure_repo_on_disk(repo)
|
|
try:
|
|
commit_hash = merge(
|
|
repo_path=repo.path,
|
|
source_branch=data.source_branch,
|
|
target_branch=data.target_branch,
|
|
message=data.message,
|
|
)
|
|
return MergeResponse(
|
|
commit_hash=commit_hash,
|
|
message=data.message or f"Merge {data.source_branch}",
|
|
)
|
|
except RuntimeError as e:
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|