1use std::error::Error;
2use std::path::{Path, PathBuf};
3
4pub fn find_repository_root(start_path: &Path) -> Result<PathBuf, Box<dyn Error>> {
28 let start = if start_path.is_relative() {
29 std::env::current_dir()?.join(start_path)
30 } else {
31 start_path.to_path_buf()
32 };
33
34 let mut current = start.canonicalize()?;
35
36 loop {
37 if current.join(".git").exists() {
38 return Ok(current);
39 }
40
41 match current.parent() {
42 Some(parent) => current = parent.to_path_buf(),
43 None => {
44 return Err(format!(
45 "Not in a git repository (or any of the parent directories): {}",
46 start_path.display()
47 )
48 .into());
49 }
50 }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use std::fs;
58 use std::path::PathBuf;
59 use tempfile::TempDir;
60
61 #[test]
62 fn test_find_repository_root_in_git_repo() {
63 let temp_dir = TempDir::new().unwrap();
65 let repo_root = temp_dir.path();
66
67 fs::create_dir(repo_root.join(".git")).unwrap();
69
70 let sub_dir = repo_root.join("src").join("commands");
72 fs::create_dir_all(&sub_dir).unwrap();
73
74 assert_eq!(
76 find_repository_root(repo_root).unwrap(),
77 repo_root.canonicalize().unwrap()
78 );
79 assert_eq!(
80 find_repository_root(&sub_dir).unwrap(),
81 repo_root.canonicalize().unwrap()
82 );
83 assert_eq!(
84 find_repository_root(&repo_root.join("src")).unwrap(),
85 repo_root.canonicalize().unwrap()
86 );
87 }
88
89 #[test]
90 fn test_find_repository_root_not_in_repo() {
91 let temp_dir = TempDir::new().unwrap();
93 let result = find_repository_root(temp_dir.path());
94
95 assert!(result.is_err());
96 assert!(
97 result
98 .unwrap_err()
99 .to_string()
100 .contains("Not in a git repository")
101 );
102 }
103
104 #[test]
105 fn test_find_repository_root_with_relative_path() {
106 let temp_dir = TempDir::new().unwrap();
108 let repo_root = temp_dir.path();
109
110 fs::create_dir(repo_root.join(".git")).unwrap();
112
113 fs::write(repo_root.join("test.txt"), "test content").unwrap();
115
116 let original_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/tmp"));
118
119 if std::env::set_current_dir(repo_root).is_ok() {
120 let result = find_repository_root(Path::new("."));
122 assert!(result.is_ok());
123
124 let found_root = result.unwrap();
125 assert!(found_root.join(".git").exists());
126
127 let _ = std::env::set_current_dir(original_dir);
129 } else {
130 let result = find_repository_root(repo_root);
132 assert!(result.is_ok());
133
134 let found_root = result.unwrap();
135 assert_eq!(found_root, repo_root.canonicalize().unwrap());
136 }
137 }
138}