the_code_graph_cli/
project.rs1use domain::error::{CodeGraphError, Result};
2use std::path::{Path, PathBuf};
3
4const BLOCKLIST: &[&str] = &["/", "/home", "/Users", "/tmp", "/var"];
5
6pub fn find_project_root(start: &Path) -> Result<PathBuf> {
7 let mut current = start
8 .canonicalize()
9 .map_err(|e| CodeGraphError::FileSystem {
10 path: start.into(),
11 source: e,
12 })?;
13
14 loop {
15 if is_blocklisted(¤t) {
16 return Err(CodeGraphError::BlocklistedRoot(current));
17 }
18 if current.join(".git").is_dir() {
19 return Ok(current);
20 }
21 if !current.pop() {
22 return Err(CodeGraphError::NoProject);
23 }
24 }
25}
26
27fn is_blocklisted(path: &Path) -> bool {
28 let s = path.to_string_lossy();
29 BLOCKLIST.contains(&s.as_ref()) || std::env::var("HOME").ok().is_some_and(|h| s == *h)
30}
31
32pub fn ensure_data_dir(project_root: &Path) -> Result<PathBuf> {
33 let dir = project_root.join(".code-graph");
34 std::fs::create_dir_all(&dir).map_err(|e| CodeGraphError::FileSystem {
35 path: dir.clone(),
36 source: e,
37 })?;
38 Ok(dir)
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44 use std::fs;
45
46 #[test]
47 fn finds_root_with_git_dir() {
48 let tmp = tempfile::tempdir().unwrap();
49 let root = tmp.path();
50 fs::create_dir(root.join(".git")).unwrap();
51 let found = find_project_root(root).unwrap();
52 assert_eq!(found, root.canonicalize().unwrap());
53 }
54
55 #[test]
56 fn walks_up_to_find_git_dir() {
57 let tmp = tempfile::tempdir().unwrap();
58 let root = tmp.path();
59 fs::create_dir(root.join(".git")).unwrap();
60 let nested = root.join("src").join("deep");
61 fs::create_dir_all(&nested).unwrap();
62 let found = find_project_root(&nested).unwrap();
63 assert_eq!(found, root.canonicalize().unwrap());
64 }
65
66 #[test]
67 fn no_git_dir_returns_no_project_or_blocklisted() {
68 let tmp = tempfile::tempdir().unwrap();
69 let result = find_project_root(tmp.path());
71 assert!(result.is_err());
72 }
73
74 #[test]
75 fn blocklisted_root_slash() {
76 let result = find_project_root(Path::new("/"));
77 assert!(matches!(result, Err(CodeGraphError::BlocklistedRoot(_))));
78 }
79
80 #[test]
81 fn ensure_data_dir_creates_directory() {
82 let tmp = tempfile::tempdir().unwrap();
83 let data = ensure_data_dir(tmp.path()).unwrap();
84 assert_eq!(data, tmp.path().join(".code-graph"));
85 assert!(data.is_dir());
86 }
87
88 #[test]
89 fn ensure_data_dir_idempotent() {
90 let tmp = tempfile::tempdir().unwrap();
91 ensure_data_dir(tmp.path()).unwrap();
92 let data = ensure_data_dir(tmp.path()).unwrap();
93 assert!(data.is_dir());
94 }
95}