Show More
@@ -1,346 +1,494 b'' | |||||
1 | // matchers.rs |
|
1 | // matchers.rs | |
2 | // |
|
2 | // | |
3 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> |
|
3 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | |
4 | // |
|
4 | // | |
5 | // This software may be used and distributed according to the terms of the |
|
5 | // This software may be used and distributed according to the terms of the | |
6 | // GNU General Public License version 2 or any later version. |
|
6 | // GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | //! Structs and types for matching files and directories. |
|
8 | //! Structs and types for matching files and directories. | |
9 |
|
9 | |||
10 | #[cfg(feature = "with-re2")] |
|
10 | #[cfg(feature = "with-re2")] | |
11 | use crate::re2::Re2; |
|
11 | use crate::re2::Re2; | |
12 | use crate::{ |
|
12 | use crate::{ | |
13 |
filepatterns::PatternResult, |
|
13 | filepatterns::PatternResult, | |
14 | DirstateMapError, PatternError, |
|
14 | utils::hg_path::{HgPath, HgPathBuf}, | |
|
15 | DirsMultiset, DirstateMapError, IgnorePattern, PatternError, | |||
|
16 | PatternSyntax, | |||
15 | }; |
|
17 | }; | |
16 | use std::collections::HashSet; |
|
18 | use std::collections::HashSet; | |
17 | use std::iter::FromIterator; |
|
19 | use std::iter::FromIterator; | |
18 | use std::ops::Deref; |
|
20 | use std::ops::Deref; | |
19 |
|
21 | |||
20 | #[derive(Debug, PartialEq)] |
|
22 | #[derive(Debug, PartialEq)] | |
21 | pub enum VisitChildrenSet<'a> { |
|
23 | pub enum VisitChildrenSet<'a> { | |
22 | /// Don't visit anything |
|
24 | /// Don't visit anything | |
23 | Empty, |
|
25 | Empty, | |
24 | /// Only visit this directory |
|
26 | /// Only visit this directory | |
25 | This, |
|
27 | This, | |
26 | /// Visit this directory and these subdirectories |
|
28 | /// Visit this directory and these subdirectories | |
27 | /// TODO Should we implement a `NonEmptyHashSet`? |
|
29 | /// TODO Should we implement a `NonEmptyHashSet`? | |
28 | Set(HashSet<&'a HgPath>), |
|
30 | Set(HashSet<&'a HgPath>), | |
29 | /// Visit this directory and all subdirectories |
|
31 | /// Visit this directory and all subdirectories | |
30 | Recursive, |
|
32 | Recursive, | |
31 | } |
|
33 | } | |
32 |
|
34 | |||
33 | pub trait Matcher { |
|
35 | pub trait Matcher { | |
34 | /// Explicitly listed files |
|
36 | /// Explicitly listed files | |
35 | fn file_set(&self) -> Option<&HashSet<&HgPath>>; |
|
37 | fn file_set(&self) -> Option<&HashSet<&HgPath>>; | |
36 | /// Returns whether `filename` is in `file_set` |
|
38 | /// Returns whether `filename` is in `file_set` | |
37 | fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool; |
|
39 | fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool; | |
38 | /// Returns whether `filename` is matched by this matcher |
|
40 | /// Returns whether `filename` is matched by this matcher | |
39 | fn matches(&self, filename: impl AsRef<HgPath>) -> bool; |
|
41 | fn matches(&self, filename: impl AsRef<HgPath>) -> bool; | |
40 | /// Decides whether a directory should be visited based on whether it |
|
42 | /// Decides whether a directory should be visited based on whether it | |
41 | /// has potential matches in it or one of its subdirectories, and |
|
43 | /// has potential matches in it or one of its subdirectories, and | |
42 | /// potentially lists which subdirectories of that directory should be |
|
44 | /// potentially lists which subdirectories of that directory should be | |
43 | /// visited. This is based on the match's primary, included, and excluded |
|
45 | /// visited. This is based on the match's primary, included, and excluded | |
44 | /// patterns. |
|
46 | /// patterns. | |
45 | /// |
|
47 | /// | |
46 | /// # Example |
|
48 | /// # Example | |
47 | /// |
|
49 | /// | |
48 | /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would |
|
50 | /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would | |
49 | /// return the following values (assuming the implementation of |
|
51 | /// return the following values (assuming the implementation of | |
50 | /// visit_children_set is capable of recognizing this; some implementations |
|
52 | /// visit_children_set is capable of recognizing this; some implementations | |
51 | /// are not). |
|
53 | /// are not). | |
52 | /// |
|
54 | /// | |
53 | /// ```text |
|
55 | /// ```text | |
54 | /// ```ignore |
|
56 | /// ```ignore | |
55 | /// '' -> {'foo', 'qux'} |
|
57 | /// '' -> {'foo', 'qux'} | |
56 | /// 'baz' -> set() |
|
58 | /// 'baz' -> set() | |
57 | /// 'foo' -> {'bar'} |
|
59 | /// 'foo' -> {'bar'} | |
58 | /// // Ideally this would be `Recursive`, but since the prefix nature of |
|
60 | /// // Ideally this would be `Recursive`, but since the prefix nature of | |
59 | /// // matchers is applied to the entire matcher, we have to downgrade this |
|
61 | /// // matchers is applied to the entire matcher, we have to downgrade this | |
60 | /// // to `This` due to the (yet to be implemented in Rust) non-prefix |
|
62 | /// // to `This` due to the (yet to be implemented in Rust) non-prefix | |
61 | /// // `RootFilesIn'-kind matcher being mixed in. |
|
63 | /// // `RootFilesIn'-kind matcher being mixed in. | |
62 | /// 'foo/bar' -> 'this' |
|
64 | /// 'foo/bar' -> 'this' | |
63 | /// 'qux' -> 'this' |
|
65 | /// 'qux' -> 'this' | |
64 | /// ``` |
|
66 | /// ``` | |
65 | /// # Important |
|
67 | /// # Important | |
66 | /// |
|
68 | /// | |
67 | /// Most matchers do not know if they're representing files or |
|
69 | /// Most matchers do not know if they're representing files or | |
68 | /// directories. They see `['path:dir/f']` and don't know whether `f` is a |
|
70 | /// directories. They see `['path:dir/f']` and don't know whether `f` is a | |
69 | /// file or a directory, so `visit_children_set('dir')` for most matchers |
|
71 | /// file or a directory, so `visit_children_set('dir')` for most matchers | |
70 | /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's |
|
72 | /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's | |
71 | /// a file (like the yet to be implemented in Rust `ExactMatcher` does), |
|
73 | /// a file (like the yet to be implemented in Rust `ExactMatcher` does), | |
72 | /// it may return `VisitChildrenSet::This`. |
|
74 | /// it may return `VisitChildrenSet::This`. | |
73 | /// Do not rely on the return being a `HashSet` indicating that there are |
|
75 | /// Do not rely on the return being a `HashSet` indicating that there are | |
74 | /// no files in this dir to investigate (or equivalently that if there are |
|
76 | /// no files in this dir to investigate (or equivalently that if there are | |
75 | /// files to investigate in 'dir' that it will always return |
|
77 | /// files to investigate in 'dir' that it will always return | |
76 | /// `VisitChildrenSet::This`). |
|
78 | /// `VisitChildrenSet::This`). | |
77 | fn visit_children_set( |
|
79 | fn visit_children_set( | |
78 | &self, |
|
80 | &self, | |
79 | directory: impl AsRef<HgPath>, |
|
81 | directory: impl AsRef<HgPath>, | |
80 | ) -> VisitChildrenSet; |
|
82 | ) -> VisitChildrenSet; | |
81 | /// Matcher will match everything and `files_set()` will be empty: |
|
83 | /// Matcher will match everything and `files_set()` will be empty: | |
82 | /// optimization might be possible. |
|
84 | /// optimization might be possible. | |
83 | fn matches_everything(&self) -> bool; |
|
85 | fn matches_everything(&self) -> bool; | |
84 | /// Matcher will match exactly the files in `files_set()`: optimization |
|
86 | /// Matcher will match exactly the files in `files_set()`: optimization | |
85 | /// might be possible. |
|
87 | /// might be possible. | |
86 | fn is_exact(&self) -> bool; |
|
88 | fn is_exact(&self) -> bool; | |
87 | } |
|
89 | } | |
88 |
|
90 | |||
89 | /// Matches everything. |
|
91 | /// Matches everything. | |
90 | ///``` |
|
92 | ///``` | |
91 | /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath }; |
|
93 | /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath }; | |
92 | /// |
|
94 | /// | |
93 | /// let matcher = AlwaysMatcher; |
|
95 | /// let matcher = AlwaysMatcher; | |
94 | /// |
|
96 | /// | |
95 | /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true); |
|
97 | /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true); | |
96 | /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true); |
|
98 | /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true); | |
97 | /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true); |
|
99 | /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true); | |
98 | /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true); |
|
100 | /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true); | |
99 | /// ``` |
|
101 | /// ``` | |
100 | #[derive(Debug)] |
|
102 | #[derive(Debug)] | |
101 | pub struct AlwaysMatcher; |
|
103 | pub struct AlwaysMatcher; | |
102 |
|
104 | |||
103 | impl Matcher for AlwaysMatcher { |
|
105 | impl Matcher for AlwaysMatcher { | |
104 | fn file_set(&self) -> Option<&HashSet<&HgPath>> { |
|
106 | fn file_set(&self) -> Option<&HashSet<&HgPath>> { | |
105 | None |
|
107 | None | |
106 | } |
|
108 | } | |
107 | fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool { |
|
109 | fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool { | |
108 | false |
|
110 | false | |
109 | } |
|
111 | } | |
110 | fn matches(&self, _filename: impl AsRef<HgPath>) -> bool { |
|
112 | fn matches(&self, _filename: impl AsRef<HgPath>) -> bool { | |
111 | true |
|
113 | true | |
112 | } |
|
114 | } | |
113 | fn visit_children_set( |
|
115 | fn visit_children_set( | |
114 | &self, |
|
116 | &self, | |
115 | _directory: impl AsRef<HgPath>, |
|
117 | _directory: impl AsRef<HgPath>, | |
116 | ) -> VisitChildrenSet { |
|
118 | ) -> VisitChildrenSet { | |
117 | VisitChildrenSet::Recursive |
|
119 | VisitChildrenSet::Recursive | |
118 | } |
|
120 | } | |
119 | fn matches_everything(&self) -> bool { |
|
121 | fn matches_everything(&self) -> bool { | |
120 | true |
|
122 | true | |
121 | } |
|
123 | } | |
122 | fn is_exact(&self) -> bool { |
|
124 | fn is_exact(&self) -> bool { | |
123 | false |
|
125 | false | |
124 | } |
|
126 | } | |
125 | } |
|
127 | } | |
126 |
|
128 | |||
127 | /// Matches the input files exactly. They are interpreted as paths, not |
|
129 | /// Matches the input files exactly. They are interpreted as paths, not | |
128 | /// patterns. |
|
130 | /// patterns. | |
129 | /// |
|
131 | /// | |
130 | ///``` |
|
132 | ///``` | |
131 | /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath }; |
|
133 | /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath }; | |
132 | /// |
|
134 | /// | |
133 | /// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")]; |
|
135 | /// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")]; | |
134 | /// let matcher = FileMatcher::new(&files).unwrap(); |
|
136 | /// let matcher = FileMatcher::new(&files).unwrap(); | |
135 | /// |
|
137 | /// | |
136 | /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true); |
|
138 | /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true); | |
137 | /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false); |
|
139 | /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false); | |
138 | /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false); |
|
140 | /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false); | |
139 | /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true); |
|
141 | /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true); | |
140 | /// ``` |
|
142 | /// ``` | |
141 | #[derive(Debug)] |
|
143 | #[derive(Debug)] | |
142 | pub struct FileMatcher<'a> { |
|
144 | pub struct FileMatcher<'a> { | |
143 | files: HashSet<&'a HgPath>, |
|
145 | files: HashSet<&'a HgPath>, | |
144 | dirs: DirsMultiset, |
|
146 | dirs: DirsMultiset, | |
145 | } |
|
147 | } | |
146 |
|
148 | |||
147 | impl<'a> FileMatcher<'a> { |
|
149 | impl<'a> FileMatcher<'a> { | |
148 | pub fn new( |
|
150 | pub fn new( | |
149 | files: &'a [impl AsRef<HgPath>], |
|
151 | files: &'a [impl AsRef<HgPath>], | |
150 | ) -> Result<Self, DirstateMapError> { |
|
152 | ) -> Result<Self, DirstateMapError> { | |
151 | Ok(Self { |
|
153 | Ok(Self { | |
152 | files: HashSet::from_iter(files.iter().map(|f| f.as_ref())), |
|
154 | files: HashSet::from_iter(files.iter().map(|f| f.as_ref())), | |
153 | dirs: DirsMultiset::from_manifest(files)?, |
|
155 | dirs: DirsMultiset::from_manifest(files)?, | |
154 | }) |
|
156 | }) | |
155 | } |
|
157 | } | |
156 | fn inner_matches(&self, filename: impl AsRef<HgPath>) -> bool { |
|
158 | fn inner_matches(&self, filename: impl AsRef<HgPath>) -> bool { | |
157 | self.files.contains(filename.as_ref()) |
|
159 | self.files.contains(filename.as_ref()) | |
158 | } |
|
160 | } | |
159 | } |
|
161 | } | |
160 |
|
162 | |||
161 | impl<'a> Matcher for FileMatcher<'a> { |
|
163 | impl<'a> Matcher for FileMatcher<'a> { | |
162 | fn file_set(&self) -> Option<&HashSet<&HgPath>> { |
|
164 | fn file_set(&self) -> Option<&HashSet<&HgPath>> { | |
163 | Some(&self.files) |
|
165 | Some(&self.files) | |
164 | } |
|
166 | } | |
165 | fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool { |
|
167 | fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool { | |
166 | self.inner_matches(filename) |
|
168 | self.inner_matches(filename) | |
167 | } |
|
169 | } | |
168 | fn matches(&self, filename: impl AsRef<HgPath>) -> bool { |
|
170 | fn matches(&self, filename: impl AsRef<HgPath>) -> bool { | |
169 | self.inner_matches(filename) |
|
171 | self.inner_matches(filename) | |
170 | } |
|
172 | } | |
171 | fn visit_children_set( |
|
173 | fn visit_children_set( | |
172 | &self, |
|
174 | &self, | |
173 | directory: impl AsRef<HgPath>, |
|
175 | directory: impl AsRef<HgPath>, | |
174 | ) -> VisitChildrenSet { |
|
176 | ) -> VisitChildrenSet { | |
175 | if self.files.is_empty() || !self.dirs.contains(&directory) { |
|
177 | if self.files.is_empty() || !self.dirs.contains(&directory) { | |
176 | return VisitChildrenSet::Empty; |
|
178 | return VisitChildrenSet::Empty; | |
177 | } |
|
179 | } | |
178 | let dirs_as_set = self.dirs.iter().map(|k| k.deref()).collect(); |
|
180 | let dirs_as_set = self.dirs.iter().map(|k| k.deref()).collect(); | |
179 |
|
181 | |||
180 | let mut candidates: HashSet<&HgPath> = |
|
182 | let mut candidates: HashSet<&HgPath> = | |
181 | self.files.union(&dirs_as_set).map(|k| *k).collect(); |
|
183 | self.files.union(&dirs_as_set).map(|k| *k).collect(); | |
182 | candidates.remove(HgPath::new(b"")); |
|
184 | candidates.remove(HgPath::new(b"")); | |
183 |
|
185 | |||
184 | if !directory.as_ref().is_empty() { |
|
186 | if !directory.as_ref().is_empty() { | |
185 | let directory = [directory.as_ref().as_bytes(), b"/"].concat(); |
|
187 | let directory = [directory.as_ref().as_bytes(), b"/"].concat(); | |
186 | candidates = candidates |
|
188 | candidates = candidates | |
187 | .iter() |
|
189 | .iter() | |
188 | .filter_map(|c| { |
|
190 | .filter_map(|c| { | |
189 | if c.as_bytes().starts_with(&directory) { |
|
191 | if c.as_bytes().starts_with(&directory) { | |
190 | Some(HgPath::new(&c.as_bytes()[directory.len()..])) |
|
192 | Some(HgPath::new(&c.as_bytes()[directory.len()..])) | |
191 | } else { |
|
193 | } else { | |
192 | None |
|
194 | None | |
193 | } |
|
195 | } | |
194 | }) |
|
196 | }) | |
195 | .collect(); |
|
197 | .collect(); | |
196 | } |
|
198 | } | |
197 |
|
199 | |||
198 | // `self.dirs` includes all of the directories, recursively, so if |
|
200 | // `self.dirs` includes all of the directories, recursively, so if | |
199 | // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo', |
|
201 | // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo', | |
200 | // 'foo/bar' in it. Thus we can safely ignore a candidate that has a |
|
202 | // 'foo/bar' in it. Thus we can safely ignore a candidate that has a | |
201 | // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate |
|
203 | // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate | |
202 | // subdir will be in there without a slash. |
|
204 | // subdir will be in there without a slash. | |
203 | VisitChildrenSet::Set( |
|
205 | VisitChildrenSet::Set( | |
204 | candidates |
|
206 | candidates | |
205 | .iter() |
|
207 | .iter() | |
206 | .filter_map(|c| { |
|
208 | .filter_map(|c| { | |
207 | if c.bytes().all(|b| *b != b'/') { |
|
209 | if c.bytes().all(|b| *b != b'/') { | |
208 | Some(*c) |
|
210 | Some(*c) | |
209 | } else { |
|
211 | } else { | |
210 | None |
|
212 | None | |
211 | } |
|
213 | } | |
212 | }) |
|
214 | }) | |
213 | .collect(), |
|
215 | .collect(), | |
214 | ) |
|
216 | ) | |
215 | } |
|
217 | } | |
216 | fn matches_everything(&self) -> bool { |
|
218 | fn matches_everything(&self) -> bool { | |
217 | false |
|
219 | false | |
218 | } |
|
220 | } | |
219 | fn is_exact(&self) -> bool { |
|
221 | fn is_exact(&self) -> bool { | |
220 | true |
|
222 | true | |
221 | } |
|
223 | } | |
222 | } |
|
224 | } | |
223 |
|
225 | |||
224 | #[cfg(feature = "with-re2")] |
|
226 | #[cfg(feature = "with-re2")] | |
225 | /// Returns a function that matches an `HgPath` against the given regex |
|
227 | /// Returns a function that matches an `HgPath` against the given regex | |
226 | /// pattern. |
|
228 | /// pattern. | |
227 | /// |
|
229 | /// | |
228 | /// This can fail when the pattern is invalid or not supported by the |
|
230 | /// This can fail when the pattern is invalid or not supported by the | |
229 | /// underlying engine `Re2`, for instance anything with back-references. |
|
231 | /// underlying engine `Re2`, for instance anything with back-references. | |
230 | fn re_matcher( |
|
232 | fn re_matcher( | |
231 | pattern: &[u8], |
|
233 | pattern: &[u8], | |
232 | ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> { |
|
234 | ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> { | |
233 | let regex = Re2::new(pattern); |
|
235 | let regex = Re2::new(pattern); | |
234 | let regex = regex.map_err(|e| PatternError::UnsupportedSyntax(e))?; |
|
236 | let regex = regex.map_err(|e| PatternError::UnsupportedSyntax(e))?; | |
235 | Ok(move |path: &HgPath| regex.is_match(path.as_bytes())) |
|
237 | Ok(move |path: &HgPath| regex.is_match(path.as_bytes())) | |
236 | } |
|
238 | } | |
237 |
|
239 | |||
238 | #[cfg(not(feature = "with-re2"))] |
|
240 | #[cfg(not(feature = "with-re2"))] | |
239 | fn re_matcher(_: &[u8]) -> PatternResult<Box<dyn Fn(&HgPath) -> bool + Sync>> { |
|
241 | fn re_matcher(_: &[u8]) -> PatternResult<Box<dyn Fn(&HgPath) -> bool + Sync>> { | |
240 | Err(PatternError::Re2NotInstalled) |
|
242 | Err(PatternError::Re2NotInstalled) | |
241 | } |
|
243 | } | |
242 |
|
244 | |||
|
245 | /// Returns roots and directories corresponding to each pattern. | |||
|
246 | /// | |||
|
247 | /// This calculates the roots and directories exactly matching the patterns and | |||
|
248 | /// returns a tuple of (roots, dirs). It does not return other directories | |||
|
249 | /// which may also need to be considered, like the parent directories. | |||
|
250 | fn roots_and_dirs( | |||
|
251 | ignore_patterns: &[IgnorePattern], | |||
|
252 | ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) { | |||
|
253 | let mut roots = Vec::new(); | |||
|
254 | let mut dirs = Vec::new(); | |||
|
255 | ||||
|
256 | for ignore_pattern in ignore_patterns { | |||
|
257 | let IgnorePattern { | |||
|
258 | syntax, pattern, .. | |||
|
259 | } = ignore_pattern; | |||
|
260 | match syntax { | |||
|
261 | PatternSyntax::RootGlob | PatternSyntax::Glob => { | |||
|
262 | let mut root = vec![]; | |||
|
263 | ||||
|
264 | for p in pattern.split(|c| *c == b'/') { | |||
|
265 | if p.iter().any(|c| match *c { | |||
|
266 | b'[' | b'{' | b'*' | b'?' => true, | |||
|
267 | _ => false, | |||
|
268 | }) { | |||
|
269 | break; | |||
|
270 | } | |||
|
271 | root.push(HgPathBuf::from_bytes(p)); | |||
|
272 | } | |||
|
273 | let buf = | |||
|
274 | root.iter().fold(HgPathBuf::new(), |acc, r| acc.join(r)); | |||
|
275 | roots.push(buf); | |||
|
276 | } | |||
|
277 | PatternSyntax::Path | PatternSyntax::RelPath => { | |||
|
278 | let pat = HgPath::new(if pattern == b"." { | |||
|
279 | &[] as &[u8] | |||
|
280 | } else { | |||
|
281 | pattern | |||
|
282 | }); | |||
|
283 | roots.push(pat.to_owned()); | |||
|
284 | } | |||
|
285 | PatternSyntax::RootFiles => { | |||
|
286 | let pat = if pattern == b"." { | |||
|
287 | &[] as &[u8] | |||
|
288 | } else { | |||
|
289 | pattern | |||
|
290 | }; | |||
|
291 | dirs.push(HgPathBuf::from_bytes(pat)); | |||
|
292 | } | |||
|
293 | _ => { | |||
|
294 | roots.push(HgPathBuf::new()); | |||
|
295 | } | |||
|
296 | } | |||
|
297 | } | |||
|
298 | (roots, dirs) | |||
|
299 | } | |||
|
300 | ||||
|
301 | /// Paths extracted from patterns | |||
|
302 | #[derive(Debug, PartialEq)] | |||
|
303 | struct RootsDirsAndParents { | |||
|
304 | /// Directories to match recursively | |||
|
305 | pub roots: HashSet<HgPathBuf>, | |||
|
306 | /// Directories to match non-recursively | |||
|
307 | pub dirs: HashSet<HgPathBuf>, | |||
|
308 | /// Implicitly required directories to go to items in either roots or dirs | |||
|
309 | pub parents: HashSet<HgPathBuf>, | |||
|
310 | } | |||
|
311 | ||||
|
312 | /// Extract roots, dirs and parents from patterns. | |||
|
313 | fn roots_dirs_and_parents( | |||
|
314 | ignore_patterns: &[IgnorePattern], | |||
|
315 | ) -> PatternResult<RootsDirsAndParents> { | |||
|
316 | let (roots, dirs) = roots_and_dirs(ignore_patterns); | |||
|
317 | ||||
|
318 | let mut parents = HashSet::new(); | |||
|
319 | ||||
|
320 | parents.extend( | |||
|
321 | DirsMultiset::from_manifest(&dirs) | |||
|
322 | .map_err(|e| match e { | |||
|
323 | DirstateMapError::InvalidPath(e) => e, | |||
|
324 | _ => unreachable!(), | |||
|
325 | })? | |||
|
326 | .iter() | |||
|
327 | .map(|k| k.to_owned()), | |||
|
328 | ); | |||
|
329 | parents.extend( | |||
|
330 | DirsMultiset::from_manifest(&roots) | |||
|
331 | .map_err(|e| match e { | |||
|
332 | DirstateMapError::InvalidPath(e) => e, | |||
|
333 | _ => unreachable!(), | |||
|
334 | })? | |||
|
335 | .iter() | |||
|
336 | .map(|k| k.to_owned()), | |||
|
337 | ); | |||
|
338 | ||||
|
339 | Ok(RootsDirsAndParents { | |||
|
340 | roots: HashSet::from_iter(roots), | |||
|
341 | dirs: HashSet::from_iter(dirs), | |||
|
342 | parents, | |||
|
343 | }) | |||
|
344 | } | |||
|
345 | ||||
243 | #[cfg(test)] |
|
346 | #[cfg(test)] | |
244 | mod tests { |
|
347 | mod tests { | |
245 | use super::*; |
|
348 | use super::*; | |
246 | use pretty_assertions::assert_eq; |
|
349 | use pretty_assertions::assert_eq; | |
|
350 | use std::path::Path; | |||
|
351 | ||||
|
352 | #[test] | |||
|
353 | fn test_roots_and_dirs() { | |||
|
354 | let pats = vec![ | |||
|
355 | IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")), | |||
|
356 | IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")), | |||
|
357 | IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")), | |||
|
358 | ]; | |||
|
359 | let (roots, dirs) = roots_and_dirs(&pats); | |||
|
360 | ||||
|
361 | assert_eq!( | |||
|
362 | roots, | |||
|
363 | vec!( | |||
|
364 | HgPathBuf::from_bytes(b"g/h"), | |||
|
365 | HgPathBuf::from_bytes(b"g/h"), | |||
|
366 | HgPathBuf::new() | |||
|
367 | ), | |||
|
368 | ); | |||
|
369 | assert_eq!(dirs, vec!()); | |||
|
370 | } | |||
|
371 | ||||
|
372 | #[test] | |||
|
373 | fn test_roots_dirs_and_parents() { | |||
|
374 | let pats = vec![ | |||
|
375 | IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")), | |||
|
376 | IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")), | |||
|
377 | IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")), | |||
|
378 | ]; | |||
|
379 | ||||
|
380 | let mut roots = HashSet::new(); | |||
|
381 | roots.insert(HgPathBuf::from_bytes(b"g/h")); | |||
|
382 | roots.insert(HgPathBuf::new()); | |||
|
383 | ||||
|
384 | let dirs = HashSet::new(); | |||
|
385 | ||||
|
386 | let mut parents = HashSet::new(); | |||
|
387 | parents.insert(HgPathBuf::new()); | |||
|
388 | parents.insert(HgPathBuf::from_bytes(b"g")); | |||
|
389 | ||||
|
390 | assert_eq!( | |||
|
391 | roots_dirs_and_parents(&pats).unwrap(), | |||
|
392 | RootsDirsAndParents {roots, dirs, parents} | |||
|
393 | ); | |||
|
394 | } | |||
247 |
|
395 | |||
248 | #[test] |
|
396 | #[test] | |
249 | fn test_filematcher_visit_children_set() { |
|
397 | fn test_filematcher_visit_children_set() { | |
250 | // Visitchildrenset |
|
398 | // Visitchildrenset | |
251 | let files = vec![HgPath::new(b"dir/subdir/foo.txt")]; |
|
399 | let files = vec![HgPath::new(b"dir/subdir/foo.txt")]; | |
252 | let matcher = FileMatcher::new(&files).unwrap(); |
|
400 | let matcher = FileMatcher::new(&files).unwrap(); | |
253 |
|
401 | |||
254 | let mut set = HashSet::new(); |
|
402 | let mut set = HashSet::new(); | |
255 | set.insert(HgPath::new(b"dir")); |
|
403 | set.insert(HgPath::new(b"dir")); | |
256 | assert_eq!( |
|
404 | assert_eq!( | |
257 | matcher.visit_children_set(HgPath::new(b"")), |
|
405 | matcher.visit_children_set(HgPath::new(b"")), | |
258 | VisitChildrenSet::Set(set) |
|
406 | VisitChildrenSet::Set(set) | |
259 | ); |
|
407 | ); | |
260 |
|
408 | |||
261 | let mut set = HashSet::new(); |
|
409 | let mut set = HashSet::new(); | |
262 | set.insert(HgPath::new(b"subdir")); |
|
410 | set.insert(HgPath::new(b"subdir")); | |
263 | assert_eq!( |
|
411 | assert_eq!( | |
264 | matcher.visit_children_set(HgPath::new(b"dir")), |
|
412 | matcher.visit_children_set(HgPath::new(b"dir")), | |
265 | VisitChildrenSet::Set(set) |
|
413 | VisitChildrenSet::Set(set) | |
266 | ); |
|
414 | ); | |
267 |
|
415 | |||
268 | let mut set = HashSet::new(); |
|
416 | let mut set = HashSet::new(); | |
269 | set.insert(HgPath::new(b"foo.txt")); |
|
417 | set.insert(HgPath::new(b"foo.txt")); | |
270 | assert_eq!( |
|
418 | assert_eq!( | |
271 | matcher.visit_children_set(HgPath::new(b"dir/subdir")), |
|
419 | matcher.visit_children_set(HgPath::new(b"dir/subdir")), | |
272 | VisitChildrenSet::Set(set) |
|
420 | VisitChildrenSet::Set(set) | |
273 | ); |
|
421 | ); | |
274 |
|
422 | |||
275 | assert_eq!( |
|
423 | assert_eq!( | |
276 | matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), |
|
424 | matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), | |
277 | VisitChildrenSet::Empty |
|
425 | VisitChildrenSet::Empty | |
278 | ); |
|
426 | ); | |
279 | assert_eq!( |
|
427 | assert_eq!( | |
280 | matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")), |
|
428 | matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")), | |
281 | VisitChildrenSet::Empty |
|
429 | VisitChildrenSet::Empty | |
282 | ); |
|
430 | ); | |
283 | assert_eq!( |
|
431 | assert_eq!( | |
284 | matcher.visit_children_set(HgPath::new(b"folder")), |
|
432 | matcher.visit_children_set(HgPath::new(b"folder")), | |
285 | VisitChildrenSet::Empty |
|
433 | VisitChildrenSet::Empty | |
286 | ); |
|
434 | ); | |
287 | } |
|
435 | } | |
288 |
|
436 | |||
289 | #[test] |
|
437 | #[test] | |
290 | fn test_filematcher_visit_children_set_files_and_dirs() { |
|
438 | fn test_filematcher_visit_children_set_files_and_dirs() { | |
291 | let files = vec![ |
|
439 | let files = vec![ | |
292 | HgPath::new(b"rootfile.txt"), |
|
440 | HgPath::new(b"rootfile.txt"), | |
293 | HgPath::new(b"a/file1.txt"), |
|
441 | HgPath::new(b"a/file1.txt"), | |
294 | HgPath::new(b"a/b/file2.txt"), |
|
442 | HgPath::new(b"a/b/file2.txt"), | |
295 | // No file in a/b/c |
|
443 | // No file in a/b/c | |
296 | HgPath::new(b"a/b/c/d/file4.txt"), |
|
444 | HgPath::new(b"a/b/c/d/file4.txt"), | |
297 | ]; |
|
445 | ]; | |
298 | let matcher = FileMatcher::new(&files).unwrap(); |
|
446 | let matcher = FileMatcher::new(&files).unwrap(); | |
299 |
|
447 | |||
300 | let mut set = HashSet::new(); |
|
448 | let mut set = HashSet::new(); | |
301 | set.insert(HgPath::new(b"a")); |
|
449 | set.insert(HgPath::new(b"a")); | |
302 | set.insert(HgPath::new(b"rootfile.txt")); |
|
450 | set.insert(HgPath::new(b"rootfile.txt")); | |
303 | assert_eq!( |
|
451 | assert_eq!( | |
304 | matcher.visit_children_set(HgPath::new(b"")), |
|
452 | matcher.visit_children_set(HgPath::new(b"")), | |
305 | VisitChildrenSet::Set(set) |
|
453 | VisitChildrenSet::Set(set) | |
306 | ); |
|
454 | ); | |
307 |
|
455 | |||
308 | let mut set = HashSet::new(); |
|
456 | let mut set = HashSet::new(); | |
309 | set.insert(HgPath::new(b"b")); |
|
457 | set.insert(HgPath::new(b"b")); | |
310 | set.insert(HgPath::new(b"file1.txt")); |
|
458 | set.insert(HgPath::new(b"file1.txt")); | |
311 | assert_eq!( |
|
459 | assert_eq!( | |
312 | matcher.visit_children_set(HgPath::new(b"a")), |
|
460 | matcher.visit_children_set(HgPath::new(b"a")), | |
313 | VisitChildrenSet::Set(set) |
|
461 | VisitChildrenSet::Set(set) | |
314 | ); |
|
462 | ); | |
315 |
|
463 | |||
316 | let mut set = HashSet::new(); |
|
464 | let mut set = HashSet::new(); | |
317 | set.insert(HgPath::new(b"c")); |
|
465 | set.insert(HgPath::new(b"c")); | |
318 | set.insert(HgPath::new(b"file2.txt")); |
|
466 | set.insert(HgPath::new(b"file2.txt")); | |
319 | assert_eq!( |
|
467 | assert_eq!( | |
320 | matcher.visit_children_set(HgPath::new(b"a/b")), |
|
468 | matcher.visit_children_set(HgPath::new(b"a/b")), | |
321 | VisitChildrenSet::Set(set) |
|
469 | VisitChildrenSet::Set(set) | |
322 | ); |
|
470 | ); | |
323 |
|
471 | |||
324 | let mut set = HashSet::new(); |
|
472 | let mut set = HashSet::new(); | |
325 | set.insert(HgPath::new(b"d")); |
|
473 | set.insert(HgPath::new(b"d")); | |
326 | assert_eq!( |
|
474 | assert_eq!( | |
327 | matcher.visit_children_set(HgPath::new(b"a/b/c")), |
|
475 | matcher.visit_children_set(HgPath::new(b"a/b/c")), | |
328 | VisitChildrenSet::Set(set) |
|
476 | VisitChildrenSet::Set(set) | |
329 | ); |
|
477 | ); | |
330 | let mut set = HashSet::new(); |
|
478 | let mut set = HashSet::new(); | |
331 | set.insert(HgPath::new(b"file4.txt")); |
|
479 | set.insert(HgPath::new(b"file4.txt")); | |
332 | assert_eq!( |
|
480 | assert_eq!( | |
333 | matcher.visit_children_set(HgPath::new(b"a/b/c/d")), |
|
481 | matcher.visit_children_set(HgPath::new(b"a/b/c/d")), | |
334 | VisitChildrenSet::Set(set) |
|
482 | VisitChildrenSet::Set(set) | |
335 | ); |
|
483 | ); | |
336 |
|
484 | |||
337 | assert_eq!( |
|
485 | assert_eq!( | |
338 | matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")), |
|
486 | matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")), | |
339 | VisitChildrenSet::Empty |
|
487 | VisitChildrenSet::Empty | |
340 | ); |
|
488 | ); | |
341 | assert_eq!( |
|
489 | assert_eq!( | |
342 | matcher.visit_children_set(HgPath::new(b"folder")), |
|
490 | matcher.visit_children_set(HgPath::new(b"folder")), | |
343 | VisitChildrenSet::Empty |
|
491 | VisitChildrenSet::Empty | |
344 | ); |
|
492 | ); | |
345 | } |
|
493 | } | |
346 | } |
|
494 | } |
General Comments 0
You need to be logged in to leave comments.
Login now