//! Git API endpoints — status and diff. use axum::{ extract::{Query, State}, http::StatusCode, Json, }; use serde::{Deserialize, Serialize}; use crate::AppState; type ApiResult = Result, (StatusCode, Json)>; fn api_err(msg: impl std::fmt::Display) -> (StatusCode, Json) { ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": msg.to_string() })), ) } // ── Types ───────────────────────────────────────────────────────────────────── #[derive(Debug, Serialize)] pub struct GitFileStatus { pub path: String, pub status: String, // "M", "A", "D", "?", "R", etc. } #[derive(Debug, Deserialize)] pub struct DiffQuery { pub path: Option, } // ── Handlers ────────────────────────────────────────────────────────────────── /// GET /api/git/status — returns list of changed files. pub async fn git_status( State(state): State, ) -> ApiResult> { let project_path = &state.config.project_path; let output = tokio::process::Command::new("git") .args(["status", "--porcelain"]) .current_dir(project_path) .output() .await .map_err(|e| api_err(format!("git status failed: {e}")))?; let stdout = String::from_utf8_lossy(&output.stdout); let mut files = Vec::new(); for line in stdout.lines() { if line.len() < 4 { continue; } let xy = &line[..2]; let path = line[3..].trim(); // Handle renames: "old -> new" let file_path = if let Some(arrow) = path.find(" -> ") { &path[arrow + 4..] } else { path }; // Determine status badge: prefer index status (first char) let status = if xy.starts_with('M') || xy.ends_with('M') { "M" } else if xy.starts_with('A') || xy.starts_with('?') { "A" } else if xy.starts_with('D') || xy.ends_with('D') { "D" } else if xy.starts_with('R') { "R" } else { "M" }; files.push(GitFileStatus { path: file_path.to_string(), status: status.to_string(), }); } Ok(Json(files)) } /// GET /api/git/diff?path= — returns unified diff. pub async fn git_diff( State(state): State, Query(q): Query, ) -> ApiResult { let project_path = &state.config.project_path; let mut cmd = tokio::process::Command::new("git"); cmd.arg("diff").current_dir(project_path); if let Some(ref path) = q.path { cmd.arg("--").arg(path); } let output = cmd .output() .await .map_err(|e| api_err(format!("git diff failed: {e}")))?; let diff = String::from_utf8_lossy(&output.stdout).to_string(); Ok(Json(diff)) }