use std::path::{Path, PathBuf};
pub fn discover(start: &Path) -> Option<PathBuf> {
let mut current: Option<&Path> = Some(start);
while let Some(dir) = current {
if dir.join(".git").exists() {
return Some(dir.to_path_buf());
}
current = dir.parent();
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::fs;
fn unique_root(label: u32) -> PathBuf {
let dir = env::temp_dir().join(format!("ptuf-repo-test-{}-{}", std::process::id(), label));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).expect("temp root");
dir
}
#[test]
fn discover_finds_git_dir_on_starting_path() {
let base = unique_root(line!());
let root = base.join("a");
fs::create_dir_all(root.join(".git")).expect("git dir");
let found = discover(&root).expect("should find");
assert_eq!(found, root);
let _ = fs::remove_dir_all(&base);
}
#[test]
fn discover_walks_upwards() {
let base = unique_root(line!());
let root = base.join("b");
let nested = root.join("nested/deep");
fs::create_dir_all(&nested).expect("dirs");
fs::create_dir_all(root.join(".git")).expect("git dir");
let found = discover(&nested).expect("should find");
assert_eq!(found, root);
let _ = fs::remove_dir_all(&base);
}
#[test]
fn discover_handles_git_file_for_submodules() {
let base = unique_root(line!());
let root = base.join("c");
fs::create_dir_all(&root).expect("dirs");
fs::write(root.join(".git"), "gitdir: ../parent/.git/modules/c").expect("git file");
let found = discover(&root).expect("should find");
assert_eq!(found, root);
let _ = fs::remove_dir_all(&base);
}
#[test]
fn discover_returns_none_if_no_git_anywhere() {
let base = unique_root(line!());
let root = base.join("d/no-repo");
fs::create_dir_all(&root).expect("dirs");
if let Some(found) = discover(&root) {
assert!(
!found.starts_with(&base),
"unexpected .git inside test tree at {found:?}"
);
}
let _ = fs::remove_dir_all(&base);
}
}