Skip to main content

the_code_graph_cli/
project.rs

1use 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(&current) {
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        // No .git created - should eventually hit a blocklisted root or no project
70        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}