objects/worktree/
worktree_ignore.rs1use std::path::Path;
5
6pub fn should_ignore(path: &Path, patterns: &[String]) -> bool {
7 let path_str = path.to_string_lossy();
8
9 for pattern in patterns {
10 if is_root_admin_pattern(pattern) {
11 if is_root_path_match(path, pattern) {
12 return true;
13 }
14 continue;
15 }
16
17 if pattern.ends_with('/') {
18 let dir_pattern = pattern.trim_end_matches('/');
19 if is_dir_match(path, dir_pattern) {
20 return true;
21 }
22 } else if let Some(suffix) = pattern.strip_prefix('*') {
23 if path_str.ends_with(suffix) {
24 return true;
25 }
26 for component in path.components() {
27 if let Some(s) = component.as_os_str().to_str()
28 && s.ends_with(suffix)
29 {
30 return true;
31 }
32 }
33 } else {
34 for component in path.components() {
35 if let Some(s) = component.as_os_str().to_str()
36 && s == pattern
37 {
38 return true;
39 }
40 }
41 }
42 }
43
44 false
45}
46
47fn is_root_admin_pattern(pattern: &str) -> bool {
48 matches!(pattern, ".heddle" | ".heddleignore" | ".git")
49}
50
51fn is_root_path_match(path: &Path, pattern: &str) -> bool {
52 let mut components = path.components();
53 let Some(first) = components.next() else {
54 return false;
55 };
56 let Some(first_str) = first.as_os_str().to_str() else {
57 return false;
58 };
59 first_str == pattern
60}
61
62fn is_dir_match(path: &Path, dir_pattern: &str) -> bool {
63 let path_str = path.to_string_lossy();
64 if path_str.starts_with(&format!("{}/", dir_pattern)) {
65 return true;
66 }
67 for component in path.components() {
68 if let Some(s) = component.as_os_str().to_str()
69 && s == dir_pattern
70 {
71 return true;
72 }
73 }
74 false
75}
76
77#[cfg(test)]
78mod tests {
79 use std::path::PathBuf;
80
81 use super::*;
82
83 #[test]
84 fn test_glob_extension() {
85 let patterns = vec!["*.log".to_string()];
86 assert!(should_ignore(&PathBuf::from("test.log"), &patterns));
87 assert!(should_ignore(&PathBuf::from("debug.log"), &patterns));
88 assert!(!should_ignore(&PathBuf::from("test.txt"), &patterns));
89 }
90
91 #[test]
92 fn test_directory_pattern() {
93 let patterns = vec!["build/".to_string()];
94 assert!(should_ignore(&PathBuf::from("build/output.txt"), &patterns));
95 assert!(should_ignore(&PathBuf::from("build"), &patterns));
96 assert!(!should_ignore(&PathBuf::from("builder.txt"), &patterns));
97 }
98
99 #[test]
100 fn test_simple_pattern() {
101 let patterns = vec!["node_modules".to_string()];
102 assert!(should_ignore(
103 &PathBuf::from("node_modules/package.json"),
104 &patterns
105 ));
106 assert!(!should_ignore(&PathBuf::from("src/main.rs"), &patterns));
107 }
108
109 #[test]
110 fn test_simple_pattern_does_not_match_prefixes() {
111 let patterns = vec!["target".to_string()];
112 assert!(should_ignore(
113 &PathBuf::from("target/output.txt"),
114 &patterns
115 ));
116 assert!(should_ignore(&PathBuf::from("build/target/app"), &patterns));
117 assert!(!should_ignore(&PathBuf::from("target.txt"), &patterns));
118 assert!(!should_ignore(
119 &PathBuf::from("targeted/output.txt"),
120 &patterns
121 ));
122 }
123
124 #[test]
125 fn test_root_admin_patterns_do_not_ignore_nested_paths() {
126 let patterns = vec![".heddle".to_string(), ".heddleignore".to_string()];
127 assert!(should_ignore(&PathBuf::from(".heddle/objects"), &patterns));
128 assert!(should_ignore(
129 &PathBuf::from(".heddle/state/index.bin"),
130 &patterns
131 ));
132 assert!(should_ignore(&PathBuf::from(".heddleignore"), &patterns));
133 assert!(!should_ignore(
134 &PathBuf::from("examples/calculator/.heddle/objects"),
135 &patterns
136 ));
137 assert!(!should_ignore(
138 &PathBuf::from("examples/calculator/.heddle/state/index.bin"),
139 &patterns
140 ));
141 assert!(!should_ignore(
142 &PathBuf::from("examples/calculator/.heddleignore"),
143 &patterns
144 ));
145 }
146}