Skip to main content

objects/worktree/
worktree_ignore.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Ignore pattern helpers for worktree operations.
3
4use 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}