MCPcopy Create free account
hub / github.com/atomicdotdev/atomic / normalize_path_with_root

Function normalize_path_with_root

atomic-repository/src/tracking/helpers.rs:28–86  ·  view source on GitHub ↗

Normalize a path for storage, optionally stripping a repo root prefix. This handles the case where absolute paths are accidentally passed in. On macOS, `/tmp` is a symlink to `/private/tmp`, so we try both the given root and its canonical form. # Arguments `path` - The path to normalize `repo_root` - Optional repository root to strip from absolute paths

(path: &Path, repo_root: Option<&Path>)

Source from the content-addressed store, hash-verified

26/// * `path` - The path to normalize
27/// * `repo_root` - Optional repository root to strip from absolute paths
28pub fn normalize_path_with_root(path: &Path, repo_root: Option<&Path>) -> String {
29 let mut path_to_normalize = path.to_path_buf();
30
31 // If path is absolute and we have a repo root, try to make it relative.
32 // We check both Path::is_absolute() (handles native absolute paths) and
33 // a leading '/' in the string representation (handles Unix-style paths on
34 // Windows, where "/repo/src" is not considered absolute by the OS but must
35 // still be treated as absolute for prefix-stripping purposes).
36 let path_str_raw = path.to_string_lossy();
37 let is_absolute = path_to_normalize.is_absolute() || path_str_raw.starts_with('/');
38
39 if is_absolute {
40 if let Some(root) = repo_root {
41 // Try stripping the root directly
42 if let Ok(rel) = path_to_normalize.strip_prefix(root) {
43 path_to_normalize = rel.to_path_buf();
44 } else if let Ok(canonical_root) = root.canonicalize() {
45 // On macOS, /tmp -> /private/tmp, so try canonical
46 if let Ok(rel) = path_to_normalize.strip_prefix(&canonical_root) {
47 path_to_normalize = rel.to_path_buf();
48 }
49 } else {
50 // Path::strip_prefix uses OS path semantics, which on Windows
51 // won't match Unix-style "/repo" against "/repo/src/main.rs"
52 // as a proper prefix. Fall back to string-level stripping so
53 // that Unix-style paths work correctly on Windows in tests and
54 // cross-platform scenarios.
55 let root_str = root.to_string_lossy();
56 let root_str = root_str.replace('\\', "/");
57 let path_str = path_str_raw.replace('\\', "/");
58 let root_with_sep = if root_str.ends_with('/') {
59 root_str.to_string()
60 } else {
61 format!("{}/", root_str)
62 };
63 if let Some(rel) = path_str.strip_prefix(root_with_sep.as_str()) {
64 path_to_normalize = PathBuf::from(rel);
65 } else if path_str == root_str {
66 path_to_normalize = PathBuf::new();
67 }
68 }
69 }
70 }
71
72 let path_str = path_to_normalize.to_string_lossy();
73
74 // Convert to forward slashes and remove trailing slash
75 let normalized = path_str
76 .replace('\\', "/")
77 .trim_end_matches('/')
78 .to_string();
79
80 // Handle empty path (current directory)
81 if normalized.is_empty() {
82 ".".to_string()
83 } else {
84 normalized
85 }

Callers 2

addMethod · 0.85
normalize_pathFunction · 0.85

Calls 3

replaceMethod · 0.45
as_strMethod · 0.45
is_emptyMethod · 0.45

Tested by

no test coverage detected