use std::{
collections::HashMap,
path::{Path, PathBuf},
process::Command,
sync::{LazyLock, Mutex, PoisonError},
};
static SCOPE_CACHE: LazyLock<Mutex<HashMap<PathBuf, PathBuf>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub(crate) fn resolve_session_scope_path(cwd: &Path) -> PathBuf {
resolve_session_scope_path_with_cache(cwd, &SCOPE_CACHE, &mut run_git_command)
}
fn resolve_session_scope_path_with_cache(
cwd: &Path,
cache: &Mutex<HashMap<PathBuf, PathBuf>>,
run_git: &mut impl FnMut(&Path, &[&str]) -> Option<GitCommandResult>,
) -> PathBuf {
let lock_cache = || cache.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(path) = lock_cache().get(cwd) {
return path.clone();
}
let path = discover_session_scope_path(cwd, run_git);
lock_cache()
.entry(cwd.to_path_buf())
.or_insert(path)
.clone()
}
fn discover_session_scope_path(
cwd: &Path,
run_git: &mut impl FnMut(&Path, &[&str]) -> Option<GitCommandResult>,
) -> PathBuf {
if is_inside_git_worktree(cwd, run_git)
&& let Some(path) = primary_worktree_path(cwd, run_git)
{
return canonicalize_or_original(path);
}
cwd.to_path_buf()
}
fn run_git_command(cwd: &Path, args: &[&str]) -> Option<GitCommandResult> {
let output = Command::new("git")
.args(["-C"])
.arg(cwd)
.args(args)
.output()
.ok()?;
Some(GitCommandResult {
success: output.status.success(),
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
})
}
struct GitCommandResult {
success: bool,
stdout: String,
}
fn is_inside_git_worktree(
cwd: &Path,
run_git: &mut impl FnMut(&Path, &[&str]) -> Option<GitCommandResult>,
) -> bool {
let Some(output) = run_git(cwd, &["rev-parse", "--is-inside-work-tree"]) else {
return false;
};
output.success && output.stdout.trim() == "true"
}
fn primary_worktree_path(
cwd: &Path,
run_git: &mut impl FnMut(&Path, &[&str]) -> Option<GitCommandResult>,
) -> Option<PathBuf> {
let output = run_git(cwd, &["worktree", "list", "--porcelain"])?;
if !output.success {
return None;
}
output
.stdout
.lines()
.find_map(|line| line.strip_prefix("worktree ").map(PathBuf::from))
}
fn canonicalize_or_original(path: PathBuf) -> PathBuf {
path.canonicalize().unwrap_or(path)
}