use std::fs;
use std::path::{Path, PathBuf};
use tokio::task::spawn_blocking;
use super::error::GitError;
use super::repo::{main_repo_root, resolve_git_dir, run_git_command, run_git_command_sync};
pub(crate) async fn detect_git_info(dir: PathBuf) -> Option<String> {
spawn_blocking(move || detect_git_info_sync(&dir))
.await
.ok()
.flatten()
}
pub(crate) async fn find_git_repo_root(dir: PathBuf) -> Option<PathBuf> {
spawn_blocking(move || find_git_repo_root_sync(&dir))
.await
.ok()
.flatten()
}
pub(crate) async fn create_worktree(
repo_path: PathBuf,
worktree_path: PathBuf,
branch_name: String,
start_ref: String,
) -> Result<(), GitError> {
spawn_blocking(move || {
let worktree_path = worktree_path.to_string_lossy().to_string();
run_git_command_sync(
&repo_path,
&[
"worktree",
"add",
"-b",
branch_name.as_str(),
worktree_path.as_str(),
start_ref.as_str(),
],
"Git worktree command failed",
)?;
Ok(())
})
.await?
}
pub(crate) async fn remove_worktree(worktree_path: PathBuf) -> Result<(), GitError> {
let repo_root = main_repo_root(worktree_path.clone()).await?;
let worktree_path = worktree_path.to_string_lossy().to_string();
run_git_command(
repo_root,
vec![
"worktree".to_string(),
"remove".to_string(),
"--force".to_string(),
worktree_path,
],
"Git worktree command failed".to_string(),
)
.await?;
Ok(())
}
pub(super) fn detect_git_info_sync(dir: &Path) -> Option<String> {
let repo_dir = find_git_repo(dir)?;
get_git_branch(&repo_dir)
}
fn find_git_repo(dir: &Path) -> Option<PathBuf> {
find_git_repo_root_sync(dir)
}
fn find_git_repo_root_sync(dir: &Path) -> Option<PathBuf> {
let mut current = dir.to_path_buf();
loop {
let git_dir = current.join(".git");
if git_dir.exists() {
let resolved_git_dir = resolve_git_dir(¤t)?;
return resolved_git_dir.is_dir().then_some(current);
}
if !current.pop() {
return None;
}
}
}
fn get_git_branch(repo_dir: &Path) -> Option<String> {
let git_dir = resolve_git_dir(repo_dir)?;
let head_path = git_dir.join("HEAD");
let content = fs::read_to_string(head_path).ok()?;
let content = content.trim();
if let Some(branch_ref) = content.strip_prefix("ref: refs/heads/") {
return Some(branch_ref.to_string());
}
if content.len() >= 7
&& content
.chars()
.all(|character| character.is_ascii_hexdigit())
{
return Some(format!("HEAD@{}", &content[..7]));
}
None
}
#[cfg(test)]
#[path = "worktree_test.rs"]
mod tests;