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