Skip to main content

weavatrix_scan/selection/
queries.rs

1use super::{
2    Error, IgnoreSourceEvidence, Path, RepositoryMatch, Result, ScanOptions, ScanWarning,
3    SelectionDecision, SelectionMatcher, SkipKind, WalkEntry, fs, relative_depth,
4};
5
6impl SelectionMatcher {
7    /// Applies the complete selection policy to one existing path.
8    ///
9    /// This convenience method performs `symlink_metadata` and, when links are
10    /// followed, target metadata. Use [`Self::matched_entry`] inside an existing
11    /// Weavatrix walk to avoid those extra reads.
12    ///
13    /// # Errors
14    ///
15    /// Returns an error for escaped paths, missing entries, metadata failures,
16    /// or required ignore-source failures under [`crate::ErrorPolicy::Abort`].
17    pub fn matched(&mut self, path: impl AsRef<Path>) -> Result<SelectionDecision> {
18        let relative = self.repository.normalize(path)?;
19        let absolute = self.repository.root().join(&relative);
20        let depth = relative_depth(&relative);
21        if let Some(decision) = self.match_ancestors(&relative)? {
22            return Ok(decision);
23        }
24        let link_metadata =
25            fs::symlink_metadata(&absolute).map_err(|source| Error::io(&absolute, source))?;
26        let is_symlink = link_metadata.file_type().is_symlink();
27        if is_symlink && !self.options.walk.follow_links {
28            return Ok(SelectionDecision::skipped(
29                SkipKind::Symlink,
30                RepositoryMatch::None,
31            ));
32        }
33        let metadata = if is_symlink {
34            let canonical = absolute
35                .canonicalize()
36                .map_err(|source| Error::io(&absolute, source))?;
37            if !canonical.starts_with(self.repository.root()) {
38                return Ok(SelectionDecision::skipped(
39                    SkipKind::PathEscape,
40                    RepositoryMatch::None,
41                ));
42            }
43            fs::metadata(&absolute).map_err(|source| Error::io(&absolute, source))?
44        } else {
45            link_metadata
46        };
47        if metadata.is_dir()
48            && let Some(decision) = self.file_system_decision(&absolute, &metadata)?
49        {
50            return Ok(decision);
51        }
52        self.classify(
53            &absolute,
54            &relative,
55            depth,
56            metadata.is_file(),
57            metadata.is_dir(),
58            false,
59            metadata.len(),
60            None,
61        )
62    }
63
64    /// Applies the complete selection policy to a walker entry.
65    ///
66    /// # Errors
67    ///
68    /// Returns an error for escaped paths, metadata failures when the walker
69    /// did not collect file metadata, or required ignore-source failures.
70    pub fn matched_entry(&mut self, entry: &WalkEntry) -> Result<SelectionDecision> {
71        let relative = self.repository.normalize(entry.relative_path())?;
72        let absolute = self.repository.root().join(&relative);
73        let bytes = if entry.is_file() {
74            match entry.bytes() {
75                Some(bytes) => bytes,
76                None => fs::metadata(&absolute)
77                    .map_err(|source| Error::io(&absolute, source))?
78                    .len(),
79            }
80        } else {
81            0
82        };
83        self.classify(
84            &absolute,
85            &relative,
86            entry.depth(),
87            entry.is_file(),
88            entry.is_dir(),
89            entry.is_symlink(),
90            bytes,
91            entry.skip_reason(),
92        )
93    }
94
95    /// Atomically reloads ignore inputs while retaining the full selection
96    /// options.
97    ///
98    /// # Errors
99    ///
100    /// Returns an error when replacement matcher construction fails.
101    pub fn refresh(&mut self) -> Result<bool> {
102        self.repository.refresh()
103    }
104
105    /// Returns the canonical matcher root.
106    #[must_use]
107    pub fn root(&self) -> &Path {
108        self.repository.root()
109    }
110
111    /// Returns the immutable selection options snapshot.
112    #[must_use]
113    pub const fn options(&self) -> &ScanOptions {
114        &self.options
115    }
116
117    /// Returns evidence for every loaded ignore source.
118    #[must_use]
119    pub fn sources(&self) -> &[IgnoreSourceEvidence] {
120        self.repository.sources()
121    }
122
123    /// Returns non-fatal matcher diagnostics.
124    #[must_use]
125    pub fn warnings(&self) -> &[ScanWarning] {
126        self.repository.warnings()
127    }
128
129    /// Returns whether all matcher inputs are portable.
130    #[must_use]
131    pub fn portable(&self) -> bool {
132        self.repository.portable()
133    }
134}