use git2::{build::CheckoutBuilder, Oid, Repository};
use std::path::{Path, PathBuf};
use std::process::Command;
use super::error::{GitError, Result};
pub struct WorktreeManager;
impl WorktreeManager {
pub fn get_or_create_worktree(
bare_repo_path: &Path,
_git_ref: &str,
oid: Oid,
worktree_path: &Path,
) -> Result<PathBuf> {
if worktree_path.exists() {
Self::force_checkout(worktree_path, oid)?;
} else {
Self::create_worktree(bare_repo_path, oid, worktree_path)?;
}
Ok(worktree_path.to_path_buf())
}
fn create_worktree(bare_repo_path: &Path, oid: Oid, worktree_path: &Path) -> Result<()> {
if let Some(parent) = worktree_path.parent() {
std::fs::create_dir_all(parent)?;
}
let oid_str = oid.to_string();
tracing::debug!(
"Creating git worktree at {} for commit {}",
worktree_path.display(),
oid_str
);
let output = Command::new("git")
.arg("-C")
.arg(bare_repo_path)
.arg("worktree")
.arg("add")
.arg("--detach")
.arg(worktree_path)
.arg(&oid_str)
.output()
.map_err(|e| {
GitError::WorktreeFailed(format!(
"Failed to spawn 'git worktree add' for bare repo {}: {}. Ensure the 'git' CLI is installed and in PATH.",
bare_repo_path.display(),
e
))
})?;
if !output.status.success() {
return Err(GitError::WorktreeFailed(format!(
"Failed to create worktree: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
tracing::debug!("Git worktree created successfully");
Ok(())
}
fn force_checkout(worktree_path: &Path, oid: Oid) -> Result<()> {
tracing::debug!(
"Force checking out commit {} in worktree {}",
oid,
worktree_path.display()
);
let repo = Repository::open(worktree_path)?;
let commit = repo.find_commit(oid)?;
let tree = commit.tree()?;
let mut checkout_builder = CheckoutBuilder::new();
checkout_builder.force();
checkout_builder.remove_untracked(true);
repo.checkout_tree(tree.as_object(), Some(&mut checkout_builder))?;
repo.set_head_detached(oid)?;
Ok(())
}
#[allow(dead_code)]
pub fn remove_worktree(bare_repo_path: &Path, worktree_path: &Path) -> Result<()> {
let output = Command::new("git")
.arg("-C")
.arg(bare_repo_path)
.arg("worktree")
.arg("remove")
.arg("--force")
.arg(worktree_path)
.output()?;
if !output.status.success() {
return Err(GitError::WorktreeFailed(format!(
"Failed to remove worktree: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
#[allow(dead_code)]
pub fn prune_worktrees(bare_repo_path: &Path) -> Result<()> {
let output = Command::new("git")
.arg("-C")
.arg(bare_repo_path)
.arg("worktree")
.arg("prune")
.output()?;
if !output.status.success() {
return Err(GitError::WorktreeFailed(format!(
"Failed to prune worktrees: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_worktree_manager_structure() {
}
}