use std::env;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use crate::utils::env::{EnvSource, SystemEnv};
fn resolve_scratch_dir(
env: &impl EnvSource,
git_root: impl FnOnce() -> Result<PathBuf>,
) -> Result<PathBuf> {
if let Some(ai_scratch) = env.var("AI_SCRATCH") {
if let Some(git_root_path) = ai_scratch.strip_prefix("git-root:") {
Ok(git_root()?.join(git_root_path))
} else {
Ok(PathBuf::from(ai_scratch))
}
} else {
let tmpdir = env.var("TMPDIR").unwrap_or_else(|| "/tmp".to_string());
Ok(PathBuf::from(tmpdir))
}
}
pub fn get_ai_scratch_dir() -> Result<PathBuf> {
resolve_scratch_dir(&SystemEnv, find_git_root)
}
pub fn get_ai_scratch_dir_at(repo_root: &Path) -> Result<PathBuf> {
resolve_scratch_dir(&SystemEnv, || find_git_root_from_path(repo_root))
}
fn find_git_root() -> Result<PathBuf> {
let current_dir = env::current_dir().context("Failed to get current directory")?;
find_git_root_from_path(¤t_dir)
}
fn find_git_root_from_path(start_path: &Path) -> Result<PathBuf> {
let mut current = start_path;
loop {
let git_dir = current.join(".git");
if git_dir.exists() {
return Ok(current.to_path_buf());
}
match current.parent() {
Some(parent) => current = parent,
None => {
return Err(anyhow::anyhow!(
"No git repository found in current directory or any parent directory"
))
}
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::test_support::env::MapEnv;
use tempfile::TempDir;
fn scratch_dir_with(env: &impl EnvSource, repo_root: &Path) -> Result<PathBuf> {
resolve_scratch_dir(env, || super::find_git_root_from_path(repo_root))
}
#[test]
fn get_ai_scratch_dir_with_direct_path() {
let env = MapEnv::new().with("AI_SCRATCH", "/custom/scratch/path");
let result = scratch_dir_with(&env, Path::new("/unused")).unwrap();
assert_eq!(result, PathBuf::from("/custom/scratch/path"));
}
#[test]
fn get_ai_scratch_dir_fallback_to_tmpdir() {
let env = MapEnv::new().with("TMPDIR", "/custom/tmp");
let result = scratch_dir_with(&env, Path::new("/unused")).unwrap();
assert_eq!(result, PathBuf::from("/custom/tmp"));
}
#[test]
fn get_ai_scratch_dir_at_resolves_git_root_from_injected_path() {
let temp_dir = {
std::fs::create_dir_all("tmp").ok();
TempDir::new_in("tmp").unwrap()
};
std::fs::create_dir(temp_dir.path().join(".git")).unwrap();
let env = MapEnv::new().with("AI_SCRATCH", "git-root:scratch");
let result = scratch_dir_with(&env, temp_dir.path()).unwrap();
assert_eq!(result, temp_dir.path().join("scratch"));
}
#[test]
fn public_wrappers_resolve_without_panicking() {
let _ = get_ai_scratch_dir();
let _ = get_ai_scratch_dir_at(Path::new("/tmp"));
}
#[test]
fn find_git_root_from_path() {
let temp_dir = {
std::fs::create_dir_all("tmp").ok();
TempDir::new_in("tmp").unwrap()
};
let git_dir = temp_dir.path().join(".git");
std::fs::create_dir(&git_dir).unwrap();
let sub_dir = temp_dir.path().join("subdir").join("deeper");
std::fs::create_dir_all(&sub_dir).unwrap();
let result = super::find_git_root_from_path(&sub_dir).unwrap();
assert_eq!(result, temp_dir.path());
}
}