git list refactor
This commit is contained in:
@@ -14,6 +14,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
import typing
|
||||
import os
|
||||
from src.dependencies import get_config_manager, has_permission
|
||||
from src.core.database import get_db
|
||||
from src.models.git import GitServerConfig, GitRepository
|
||||
@@ -21,7 +22,8 @@ from src.api.routes.git_schemas import (
|
||||
GitServerConfigSchema, GitServerConfigCreate,
|
||||
BranchSchema, BranchCreate,
|
||||
BranchCheckout, CommitSchema, CommitCreate,
|
||||
DeploymentEnvironmentSchema, DeployRequest, RepoInitRequest
|
||||
DeploymentEnvironmentSchema, DeployRequest, RepoInitRequest,
|
||||
RepoStatusBatchRequest, RepoStatusBatchResponse,
|
||||
)
|
||||
from src.services.git_service import GitService
|
||||
from src.core.logger import logger, belief_scope
|
||||
@@ -33,6 +35,69 @@ from ...services.llm_prompt_templates import (
|
||||
|
||||
router = APIRouter(tags=["git"])
|
||||
git_service = GitService()
|
||||
MAX_REPOSITORY_STATUS_BATCH = 50
|
||||
|
||||
|
||||
# [DEF:_build_no_repo_status_payload:Function]
|
||||
# @PURPOSE: Build a consistent status payload for dashboards without initialized repositories.
|
||||
# @PRE: None.
|
||||
# @POST: Returns a stable payload compatible with frontend repository status parsing.
|
||||
# @RETURN: dict
|
||||
def _build_no_repo_status_payload() -> dict:
|
||||
return {
|
||||
"is_dirty": False,
|
||||
"untracked_files": [],
|
||||
"modified_files": [],
|
||||
"staged_files": [],
|
||||
"current_branch": None,
|
||||
"upstream_branch": None,
|
||||
"has_upstream": False,
|
||||
"ahead_count": 0,
|
||||
"behind_count": 0,
|
||||
"is_diverged": False,
|
||||
"sync_state": "NO_REPO",
|
||||
"sync_status": "NO_REPO",
|
||||
"has_repo": False,
|
||||
}
|
||||
# [/DEF:_build_no_repo_status_payload:Function]
|
||||
|
||||
|
||||
# [DEF:_handle_unexpected_git_route_error:Function]
|
||||
# @PURPOSE: Convert unexpected route-level exceptions to stable 500 API responses.
|
||||
# @PRE: `error` is a non-HTTPException instance.
|
||||
# @POST: Raises HTTPException(500) with route-specific context.
|
||||
# @PARAM: route_name (str)
|
||||
# @PARAM: error (Exception)
|
||||
def _handle_unexpected_git_route_error(route_name: str, error: Exception) -> None:
|
||||
logger.error(f"[{route_name}][Coherence:Failed] {error}")
|
||||
raise HTTPException(status_code=500, detail=f"{route_name} failed: {str(error)}")
|
||||
# [/DEF:_handle_unexpected_git_route_error:Function]
|
||||
|
||||
|
||||
# [DEF:_resolve_repository_status:Function]
|
||||
# @PURPOSE: Resolve repository status for one dashboard with graceful NO_REPO semantics.
|
||||
# @PRE: `dashboard_id` is a valid integer.
|
||||
# @POST: Returns standard status payload or `NO_REPO` payload when repository path is absent.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @RETURN: dict
|
||||
def _resolve_repository_status(dashboard_id: int) -> dict:
|
||||
repo_path = git_service._get_repo_path(dashboard_id)
|
||||
if not os.path.exists(repo_path):
|
||||
logger.debug(
|
||||
f"[get_repository_status][Action] Repository is not initialized for dashboard {dashboard_id}"
|
||||
)
|
||||
return _build_no_repo_status_payload()
|
||||
|
||||
try:
|
||||
return git_service.get_status(dashboard_id)
|
||||
except HTTPException as e:
|
||||
if e.status_code == 404:
|
||||
logger.debug(
|
||||
f"[get_repository_status][Action] Repository is not initialized for dashboard {dashboard_id}"
|
||||
)
|
||||
return _build_no_repo_status_payload()
|
||||
raise
|
||||
# [/DEF:_resolve_repository_status:Function]
|
||||
|
||||
# [DEF:get_git_configs:Function]
|
||||
# @PURPOSE: List all configured Git servers.
|
||||
@@ -153,7 +218,9 @@ async def init_repository(
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"[init_repository][Coherence:Failed] Failed to init repository: {e}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
if isinstance(e, HTTPException):
|
||||
raise
|
||||
_handle_unexpected_git_route_error("init_repository", e)
|
||||
# [/DEF:init_repository:Function]
|
||||
|
||||
# [DEF:get_branches:Function]
|
||||
@@ -170,8 +237,10 @@ async def get_branches(
|
||||
with belief_scope("get_branches"):
|
||||
try:
|
||||
return git_service.list_branches(dashboard_id)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
_handle_unexpected_git_route_error("get_branches", e)
|
||||
# [/DEF:get_branches:Function]
|
||||
|
||||
# [DEF:create_branch:Function]
|
||||
@@ -190,8 +259,10 @@ async def create_branch(
|
||||
try:
|
||||
git_service.create_branch(dashboard_id, branch_data.name, branch_data.from_branch)
|
||||
return {"status": "success"}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("create_branch", e)
|
||||
# [/DEF:create_branch:Function]
|
||||
|
||||
# [DEF:checkout_branch:Function]
|
||||
@@ -210,8 +281,10 @@ async def checkout_branch(
|
||||
try:
|
||||
git_service.checkout_branch(dashboard_id, checkout_data.name)
|
||||
return {"status": "success"}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("checkout_branch", e)
|
||||
# [/DEF:checkout_branch:Function]
|
||||
|
||||
# [DEF:commit_changes:Function]
|
||||
@@ -230,8 +303,10 @@ async def commit_changes(
|
||||
try:
|
||||
git_service.commit_changes(dashboard_id, commit_data.message, commit_data.files)
|
||||
return {"status": "success"}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("commit_changes", e)
|
||||
# [/DEF:commit_changes:Function]
|
||||
|
||||
# [DEF:push_changes:Function]
|
||||
@@ -248,8 +323,10 @@ async def push_changes(
|
||||
try:
|
||||
git_service.push_changes(dashboard_id)
|
||||
return {"status": "success"}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("push_changes", e)
|
||||
# [/DEF:push_changes:Function]
|
||||
|
||||
# [DEF:pull_changes:Function]
|
||||
@@ -266,8 +343,10 @@ async def pull_changes(
|
||||
try:
|
||||
git_service.pull_changes(dashboard_id)
|
||||
return {"status": "success"}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("pull_changes", e)
|
||||
# [/DEF:pull_changes:Function]
|
||||
|
||||
# [DEF:sync_dashboard:Function]
|
||||
@@ -291,8 +370,10 @@ async def sync_dashboard(
|
||||
"dashboard_id": dashboard_id,
|
||||
"source_env_id": source_env_id
|
||||
})
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("sync_dashboard", e)
|
||||
# [/DEF:sync_dashboard:Function]
|
||||
|
||||
# [DEF:get_environments:Function]
|
||||
@@ -338,8 +419,10 @@ async def deploy_dashboard(
|
||||
"dashboard_id": dashboard_id,
|
||||
"environment_id": deploy_data.environment_id
|
||||
})
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("deploy_dashboard", e)
|
||||
# [/DEF:deploy_dashboard:Function]
|
||||
|
||||
# [DEF:get_history:Function]
|
||||
@@ -358,14 +441,16 @@ async def get_history(
|
||||
with belief_scope("get_history"):
|
||||
try:
|
||||
return git_service.get_commit_history(dashboard_id, limit)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
_handle_unexpected_git_route_error("get_history", e)
|
||||
# [/DEF:get_history:Function]
|
||||
|
||||
# [DEF:get_repository_status:Function]
|
||||
# @PURPOSE: Get current Git status for a dashboard repository.
|
||||
# @PRE: `dashboard_id` repository exists.
|
||||
# @POST: Returns the status of the working directory (staged, unstaged, untracked).
|
||||
# @PRE: `dashboard_id` is a valid integer.
|
||||
# @POST: Returns repository status; if repo is not initialized, returns `NO_REPO` payload.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @RETURN: dict
|
||||
@router.get("/repositories/{dashboard_id}/status")
|
||||
@@ -375,11 +460,57 @@ async def get_repository_status(
|
||||
):
|
||||
with belief_scope("get_repository_status"):
|
||||
try:
|
||||
return git_service.get_status(dashboard_id)
|
||||
return _resolve_repository_status(dashboard_id)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("get_repository_status", e)
|
||||
# [/DEF:get_repository_status:Function]
|
||||
|
||||
|
||||
# [DEF:get_repository_status_batch:Function]
|
||||
# @PURPOSE: Get Git statuses for multiple dashboard repositories in one request.
|
||||
# @PRE: `request.dashboard_ids` is provided.
|
||||
# @POST: Returns `statuses` map where each key is dashboard ID and value is repository status payload.
|
||||
# @PARAM: request (RepoStatusBatchRequest)
|
||||
# @RETURN: RepoStatusBatchResponse
|
||||
@router.post("/repositories/status/batch", response_model=RepoStatusBatchResponse)
|
||||
async def get_repository_status_batch(
|
||||
request: RepoStatusBatchRequest,
|
||||
_ = Depends(has_permission("plugin:git", "EXECUTE"))
|
||||
):
|
||||
with belief_scope("get_repository_status_batch"):
|
||||
dashboard_ids = list(dict.fromkeys(request.dashboard_ids))
|
||||
if len(dashboard_ids) > MAX_REPOSITORY_STATUS_BATCH:
|
||||
logger.warning(
|
||||
"[get_repository_status_batch][Action] Batch size %s exceeds limit %s. Truncating request.",
|
||||
len(dashboard_ids),
|
||||
MAX_REPOSITORY_STATUS_BATCH,
|
||||
)
|
||||
dashboard_ids = dashboard_ids[:MAX_REPOSITORY_STATUS_BATCH]
|
||||
|
||||
statuses = {}
|
||||
for dashboard_id in dashboard_ids:
|
||||
try:
|
||||
statuses[str(dashboard_id)] = _resolve_repository_status(dashboard_id)
|
||||
except HTTPException:
|
||||
statuses[str(dashboard_id)] = {
|
||||
**_build_no_repo_status_payload(),
|
||||
"sync_state": "ERROR",
|
||||
"sync_status": "ERROR",
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[get_repository_status_batch][Coherence:Failed] Failed for dashboard {dashboard_id}: {e}"
|
||||
)
|
||||
statuses[str(dashboard_id)] = {
|
||||
**_build_no_repo_status_payload(),
|
||||
"sync_state": "ERROR",
|
||||
"sync_status": "ERROR",
|
||||
}
|
||||
return RepoStatusBatchResponse(statuses=statuses)
|
||||
# [/DEF:get_repository_status_batch:Function]
|
||||
|
||||
# [DEF:get_repository_diff:Function]
|
||||
# @PURPOSE: Get Git diff for a dashboard repository.
|
||||
# @PRE: `dashboard_id` repository exists.
|
||||
@@ -399,8 +530,10 @@ async def get_repository_diff(
|
||||
try:
|
||||
diff_text = git_service.get_diff(dashboard_id, file_path, staged)
|
||||
return diff_text
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("get_repository_diff", e)
|
||||
# [/DEF:get_repository_diff:Function]
|
||||
|
||||
# [DEF:generate_commit_message:Function]
|
||||
@@ -466,9 +599,10 @@ async def generate_commit_message(
|
||||
)
|
||||
|
||||
return {"message": message}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to generate commit message: {e}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
_handle_unexpected_git_route_error("generate_commit_message", e)
|
||||
# [/DEF:generate_commit_message:Function]
|
||||
|
||||
# [/DEF:backend.src.api.routes.git:Module]
|
||||
|
||||
Reference in New Issue
Block a user