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}