Skip to main content

omne_cli/
volume.rs

1//! Volume root detection.
2//!
3//! The primary entry point is `find_omne_root`, which walks up from a
4//! starting directory looking for an ancestor that contains a `.omne/`
5//! subdirectory. `upgrade` and `validate` use this so they work from
6//! any subdirectory of a volume; `init` deliberately does **not** walk
7//! up (R13 — `init` creates in the current directory only).
8//!
9//! The Python module's `is_mounted()` helper is **not ported** per R12:
10//! the submodule/mount model was removed when releases-based
11//! distribution replaced the submodule architecture. Dead code
12//! deletion happens in Unit 12.
13
14#![allow(dead_code)]
15
16use std::path::{Path, PathBuf};
17
18/// Walk up from `start` looking for an ancestor that contains a
19/// `.omne/` subdirectory. Returns the first match, or `None` when the
20/// walk reaches the filesystem root without finding one.
21///
22/// `start` is canonicalized first so relative paths, `..` segments,
23/// and symlinks are resolved before the walk begins. If `start` does
24/// not exist or cannot be canonicalized, the function returns `None`
25/// — the caller surfaces this as `CliError::NotAVolume` at the
26/// command boundary.
27pub fn find_omne_root(start: &Path) -> Option<PathBuf> {
28    let mut current = start.canonicalize().ok()?;
29    loop {
30        if current.join(".omne").is_dir() {
31            return Some(current);
32        }
33        if !current.pop() {
34            return None;
35        }
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use std::fs;
42
43    use tempfile::TempDir;
44
45    use super::*;
46
47    #[test]
48    fn finds_root_when_omne_exists_at_start() {
49        let tmp = TempDir::new().expect("create tempdir");
50        fs::create_dir(tmp.path().join(".omne")).expect("create .omne");
51
52        let found = find_omne_root(tmp.path());
53        let expected = tmp.path().canonicalize().expect("canonicalize tmpdir");
54        assert_eq!(found, Some(expected));
55    }
56
57    #[test]
58    fn finds_root_from_subdirectory() {
59        let tmp = TempDir::new().expect("create tempdir");
60        fs::create_dir(tmp.path().join(".omne")).expect("create .omne");
61
62        let deep = tmp.path().join("src").join("deep");
63        fs::create_dir_all(&deep).expect("create deep subdir");
64
65        let found = find_omne_root(&deep);
66        let expected = tmp.path().canonicalize().expect("canonicalize tmpdir");
67        assert_eq!(found, Some(expected));
68    }
69
70    #[test]
71    fn walk_up_matches_actual_ancestor_chain() {
72        // Do NOT create `.omne` in the tempdir itself. Derive the
73        // expected result from the canonicalized ancestor chain so the
74        // test stays hermetic even if some ambient ancestor (e.g.
75        // `/tmp/.omne` on a developer machine) already contains a
76        // `.omne` directory. In a clean environment this still asserts
77        // `None`; on a contaminated environment it asserts the
78        // contaminating ancestor's path instead of failing.
79        let tmp = TempDir::new().expect("create tempdir");
80        let canonical_start = tmp.path().canonicalize().expect("canonicalize tmpdir");
81        let expected = canonical_start
82            .ancestors()
83            .find(|ancestor| ancestor.join(".omne").is_dir())
84            .map(Path::to_path_buf);
85
86        let found = find_omne_root(tmp.path());
87        assert_eq!(found, expected);
88    }
89
90    #[test]
91    fn returns_none_when_start_does_not_exist() {
92        let tmp = TempDir::new().expect("create tempdir");
93        let missing = tmp.path().join("does-not-exist");
94
95        let found = find_omne_root(&missing);
96        assert_eq!(found, None);
97    }
98}