bacon/ignorer/
mod.rs

1use {
2    anyhow::Result,
3    glob::Pattern,
4    std::path::{
5        Path,
6        PathBuf,
7    },
8};
9
10mod git_ignorer;
11mod glob_ignorer;
12
13pub use {
14    git_ignorer::GitIgnorer,
15    glob_ignorer::GlobIgnorer,
16};
17
18/// Build glob patterns from a pattern string, handling both absolute
19/// (starting with `/`) and relative patterns.
20pub(crate) fn build_glob_patterns(
21    pattern: &str,
22    root: &Path,
23) -> Result<Vec<Pattern>> {
24    let mut patterns = Vec::new();
25    if pattern.starts_with('/') {
26        patterns.push(Pattern::new(pattern)?);
27        let abs_pattern = root.join(pattern);
28        patterns.push(Pattern::new(&abs_pattern.to_string_lossy())?);
29    } else {
30        patterns.push(Pattern::new(&format!("/**/{pattern}"))?);
31    }
32    Ok(patterns)
33}
34
35pub trait Ignorer {
36    /// Tell whether all given paths are excluded according to
37    /// either the global gitignore rules or the ones of the repository.
38    ///
39    /// Return Ok(false) when at least one file is included (i.e. we should
40    /// execute the job)
41    fn excludes(
42        &mut self,
43        paths: &Path,
44    ) -> Result<bool>;
45}
46
47/// A set of ignorers
48#[derive(Default)]
49pub struct IgnorerSet {
50    ignorers: Vec<Box<dyn Ignorer + Send>>,
51    /// Patterns that override ignore rules (from `!pattern` in the ignore config)
52    override_globs: Vec<Pattern>,
53}
54impl IgnorerSet {
55    pub fn add(
56        &mut self,
57        ignorer: Box<dyn Ignorer + Send>,
58    ) {
59        self.ignorers.push(ignorer);
60    }
61    /// Add an override pattern that will force-include matching paths,
62    /// overriding any ignore rules (including .gitignore).
63    /// This is used for negative patterns like `!myfile.txt` in the ignore config.
64    pub fn add_override(
65        &mut self,
66        pattern: &str,
67        root: &Path,
68    ) -> Result<()> {
69        self.override_globs
70            .extend(build_glob_patterns(pattern, root)?);
71        Ok(())
72    }
73    /// Check if a path matches any override pattern
74    fn is_overridden(
75        &self,
76        path: &Path,
77    ) -> bool {
78        for glob in &self.override_globs {
79            if glob.matches_path(path) {
80                return true;
81            }
82        }
83        false
84    }
85    pub fn excludes_all_pathbufs(
86        &mut self,
87        paths: &[PathBuf],
88    ) -> Result<bool> {
89        if self.ignorers.is_empty() {
90            return Ok(false);
91        }
92        for path in paths {
93            // First check if this path matches an override pattern.
94            // Override patterns (from `!pattern` in ignore config) force-include
95            // the path, regardless of any ignore rules.
96            if self.is_overridden(path) {
97                return Ok(false);
98            }
99            let mut excluded = false;
100            for ignorer in &mut self.ignorers {
101                if ignorer.excludes(path)? {
102                    excluded = true;
103                    break;
104                }
105            }
106            if !excluded {
107                return Ok(false);
108            }
109        }
110        Ok(true)
111    }
112}