use std::path::PathBuf;
use anyhow::Result;
use endringer_core::types::{CommitId, WorktreeDetail, WorktreeState};
use gix::Repository;
pub(crate) fn worktree_details(repo: &Repository) -> Result<Vec<WorktreeDetail>> {
let git_dir = repo.git_dir();
let worktrees_dir = git_dir.join("worktrees");
if !worktrees_dir.is_dir() {
return Ok(vec![]);
}
let mut details: Vec<WorktreeDetail> = std::fs::read_dir(&worktrees_dir)?
.filter_map(|entry| entry.ok())
.filter(|e| e.path().is_dir())
.map(|e| read_worktree_detail(&e.path()))
.collect();
details.sort_by(|a, b| a.id.cmp(&b.id));
Ok(details)
}
fn read_worktree_detail(admin_dir: &std::path::Path) -> WorktreeDetail {
let id = admin_dir
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_owned();
let path = read_worktree_path(admin_dir)
.unwrap_or_else(|| PathBuf::from("(unknown)"));
let state = if !path.exists() {
WorktreeState::MissingPath
} else if !path.join(".git").exists() {
WorktreeState::MissingGitFile
} else {
WorktreeState::Present
};
let current_branch = read_head_branch(admin_dir)
.unwrap_or_else(|| "(detached)".to_owned());
let head_commit_id = read_head_commit(admin_dir);
let lock_path = admin_dir.join("locked");
let is_locked = lock_path.exists();
let lock_reason = if is_locked {
std::fs::read_to_string(&lock_path).ok()
.map(|s| s.trim().to_owned())
.filter(|s| !s.is_empty())
} else {
None
};
WorktreeDetail {
id,
path,
current_branch,
head_commit_id,
is_locked,
lock_reason,
state,
}
}
fn read_worktree_path(admin_dir: &std::path::Path) -> Option<PathBuf> {
let gitdir_content = std::fs::read_to_string(admin_dir.join("gitdir")).ok()?;
let gitdir_path = std::path::Path::new(gitdir_content.trim());
let resolved = if gitdir_path.is_absolute() {
gitdir_path.to_path_buf()
} else {
admin_dir.join(gitdir_path)
};
resolved.canonicalize().ok().and_then(|p| p.parent().map(|par| par.to_path_buf()))
}
fn read_head_branch(admin_dir: &std::path::Path) -> Option<String> {
let content = std::fs::read_to_string(admin_dir.join("HEAD")).ok()?;
let trimmed = content.trim();
if let Some(refname) = trimmed.strip_prefix("ref: refs/heads/") {
Some(refname.to_owned())
} else {
None }
}
fn read_head_commit(admin_dir: &std::path::Path) -> Option<CommitId> {
let content = std::fs::read_to_string(admin_dir.join("HEAD")).ok()?;
let trimmed = content.trim();
if !trimmed.starts_with("ref: ") && trimmed.len() >= 40 {
return CommitId::from_hex(trimmed).ok();
}
None
}