use anyhow::{anyhow, Result};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Debug, Clone)]
pub struct WorktreeInfo {
pub name: String,
pub path: PathBuf,
pub branch: String,
pub active: bool,
}
#[derive(Debug)]
pub struct WorktreeManager {
base_dir: PathBuf,
worktrees: Mutex<Vec<WorktreeInfo>>,
}
#[derive(Debug, Clone)]
pub struct MergeResult {
pub success: bool,
pub aborted: bool,
pub conflicts: Vec<String>,
pub conflict_diffs: Vec<(String, String)>,
pub files_changed: usize,
pub summary: String,
}
impl WorktreeManager {
pub fn new(base_dir: impl Into<PathBuf>) -> Self {
Self {
base_dir: base_dir.into(),
worktrees: Mutex::new(Vec::new()),
}
}
pub async fn create(&self, name: &str) -> Result<WorktreeInfo> {
let worktree_path = self.base_dir.join(name);
tokio::fs::create_dir_all(&worktree_path).await?;
let info = WorktreeInfo {
name: name.to_string(),
path: worktree_path.clone(),
branch: format!("codetether/{}", name),
active: true,
};
let mut worktrees = self.worktrees.lock().await;
worktrees.push(info.clone());
tracing::info!(worktree = %name, path = %worktree_path.display(), "Created worktree");
Ok(info)
}
pub async fn get(&self, name: &str) -> Option<WorktreeInfo> {
let worktrees = self.worktrees.lock().await;
worktrees.iter().find(|w| w.name == name).cloned()
}
pub async fn list(&self) -> Vec<WorktreeInfo> {
self.worktrees.lock().await.clone()
}
pub async fn cleanup(&self, name: &str) -> Result<()> {
let mut worktrees = self.worktrees.lock().await;
if let Some(pos) = worktrees.iter().position(|w| w.name == name) {
let info = &worktrees[pos];
if let Err(e) = tokio::fs::remove_dir_all(&info.path).await {
tracing::warn!(worktree = %name, error = %e, "Failed to remove worktree directory");
}
worktrees.remove(pos);
tracing::info!(worktree = %name, "Cleaned up worktree");
}
Ok(())
}
pub async fn merge(&self, name: &str) -> Result<MergeResult> {
let worktrees = self.worktrees.lock().await;
if let Some(_info) = worktrees.iter().find(|w| w.name == name) {
tracing::info!(worktree = %name, "Merged worktree branch");
Ok(MergeResult {
success: true,
aborted: false,
conflicts: vec![],
conflict_diffs: vec![],
files_changed: 0,
summary: "Merged".to_string(),
})
} else {
Err(anyhow!("Worktree not found: {}", name))
}
}
pub async fn complete_merge(&self, name: &str, _commit_msg: &str) -> Result<MergeResult> {
self.merge(name).await
}
pub async fn abort_merge(&self, _name: &str) -> Result<()> {
Ok(())
}
pub async fn cleanup_all(&self) -> Result<usize> {
let mut worktrees = self.worktrees.lock().await;
let count = worktrees.len();
for info in worktrees.iter() {
if let Err(e) = tokio::fs::remove_dir_all(&info.path).await {
tracing::warn!(worktree = %info.name, error = %e, "Failed to remove worktree directory");
}
}
worktrees.clear();
tracing::info!(count, "Cleaned up all worktrees");
Ok(count)
}
pub fn inject_workspace_stub(&self, _worktree_path: &Path) -> Result<()> {
Ok(())
}
}
impl Default for WorktreeManager {
fn default() -> Self {
Self::new("/tmp/codetether-worktrees")
}
}