objects/worktree/
worktree_reserved.rs1use std::path::{Component, Path};
12
13#[must_use]
18pub fn is_reserved_worktree_path(path: &Path) -> bool {
19 first_normal_component(path).is_some_and(is_reserved_root_name)
20}
21
22#[must_use]
27pub fn is_reserved_directory_child(parent: &Path, name: &str) -> bool {
28 if is_reserved_worktree_path(parent) {
29 return true;
30 }
31 is_worktree_root(parent) && is_reserved_root_name(std::ffi::OsStr::new(name))
32}
33
34fn is_reserved_root_name(name: &std::ffi::OsStr) -> bool {
35 let Some(name) = name.to_str() else {
36 return false;
37 };
38 name == ".heddle"
39 || name == ".heddle.identity"
40 || name == ".identity.lock"
41 || name == ".identity.tmp"
42 || name.starts_with(".identity.tmp.")
43}
44
45fn is_worktree_root(path: &Path) -> bool {
46 path.as_os_str().is_empty() || path == Path::new(".")
47}
48
49fn first_normal_component(path: &Path) -> Option<&std::ffi::OsStr> {
50 for component in path.components() {
51 match component {
52 Component::CurDir => continue,
53 Component::Normal(name) => return Some(name),
54 Component::ParentDir | Component::Prefix(_) | Component::RootDir => return None,
55 }
56 }
57 None
58}
59
60#[cfg(test)]
61mod tests {
62 use std::path::Path;
63
64 use super::{is_reserved_directory_child, is_reserved_worktree_path};
65
66 #[test]
67 fn reserves_root_heddle_tree_and_identity() {
68 for path in [
69 ".heddle",
70 ".heddle/identity.toml",
71 ".heddle/objects/pack",
72 ".heddle/info/exclude",
73 "./.heddle/identity.toml",
74 ] {
75 assert!(
76 is_reserved_worktree_path(Path::new(path)),
77 "expected reserved: {path}"
78 );
79 }
80 }
81
82 #[test]
83 fn reserves_pointer_checkout_cursor_artifacts() {
84 for path in [
85 ".heddle.identity",
86 "./.heddle.identity",
87 ".identity.lock",
88 ".identity.tmp.123.0",
89 ".identity.tmp",
90 ] {
91 assert!(
92 is_reserved_worktree_path(Path::new(path)),
93 "expected reserved: {path}"
94 );
95 }
96 }
97
98 #[test]
99 fn does_not_reserve_nested_or_unrelated_paths() {
100 for path in [
101 "",
102 ".",
103 "src/main.rs",
104 "heddle",
105 ".heddleignore",
106 "examples/calculator/.heddle/identity.toml",
107 "examples/calculator/.heddle",
108 "examples/foo/.heddle.identity",
109 "examples/foo/.identity.lock",
110 "src/.identity.tmp.1.2",
111 "../.heddle/identity.toml",
112 ] {
113 assert!(
114 !is_reserved_worktree_path(Path::new(path)),
115 "expected not reserved: {path}"
116 );
117 }
118 }
119
120 #[test]
121 fn directory_child_reserves_root_heddle_and_descendants() {
122 assert!(is_reserved_directory_child(Path::new(""), ".heddle"));
123 assert!(is_reserved_directory_child(Path::new("."), ".heddle"));
124 assert!(is_reserved_directory_child(
125 Path::new(".heddle"),
126 "identity.toml"
127 ));
128 assert!(is_reserved_directory_child(
129 Path::new(""),
130 ".heddle.identity"
131 ));
132 assert!(is_reserved_directory_child(
133 Path::new("."),
134 ".identity.lock"
135 ));
136 assert!(is_reserved_directory_child(
137 Path::new(""),
138 ".identity.tmp.9.1"
139 ));
140 assert!(!is_reserved_directory_child(Path::new(""), "src"));
141 assert!(!is_reserved_directory_child(
142 Path::new("examples/calculator"),
143 ".heddle"
144 ));
145 assert!(!is_reserved_directory_child(
146 Path::new("examples/foo"),
147 ".heddle.identity"
148 ));
149 }
150}