use std::path::PathBuf;
use std::process::Command;
use axum::Json;
use axum::extract::State;
use serde::Serialize;
use super::{ApiError, RuntimeApiState};
#[derive(Debug, Serialize)]
pub(crate) struct WorkspaceStatusResponse {
workspace: PathBuf,
git_repo: bool,
branch: Option<String>,
staged: usize,
unstaged: usize,
untracked: usize,
ahead: Option<u32>,
behind: Option<u32>,
}
pub(crate) async fn workspace_status(
State(state): State<RuntimeApiState>,
) -> Result<Json<WorkspaceStatusResponse>, ApiError> {
Ok(Json(collect_workspace_status(&state.workspace)))
}
pub(crate) fn collect_workspace_status(workspace: &std::path::Path) -> WorkspaceStatusResponse {
let mut status = WorkspaceStatusResponse {
workspace: workspace.to_path_buf(),
git_repo: false,
branch: None,
staged: 0,
unstaged: 0,
untracked: 0,
ahead: None,
behind: None,
};
let Some(repo_check) = run_git(workspace, &["rev-parse", "--is-inside-work-tree"]) else {
return status;
};
if repo_check.trim() != "true" {
return status;
}
status.git_repo = true;
status.branch = run_git(workspace, &["rev-parse", "--abbrev-ref", "HEAD"])
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
if let Some(porcelain) = run_git(workspace, &["status", "--porcelain=v1"]) {
for line in porcelain.lines() {
if line.starts_with("??") {
status.untracked += 1;
continue;
}
let chars: Vec<char> = line.chars().collect();
if chars.len() >= 2 {
if chars[0] != ' ' {
status.staged += 1;
}
if chars[1] != ' ' {
status.unstaged += 1;
}
}
}
}
if let Some(counts) = run_git(
workspace,
&["rev-list", "--left-right", "--count", "@{upstream}...HEAD"],
) {
let mut parts = counts.split_whitespace();
if let (Some(behind), Some(ahead)) = (parts.next(), parts.next()) {
status.behind = behind.parse::<u32>().ok();
status.ahead = ahead.parse::<u32>().ok();
}
}
status
}
pub(crate) fn run_git(workspace: &std::path::Path, args: &[&str]) -> Option<String> {
let output = Command::new("git")
.args(args)
.current_dir(workspace)
.output()
.ok()?;
if !output.status.success() {
return None;
}
String::from_utf8(output.stdout).ok()
}