##// END OF EJS Templates
rust-status: refactor options into a `StatusOptions` struct...
Raphaël Gomès -
r45011:483fce65 default
parent child Browse files
Show More
@@ -1,335 +1,321 b''
1 1 // status.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Rust implementation of dirstate.status (dirstate.py).
9 9 //! It is currently missing a lot of functionality compared to the Python one
10 10 //! and will only be triggered in narrow cases.
11 11
12 12 use crate::{
13 13 dirstate::SIZE_FROM_OTHER_PARENT,
14 14 matchers::Matcher,
15 15 utils::{
16 16 files::HgMetadata,
17 17 hg_path::{
18 18 hg_path_to_path_buf, os_string_to_hg_path_buf, HgPath, HgPathBuf,
19 19 },
20 20 },
21 21 CopyMap, DirstateEntry, DirstateMap, EntryState,
22 22 };
23 23 use rayon::prelude::*;
24 24 use std::collections::HashSet;
25 25 use std::fs::{read_dir, DirEntry};
26 26 use std::path::Path;
27 27
28 28 /// Marker enum used to dispatch new status entries into the right collections.
29 29 /// Is similar to `crate::EntryState`, but represents the transient state of
30 30 /// entries during the lifetime of a command.
31 31 enum Dispatch {
32 32 Unsure,
33 33 Modified,
34 34 Added,
35 35 Removed,
36 36 Deleted,
37 37 Clean,
38 38 Unknown,
39 39 }
40 40
41 41 type IoResult<T> = std::io::Result<T>;
42 42
43 43 /// Dates and times that are outside the 31-bit signed range are compared
44 44 /// modulo 2^31. This should prevent hg from behaving badly with very large
45 45 /// files or corrupt dates while still having a high probability of detecting
46 46 /// changes. (issue2608)
47 47 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
48 48 /// is not defined for `i32`, and there is no `As` trait. This forces the
49 49 /// caller to cast `b` as `i32`.
50 50 fn mod_compare(a: i32, b: i32) -> bool {
51 51 a & i32::max_value() != b & i32::max_value()
52 52 }
53 53
54 54 /// Return a sorted list containing information about the entries
55 55 /// in the directory.
56 56 ///
57 57 /// * `skip_dot_hg` - Return an empty vec if `path` contains a `.hg` directory
58 58 fn list_directory(
59 59 path: impl AsRef<Path>,
60 60 skip_dot_hg: bool,
61 61 ) -> std::io::Result<Vec<(HgPathBuf, DirEntry)>> {
62 62 let mut results = vec![];
63 63 let entries = read_dir(path.as_ref())?;
64 64
65 65 for entry in entries {
66 66 let entry = entry?;
67 67 let filename = os_string_to_hg_path_buf(entry.file_name())?;
68 68 let file_type = entry.file_type()?;
69 69 if skip_dot_hg && filename.as_bytes() == b".hg" && file_type.is_dir() {
70 70 return Ok(vec![]);
71 71 } else {
72 72 results.push((HgPathBuf::from(filename), entry))
73 73 }
74 74 }
75 75
76 76 results.sort_unstable_by_key(|e| e.0.clone());
77 77 Ok(results)
78 78 }
79 79
80 80 /// The file corresponding to the dirstate entry was found on the filesystem.
81 81 fn dispatch_found(
82 82 filename: impl AsRef<HgPath>,
83 83 entry: DirstateEntry,
84 84 metadata: HgMetadata,
85 85 copy_map: &CopyMap,
86 check_exec: bool,
87 list_clean: bool,
88 last_normal_time: i64,
86 options: StatusOptions,
89 87 ) -> Dispatch {
90 88 let DirstateEntry {
91 89 state,
92 90 mode,
93 91 mtime,
94 92 size,
95 93 } = entry;
96 94
97 95 let HgMetadata {
98 96 st_mode,
99 97 st_size,
100 98 st_mtime,
101 99 ..
102 100 } = metadata;
103 101
104 102 match state {
105 103 EntryState::Normal => {
106 104 let size_changed = mod_compare(size, st_size as i32);
107 105 let mode_changed =
108 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
106 (mode ^ st_mode as i32) & 0o100 != 0o000 && options.check_exec;
109 107 let metadata_changed = size >= 0 && (size_changed || mode_changed);
110 108 let other_parent = size == SIZE_FROM_OTHER_PARENT;
111 109 if metadata_changed
112 110 || other_parent
113 111 || copy_map.contains_key(filename.as_ref())
114 112 {
115 113 Dispatch::Modified
116 114 } else if mod_compare(mtime, st_mtime as i32) {
117 115 Dispatch::Unsure
118 } else if st_mtime == last_normal_time {
116 } else if st_mtime == options.last_normal_time {
119 117 // the file may have just been marked as normal and
120 118 // it may have changed in the same second without
121 119 // changing its size. This can happen if we quickly
122 120 // do multiple commits. Force lookup, so we don't
123 121 // miss such a racy file change.
124 122 Dispatch::Unsure
125 } else if list_clean {
123 } else if options.list_clean {
126 124 Dispatch::Clean
127 125 } else {
128 126 Dispatch::Unknown
129 127 }
130 128 }
131 129 EntryState::Merged => Dispatch::Modified,
132 130 EntryState::Added => Dispatch::Added,
133 131 EntryState::Removed => Dispatch::Removed,
134 132 EntryState::Unknown => Dispatch::Unknown,
135 133 }
136 134 }
137 135
138 136 /// The file corresponding to this Dirstate entry is missing.
139 137 fn dispatch_missing(state: EntryState) -> Dispatch {
140 138 match state {
141 139 // File was removed from the filesystem during commands
142 140 EntryState::Normal | EntryState::Merged | EntryState::Added => {
143 141 Dispatch::Deleted
144 142 }
145 143 // File was removed, everything is normal
146 144 EntryState::Removed => Dispatch::Removed,
147 145 // File is unknown to Mercurial, everything is normal
148 146 EntryState::Unknown => Dispatch::Unknown,
149 147 }
150 148 }
151 149
152 150 /// Get stat data about the files explicitly specified by match.
153 151 /// TODO subrepos
154 152 fn walk_explicit<'a>(
155 153 files: &'a HashSet<&HgPath>,
156 154 dmap: &'a DirstateMap,
157 155 root_dir: impl AsRef<Path> + Sync + Send,
158 check_exec: bool,
159 list_clean: bool,
160 last_normal_time: i64,
156 options: StatusOptions,
161 157 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
162 158 files.par_iter().filter_map(move |filename| {
163 159 // TODO normalization
164 160 let normalized = filename.as_ref();
165 161
166 162 let buf = match hg_path_to_path_buf(normalized) {
167 163 Ok(x) => x,
168 164 Err(e) => return Some(Err(e.into())),
169 165 };
170 166 let target = root_dir.as_ref().join(buf);
171 167 let st = target.symlink_metadata();
172 168 match st {
173 169 Ok(meta) => {
174 170 let file_type = meta.file_type();
175 171 if file_type.is_file() || file_type.is_symlink() {
176 172 if let Some(entry) = dmap.get(normalized) {
177 173 return Some(Ok((
178 174 normalized,
179 175 dispatch_found(
180 176 &normalized,
181 177 *entry,
182 178 HgMetadata::from_metadata(meta),
183 179 &dmap.copy_map,
184 check_exec,
185 list_clean,
186 last_normal_time,
180 options,
187 181 ),
188 182 )));
189 183 }
190 184 } else {
191 185 if dmap.contains_key(normalized) {
192 186 return Some(Ok((normalized, Dispatch::Removed)));
193 187 }
194 188 }
195 189 }
196 190 Err(_) => {
197 191 if let Some(entry) = dmap.get(normalized) {
198 192 return Some(Ok((
199 193 normalized,
200 194 dispatch_missing(entry.state),
201 195 )));
202 196 }
203 197 }
204 198 };
205 199 None
206 200 })
207 201 }
208 202
203 #[derive(Debug, Copy, Clone)]
204 pub struct StatusOptions {
205 /// Remember the most recent modification timeslot for status, to make
206 /// sure we won't miss future size-preserving file content modifications
207 /// that happen within the same timeslot.
208 pub last_normal_time: i64,
209 /// Whether we are on a filesystem with UNIX-like exec flags
210 pub check_exec: bool,
211 pub list_clean: bool,
212 }
213
209 214 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
210 215 /// the relevant collections.
211 216 fn stat_dmap_entries(
212 217 dmap: &DirstateMap,
213 218 root_dir: impl AsRef<Path> + Sync + Send,
214 check_exec: bool,
215 list_clean: bool,
216 last_normal_time: i64,
219 options: StatusOptions,
217 220 ) -> impl ParallelIterator<Item = IoResult<(&HgPath, Dispatch)>> {
218 221 dmap.par_iter().map(move |(filename, entry)| {
219 222 let filename: &HgPath = filename;
220 223 let filename_as_path = hg_path_to_path_buf(filename)?;
221 224 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
222 225
223 226 match meta {
224 227 Ok(ref m)
225 228 if !(m.file_type().is_file()
226 229 || m.file_type().is_symlink()) =>
227 230 {
228 231 Ok((filename, dispatch_missing(entry.state)))
229 232 }
230 233 Ok(m) => Ok((
231 234 filename,
232 235 dispatch_found(
233 236 filename,
234 237 *entry,
235 238 HgMetadata::from_metadata(m),
236 239 &dmap.copy_map,
237 check_exec,
238 list_clean,
239 last_normal_time,
240 options,
240 241 ),
241 242 )),
242 243 Err(ref e)
243 244 if e.kind() == std::io::ErrorKind::NotFound
244 245 || e.raw_os_error() == Some(20) =>
245 246 {
246 247 // Rust does not yet have an `ErrorKind` for
247 248 // `NotADirectory` (errno 20)
248 249 // It happens if the dirstate contains `foo/bar` and
249 250 // foo is not a directory
250 251 Ok((filename, dispatch_missing(entry.state)))
251 252 }
252 253 Err(e) => Err(e),
253 254 }
254 255 })
255 256 }
256 257
257 258 pub struct StatusResult<'a> {
258 259 pub modified: Vec<&'a HgPath>,
259 260 pub added: Vec<&'a HgPath>,
260 261 pub removed: Vec<&'a HgPath>,
261 262 pub deleted: Vec<&'a HgPath>,
262 263 pub clean: Vec<&'a HgPath>,
263 264 /* TODO ignored
264 265 * TODO unknown */
265 266 }
266 267
267 268 fn build_response<'a>(
268 269 results: impl IntoIterator<Item = IoResult<(&'a HgPath, Dispatch)>>,
269 270 ) -> IoResult<(Vec<&'a HgPath>, StatusResult<'a>)> {
270 271 let mut lookup = vec![];
271 272 let mut modified = vec![];
272 273 let mut added = vec![];
273 274 let mut removed = vec![];
274 275 let mut deleted = vec![];
275 276 let mut clean = vec![];
276 277
277 278 for res in results.into_iter() {
278 279 let (filename, dispatch) = res?;
279 280 match dispatch {
280 281 Dispatch::Unknown => {}
281 282 Dispatch::Unsure => lookup.push(filename),
282 283 Dispatch::Modified => modified.push(filename),
283 284 Dispatch::Added => added.push(filename),
284 285 Dispatch::Removed => removed.push(filename),
285 286 Dispatch::Deleted => deleted.push(filename),
286 287 Dispatch::Clean => clean.push(filename),
287 288 }
288 289 }
289 290
290 291 Ok((
291 292 lookup,
292 293 StatusResult {
293 294 modified,
294 295 added,
295 296 removed,
296 297 deleted,
297 298 clean,
298 299 },
299 300 ))
300 301 }
301 302
302 303 pub fn status<'a: 'c, 'b: 'c, 'c>(
303 304 dmap: &'a DirstateMap,
304 305 matcher: &'b impl Matcher,
305 306 root_dir: impl AsRef<Path> + Sync + Send + Copy,
306 list_clean: bool,
307 last_normal_time: i64,
308 check_exec: bool,
307 options: StatusOptions,
309 308 ) -> IoResult<(Vec<&'c HgPath>, StatusResult<'c>)> {
310 309 let files = matcher.file_set();
311 310 let mut results = vec![];
312 311 if let Some(files) = files {
313 results.par_extend(walk_explicit(
314 &files,
315 &dmap,
316 root_dir,
317 check_exec,
318 list_clean,
319 last_normal_time,
320 ));
312 results.par_extend(walk_explicit(&files, &dmap, root_dir, options));
321 313 }
322 314
323 315 if !matcher.is_exact() {
324 let stat_results = stat_dmap_entries(
325 &dmap,
326 root_dir,
327 check_exec,
328 list_clean,
329 last_normal_time,
330 );
316 let stat_results = stat_dmap_entries(&dmap, root_dir, options);
331 317 results.par_extend(stat_results);
332 318 }
333 319
334 320 build_response(results)
335 321 }
@@ -1,184 +1,184 b''
1 1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
2 2 // and Mercurial contributors
3 3 //
4 4 // This software may be used and distributed according to the terms of the
5 5 // GNU General Public License version 2 or any later version.
6 6 mod ancestors;
7 7 pub mod dagops;
8 8 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
9 9 mod dirstate;
10 10 pub mod discovery;
11 11 pub mod testing; // unconditionally built, for use from integration tests
12 12 pub use dirstate::{
13 13 dirs_multiset::{DirsMultiset, DirsMultisetIter},
14 14 dirstate_map::DirstateMap,
15 15 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
16 status::{status, StatusResult},
16 status::{status, StatusOptions, StatusResult},
17 17 CopyMap, CopyMapIter, DirstateEntry, DirstateParents, EntryState,
18 18 StateMap, StateMapIter,
19 19 };
20 20 mod filepatterns;
21 21 pub mod matchers;
22 22 pub mod revlog;
23 23 pub use revlog::*;
24 24 #[cfg(feature = "with-re2")]
25 25 pub mod re2;
26 26 pub mod utils;
27 27
28 28 use crate::utils::hg_path::{HgPathBuf, HgPathError};
29 29 pub use filepatterns::{
30 30 parse_pattern_syntax, read_pattern_file, IgnorePattern,
31 31 PatternFileWarning, PatternSyntax,
32 32 };
33 33 use std::collections::HashMap;
34 34 use twox_hash::RandomXxHashBuilder64;
35 35
36 36 pub type LineNumber = usize;
37 37
38 38 /// Rust's default hasher is too slow because it tries to prevent collision
39 39 /// attacks. We are not concerned about those: if an ill-minded person has
40 40 /// write access to your repository, you have other issues.
41 41 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
42 42
43 43 #[derive(Clone, Debug, PartialEq)]
44 44 pub enum DirstateParseError {
45 45 TooLittleData,
46 46 Overflow,
47 47 CorruptedEntry(String),
48 48 Damaged,
49 49 }
50 50
51 51 impl From<std::io::Error> for DirstateParseError {
52 52 fn from(e: std::io::Error) -> Self {
53 53 DirstateParseError::CorruptedEntry(e.to_string())
54 54 }
55 55 }
56 56
57 57 impl ToString for DirstateParseError {
58 58 fn to_string(&self) -> String {
59 59 use crate::DirstateParseError::*;
60 60 match self {
61 61 TooLittleData => "Too little data for dirstate.".to_string(),
62 62 Overflow => "Overflow in dirstate.".to_string(),
63 63 CorruptedEntry(e) => format!("Corrupted entry: {:?}.", e),
64 64 Damaged => "Dirstate appears to be damaged.".to_string(),
65 65 }
66 66 }
67 67 }
68 68
69 69 #[derive(Debug, PartialEq)]
70 70 pub enum DirstatePackError {
71 71 CorruptedEntry(String),
72 72 CorruptedParent,
73 73 BadSize(usize, usize),
74 74 }
75 75
76 76 impl From<std::io::Error> for DirstatePackError {
77 77 fn from(e: std::io::Error) -> Self {
78 78 DirstatePackError::CorruptedEntry(e.to_string())
79 79 }
80 80 }
81 81 #[derive(Debug, PartialEq)]
82 82 pub enum DirstateMapError {
83 83 PathNotFound(HgPathBuf),
84 84 EmptyPath,
85 85 InvalidPath(HgPathError),
86 86 }
87 87
88 88 impl ToString for DirstateMapError {
89 89 fn to_string(&self) -> String {
90 90 match self {
91 91 DirstateMapError::PathNotFound(_) => {
92 92 "expected a value, found none".to_string()
93 93 }
94 94 DirstateMapError::EmptyPath => "Overflow in dirstate.".to_string(),
95 95 DirstateMapError::InvalidPath(e) => e.to_string(),
96 96 }
97 97 }
98 98 }
99 99
100 100 pub enum DirstateError {
101 101 Parse(DirstateParseError),
102 102 Pack(DirstatePackError),
103 103 Map(DirstateMapError),
104 104 IO(std::io::Error),
105 105 }
106 106
107 107 impl From<DirstateParseError> for DirstateError {
108 108 fn from(e: DirstateParseError) -> Self {
109 109 DirstateError::Parse(e)
110 110 }
111 111 }
112 112
113 113 impl From<DirstatePackError> for DirstateError {
114 114 fn from(e: DirstatePackError) -> Self {
115 115 DirstateError::Pack(e)
116 116 }
117 117 }
118 118
119 119 #[derive(Debug)]
120 120 pub enum PatternError {
121 121 Path(HgPathError),
122 122 UnsupportedSyntax(String),
123 123 UnsupportedSyntaxInFile(String, String, usize),
124 124 TooLong(usize),
125 125 IO(std::io::Error),
126 126 /// Needed a pattern that can be turned into a regex but got one that
127 127 /// can't. This should only happen through programmer error.
128 128 NonRegexPattern(IgnorePattern),
129 129 /// This is temporary, see `re2/mod.rs`.
130 130 /// This will cause a fallback to Python.
131 131 Re2NotInstalled,
132 132 }
133 133
134 134 impl ToString for PatternError {
135 135 fn to_string(&self) -> String {
136 136 match self {
137 137 PatternError::UnsupportedSyntax(syntax) => {
138 138 format!("Unsupported syntax {}", syntax)
139 139 }
140 140 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
141 141 format!(
142 142 "{}:{}: unsupported syntax {}",
143 143 file_path, line, syntax
144 144 )
145 145 }
146 146 PatternError::TooLong(size) => {
147 147 format!("matcher pattern is too long ({} bytes)", size)
148 148 }
149 149 PatternError::IO(e) => e.to_string(),
150 150 PatternError::Path(e) => e.to_string(),
151 151 PatternError::NonRegexPattern(pattern) => {
152 152 format!("'{:?}' cannot be turned into a regex", pattern)
153 153 }
154 154 PatternError::Re2NotInstalled => {
155 155 "Re2 is not installed, cannot use regex functionality."
156 156 .to_string()
157 157 }
158 158 }
159 159 }
160 160 }
161 161
162 162 impl From<DirstateMapError> for DirstateError {
163 163 fn from(e: DirstateMapError) -> Self {
164 164 DirstateError::Map(e)
165 165 }
166 166 }
167 167
168 168 impl From<std::io::Error> for DirstateError {
169 169 fn from(e: std::io::Error) -> Self {
170 170 DirstateError::IO(e)
171 171 }
172 172 }
173 173
174 174 impl From<std::io::Error> for PatternError {
175 175 fn from(e: std::io::Error) -> Self {
176 176 PatternError::IO(e)
177 177 }
178 178 }
179 179
180 180 impl From<HgPathError> for PatternError {
181 181 fn from(e: HgPathError) -> Self {
182 182 PatternError::Path(e)
183 183 }
184 184 }
@@ -1,888 +1,892 b''
1 1 // matchers.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Structs and types for matching files and directories.
9 9
10 10 #[cfg(feature = "with-re2")]
11 11 use crate::re2::Re2;
12 12 use crate::{
13 13 dirstate::dirs_multiset::DirsChildrenMultiset,
14 14 filepatterns::{
15 15 build_single_regex, filter_subincludes, get_patterns_from_file,
16 16 PatternFileWarning, PatternResult, SubInclude,
17 17 },
18 18 utils::{
19 19 files::find_dirs,
20 20 hg_path::{HgPath, HgPathBuf},
21 21 Escaped,
22 22 },
23 23 DirsMultiset, DirstateMapError, FastHashMap, IgnorePattern, PatternError,
24 24 PatternSyntax,
25 25 };
26 26
27 27 use std::collections::HashSet;
28 28 use std::fmt::{Display, Error, Formatter};
29 29 use std::iter::FromIterator;
30 30 use std::ops::Deref;
31 31 use std::path::Path;
32 32
33 33 #[derive(Debug, PartialEq)]
34 34 pub enum VisitChildrenSet<'a> {
35 35 /// Don't visit anything
36 36 Empty,
37 37 /// Only visit this directory
38 38 This,
39 39 /// Visit this directory and these subdirectories
40 40 /// TODO Should we implement a `NonEmptyHashSet`?
41 41 Set(HashSet<&'a HgPath>),
42 42 /// Visit this directory and all subdirectories
43 43 Recursive,
44 44 }
45 45
46 46 pub trait Matcher {
47 47 /// Explicitly listed files
48 48 fn file_set(&self) -> Option<&HashSet<&HgPath>>;
49 49 /// Returns whether `filename` is in `file_set`
50 50 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool;
51 51 /// Returns whether `filename` is matched by this matcher
52 52 fn matches(&self, filename: impl AsRef<HgPath>) -> bool;
53 53 /// Decides whether a directory should be visited based on whether it
54 54 /// has potential matches in it or one of its subdirectories, and
55 55 /// potentially lists which subdirectories of that directory should be
56 56 /// visited. This is based on the match's primary, included, and excluded
57 57 /// patterns.
58 58 ///
59 59 /// # Example
60 60 ///
61 61 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
62 62 /// return the following values (assuming the implementation of
63 63 /// visit_children_set is capable of recognizing this; some implementations
64 64 /// are not).
65 65 ///
66 66 /// ```text
67 67 /// ```ignore
68 68 /// '' -> {'foo', 'qux'}
69 69 /// 'baz' -> set()
70 70 /// 'foo' -> {'bar'}
71 71 /// // Ideally this would be `Recursive`, but since the prefix nature of
72 72 /// // matchers is applied to the entire matcher, we have to downgrade this
73 73 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
74 74 /// // `RootFilesIn'-kind matcher being mixed in.
75 75 /// 'foo/bar' -> 'this'
76 76 /// 'qux' -> 'this'
77 77 /// ```
78 78 /// # Important
79 79 ///
80 80 /// Most matchers do not know if they're representing files or
81 81 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
82 82 /// file or a directory, so `visit_children_set('dir')` for most matchers
83 83 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
84 84 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
85 85 /// it may return `VisitChildrenSet::This`.
86 86 /// Do not rely on the return being a `HashSet` indicating that there are
87 87 /// no files in this dir to investigate (or equivalently that if there are
88 88 /// files to investigate in 'dir' that it will always return
89 89 /// `VisitChildrenSet::This`).
90 90 fn visit_children_set(
91 91 &self,
92 92 directory: impl AsRef<HgPath>,
93 93 ) -> VisitChildrenSet;
94 94 /// Matcher will match everything and `files_set()` will be empty:
95 95 /// optimization might be possible.
96 96 fn matches_everything(&self) -> bool;
97 97 /// Matcher will match exactly the files in `files_set()`: optimization
98 98 /// might be possible.
99 99 fn is_exact(&self) -> bool;
100 100 }
101 101
102 102 /// Matches everything.
103 103 ///```
104 104 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
105 105 ///
106 106 /// let matcher = AlwaysMatcher;
107 107 ///
108 108 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
109 109 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
110 110 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
111 111 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
112 112 /// ```
113 113 #[derive(Debug)]
114 114 pub struct AlwaysMatcher;
115 115
116 116 impl Matcher for AlwaysMatcher {
117 117 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
118 118 None
119 119 }
120 120 fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool {
121 121 false
122 122 }
123 123 fn matches(&self, _filename: impl AsRef<HgPath>) -> bool {
124 124 true
125 125 }
126 126 fn visit_children_set(
127 127 &self,
128 128 _directory: impl AsRef<HgPath>,
129 129 ) -> VisitChildrenSet {
130 130 VisitChildrenSet::Recursive
131 131 }
132 132 fn matches_everything(&self) -> bool {
133 133 true
134 134 }
135 135 fn is_exact(&self) -> bool {
136 136 false
137 137 }
138 138 }
139 139
140 140 /// Matches the input files exactly. They are interpreted as paths, not
141 141 /// patterns.
142 142 ///
143 143 ///```
144 144 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath };
145 145 ///
146 146 /// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")];
147 147 /// let matcher = FileMatcher::new(&files).unwrap();
148 148 ///
149 149 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
150 150 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
151 151 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
152 152 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
153 153 /// ```
154 154 #[derive(Debug)]
155 155 pub struct FileMatcher<'a> {
156 156 files: HashSet<&'a HgPath>,
157 157 dirs: DirsMultiset,
158 158 }
159 159
160 160 impl<'a> FileMatcher<'a> {
161 161 pub fn new(
162 162 files: &'a [impl AsRef<HgPath>],
163 163 ) -> Result<Self, DirstateMapError> {
164 164 Ok(Self {
165 165 files: HashSet::from_iter(files.iter().map(|f| f.as_ref())),
166 166 dirs: DirsMultiset::from_manifest(files)?,
167 167 })
168 168 }
169 169 fn inner_matches(&self, filename: impl AsRef<HgPath>) -> bool {
170 170 self.files.contains(filename.as_ref())
171 171 }
172 172 }
173 173
174 174 impl<'a> Matcher for FileMatcher<'a> {
175 175 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
176 176 Some(&self.files)
177 177 }
178 178 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool {
179 179 self.inner_matches(filename)
180 180 }
181 181 fn matches(&self, filename: impl AsRef<HgPath>) -> bool {
182 182 self.inner_matches(filename)
183 183 }
184 184 fn visit_children_set(
185 185 &self,
186 186 directory: impl AsRef<HgPath>,
187 187 ) -> VisitChildrenSet {
188 188 if self.files.is_empty() || !self.dirs.contains(&directory) {
189 189 return VisitChildrenSet::Empty;
190 190 }
191 191 let dirs_as_set = self.dirs.iter().map(|k| k.deref()).collect();
192 192
193 193 let mut candidates: HashSet<&HgPath> =
194 194 self.files.union(&dirs_as_set).map(|k| *k).collect();
195 195 candidates.remove(HgPath::new(b""));
196 196
197 197 if !directory.as_ref().is_empty() {
198 198 let directory = [directory.as_ref().as_bytes(), b"/"].concat();
199 199 candidates = candidates
200 200 .iter()
201 201 .filter_map(|c| {
202 202 if c.as_bytes().starts_with(&directory) {
203 203 Some(HgPath::new(&c.as_bytes()[directory.len()..]))
204 204 } else {
205 205 None
206 206 }
207 207 })
208 208 .collect();
209 209 }
210 210
211 211 // `self.dirs` includes all of the directories, recursively, so if
212 212 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
213 213 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
214 214 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
215 215 // subdir will be in there without a slash.
216 216 VisitChildrenSet::Set(
217 217 candidates
218 218 .iter()
219 219 .filter_map(|c| {
220 220 if c.bytes().all(|b| *b != b'/') {
221 221 Some(*c)
222 222 } else {
223 223 None
224 224 }
225 225 })
226 226 .collect(),
227 227 )
228 228 }
229 229 fn matches_everything(&self) -> bool {
230 230 false
231 231 }
232 232 fn is_exact(&self) -> bool {
233 233 true
234 234 }
235 235 }
236 236
237 237 /// Matches files that are included in the ignore rules.
238 238 #[cfg_attr(
239 239 feature = "with-re2",
240 240 doc = r##"
241 241 ```
242 242 use hg::{
243 243 matchers::{IncludeMatcher, Matcher},
244 244 IgnorePattern,
245 245 PatternSyntax,
246 246 utils::hg_path::HgPath
247 247 };
248 248 use std::path::Path;
249 249 ///
250 250 let ignore_patterns =
251 251 vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
252 252 let (matcher, _) = IncludeMatcher::new(ignore_patterns, "").unwrap();
253 253 ///
254 254 assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
255 255 assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
256 256 assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
257 257 assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
258 258 ```
259 259 "##
260 260 )]
261 261 pub struct IncludeMatcher<'a> {
262 262 patterns: Vec<u8>,
263 263 match_fn: Box<dyn for<'r> Fn(&'r HgPath) -> bool + 'a + Sync>,
264 264 /// Whether all the patterns match a prefix (i.e. recursively)
265 265 prefix: bool,
266 266 roots: HashSet<HgPathBuf>,
267 267 dirs: HashSet<HgPathBuf>,
268 268 parents: HashSet<HgPathBuf>,
269 269 }
270 270
271 271 impl<'a> Matcher for IncludeMatcher<'a> {
272 272 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
273 273 None
274 274 }
275 275
276 276 fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool {
277 277 false
278 278 }
279 279
280 280 fn matches(&self, filename: impl AsRef<HgPath>) -> bool {
281 281 (self.match_fn)(filename.as_ref())
282 282 }
283 283
284 284 fn visit_children_set(
285 285 &self,
286 286 directory: impl AsRef<HgPath>,
287 287 ) -> VisitChildrenSet {
288 288 let dir = directory.as_ref();
289 289 if self.prefix && self.roots.contains(dir) {
290 290 return VisitChildrenSet::Recursive;
291 291 }
292 292 if self.roots.contains(HgPath::new(b""))
293 293 || self.roots.contains(dir)
294 294 || self.dirs.contains(dir)
295 295 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
296 296 {
297 297 return VisitChildrenSet::This;
298 298 }
299 299
300 300 if self.parents.contains(directory.as_ref()) {
301 301 let multiset = self.get_all_parents_children();
302 302 if let Some(children) = multiset.get(dir) {
303 303 return VisitChildrenSet::Set(children.to_owned());
304 304 }
305 305 }
306 306 VisitChildrenSet::Empty
307 307 }
308 308
309 309 fn matches_everything(&self) -> bool {
310 310 false
311 311 }
312 312
313 313 fn is_exact(&self) -> bool {
314 314 false
315 315 }
316 316 }
317 317
318 318 #[cfg(feature = "with-re2")]
319 319 /// Returns a function that matches an `HgPath` against the given regex
320 320 /// pattern.
321 321 ///
322 322 /// This can fail when the pattern is invalid or not supported by the
323 323 /// underlying engine `Re2`, for instance anything with back-references.
324 324 fn re_matcher(
325 325 pattern: &[u8],
326 326 ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
327 327 let regex = Re2::new(pattern);
328 328 let regex = regex.map_err(|e| PatternError::UnsupportedSyntax(e))?;
329 329 Ok(move |path: &HgPath| regex.is_match(path.as_bytes()))
330 330 }
331 331
332 332 #[cfg(not(feature = "with-re2"))]
333 333 fn re_matcher(_: &[u8]) -> PatternResult<Box<dyn Fn(&HgPath) -> bool + Sync>> {
334 334 Err(PatternError::Re2NotInstalled)
335 335 }
336 336
337 337 /// Returns the regex pattern and a function that matches an `HgPath` against
338 338 /// said regex formed by the given ignore patterns.
339 339 fn build_regex_match<'a>(
340 340 ignore_patterns: &'a [&'a IgnorePattern],
341 341 ) -> PatternResult<(Vec<u8>, Box<dyn Fn(&HgPath) -> bool + Sync>)> {
342 342 let regexps: Result<Vec<_>, PatternError> = ignore_patterns
343 343 .into_iter()
344 344 .map(|k| build_single_regex(*k))
345 345 .collect();
346 346 let regexps = regexps?;
347 347 let full_regex = regexps.join(&b'|');
348 348
349 349 let matcher = re_matcher(&full_regex)?;
350 350 let func = Box::new(move |filename: &HgPath| matcher(filename));
351 351
352 352 Ok((full_regex, func))
353 353 }
354 354
355 355 /// Returns roots and directories corresponding to each pattern.
356 356 ///
357 357 /// This calculates the roots and directories exactly matching the patterns and
358 358 /// returns a tuple of (roots, dirs). It does not return other directories
359 359 /// which may also need to be considered, like the parent directories.
360 360 fn roots_and_dirs(
361 361 ignore_patterns: &[IgnorePattern],
362 362 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
363 363 let mut roots = Vec::new();
364 364 let mut dirs = Vec::new();
365 365
366 366 for ignore_pattern in ignore_patterns {
367 367 let IgnorePattern {
368 368 syntax, pattern, ..
369 369 } = ignore_pattern;
370 370 match syntax {
371 371 PatternSyntax::RootGlob | PatternSyntax::Glob => {
372 372 let mut root = vec![];
373 373
374 374 for p in pattern.split(|c| *c == b'/') {
375 375 if p.iter().any(|c| match *c {
376 376 b'[' | b'{' | b'*' | b'?' => true,
377 377 _ => false,
378 378 }) {
379 379 break;
380 380 }
381 381 root.push(HgPathBuf::from_bytes(p));
382 382 }
383 383 let buf =
384 384 root.iter().fold(HgPathBuf::new(), |acc, r| acc.join(r));
385 385 roots.push(buf);
386 386 }
387 387 PatternSyntax::Path | PatternSyntax::RelPath => {
388 388 let pat = HgPath::new(if pattern == b"." {
389 389 &[] as &[u8]
390 390 } else {
391 391 pattern
392 392 });
393 393 roots.push(pat.to_owned());
394 394 }
395 395 PatternSyntax::RootFiles => {
396 396 let pat = if pattern == b"." {
397 397 &[] as &[u8]
398 398 } else {
399 399 pattern
400 400 };
401 401 dirs.push(HgPathBuf::from_bytes(pat));
402 402 }
403 403 _ => {
404 404 roots.push(HgPathBuf::new());
405 405 }
406 406 }
407 407 }
408 408 (roots, dirs)
409 409 }
410 410
411 411 /// Paths extracted from patterns
412 412 #[derive(Debug, PartialEq)]
413 413 struct RootsDirsAndParents {
414 414 /// Directories to match recursively
415 415 pub roots: HashSet<HgPathBuf>,
416 416 /// Directories to match non-recursively
417 417 pub dirs: HashSet<HgPathBuf>,
418 418 /// Implicitly required directories to go to items in either roots or dirs
419 419 pub parents: HashSet<HgPathBuf>,
420 420 }
421 421
422 422 /// Extract roots, dirs and parents from patterns.
423 423 fn roots_dirs_and_parents(
424 424 ignore_patterns: &[IgnorePattern],
425 425 ) -> PatternResult<RootsDirsAndParents> {
426 426 let (roots, dirs) = roots_and_dirs(ignore_patterns);
427 427
428 428 let mut parents = HashSet::new();
429 429
430 430 parents.extend(
431 431 DirsMultiset::from_manifest(&dirs)
432 432 .map_err(|e| match e {
433 433 DirstateMapError::InvalidPath(e) => e,
434 434 _ => unreachable!(),
435 435 })?
436 436 .iter()
437 437 .map(|k| k.to_owned()),
438 438 );
439 439 parents.extend(
440 440 DirsMultiset::from_manifest(&roots)
441 441 .map_err(|e| match e {
442 442 DirstateMapError::InvalidPath(e) => e,
443 443 _ => unreachable!(),
444 444 })?
445 445 .iter()
446 446 .map(|k| k.to_owned()),
447 447 );
448 448
449 449 Ok(RootsDirsAndParents {
450 450 roots: HashSet::from_iter(roots),
451 451 dirs: HashSet::from_iter(dirs),
452 452 parents,
453 453 })
454 454 }
455 455
456 456 /// Returns a function that checks whether a given file (in the general sense)
457 457 /// should be matched.
458 458 fn build_match<'a, 'b>(
459 459 ignore_patterns: &'a [IgnorePattern],
460 460 root_dir: impl AsRef<Path>,
461 461 ) -> PatternResult<(
462 462 Vec<u8>,
463 463 Box<dyn Fn(&HgPath) -> bool + 'b + Sync>,
464 464 Vec<PatternFileWarning>,
465 465 )> {
466 466 let mut match_funcs: Vec<Box<dyn Fn(&HgPath) -> bool + Sync>> = vec![];
467 467 // For debugging and printing
468 468 let mut patterns = vec![];
469 469 let mut all_warnings = vec![];
470 470
471 471 let (subincludes, ignore_patterns) =
472 472 filter_subincludes(ignore_patterns, root_dir)?;
473 473
474 474 if !subincludes.is_empty() {
475 475 // Build prefix-based matcher functions for subincludes
476 476 let mut submatchers = FastHashMap::default();
477 477 let mut prefixes = vec![];
478 478
479 479 for SubInclude { prefix, root, path } in subincludes.into_iter() {
480 480 let (match_fn, warnings) = get_ignore_function(&[path], root)?;
481 481 all_warnings.extend(warnings);
482 482 prefixes.push(prefix.to_owned());
483 483 submatchers.insert(prefix.to_owned(), match_fn);
484 484 }
485 485
486 486 let match_subinclude = move |filename: &HgPath| {
487 487 for prefix in prefixes.iter() {
488 488 if let Some(rel) = filename.relative_to(prefix) {
489 489 if (submatchers.get(prefix).unwrap())(rel) {
490 490 return true;
491 491 }
492 492 }
493 493 }
494 494 false
495 495 };
496 496
497 497 match_funcs.push(Box::new(match_subinclude));
498 498 }
499 499
500 500 if !ignore_patterns.is_empty() {
501 501 // Either do dumb matching if all patterns are rootfiles, or match
502 502 // with a regex.
503 503 if ignore_patterns
504 504 .iter()
505 505 .all(|k| k.syntax == PatternSyntax::RootFiles)
506 506 {
507 507 let dirs: HashSet<_> = ignore_patterns
508 508 .iter()
509 509 .map(|k| k.pattern.to_owned())
510 510 .collect();
511 511 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
512 512
513 513 let match_func = move |path: &HgPath| -> bool {
514 514 let path = path.as_bytes();
515 515 let i = path.iter().rfind(|a| **a == b'/');
516 516 let dir = if let Some(i) = i {
517 517 &path[..*i as usize]
518 518 } else {
519 519 b"."
520 520 };
521 521 dirs.contains(dir.deref())
522 522 };
523 523 match_funcs.push(Box::new(match_func));
524 524
525 525 patterns.extend(b"rootfilesin: ");
526 526 dirs_vec.sort();
527 527 patterns.extend(dirs_vec.escaped_bytes());
528 528 } else {
529 529 let (new_re, match_func) = build_regex_match(&ignore_patterns)?;
530 530 patterns = new_re;
531 531 match_funcs.push(match_func)
532 532 }
533 533 }
534 534
535 535 Ok(if match_funcs.len() == 1 {
536 536 (patterns, match_funcs.remove(0), all_warnings)
537 537 } else {
538 538 (
539 539 patterns,
540 540 Box::new(move |f: &HgPath| -> bool {
541 541 match_funcs.iter().any(|match_func| match_func(f))
542 542 }),
543 543 all_warnings,
544 544 )
545 545 })
546 546 }
547 547
548 548 /// Parses all "ignore" files with their recursive includes and returns a
549 549 /// function that checks whether a given file (in the general sense) should be
550 550 /// ignored.
551 551 pub fn get_ignore_function<'a>(
552 552 all_pattern_files: &[impl AsRef<Path>],
553 553 root_dir: impl AsRef<Path>,
554 554 ) -> PatternResult<(
555 555 impl for<'r> Fn(&'r HgPath) -> bool + Sync,
556 556 Vec<PatternFileWarning>,
557 557 )> {
558 558 let mut all_patterns = vec![];
559 559 let mut all_warnings = vec![];
560 560
561 561 for pattern_file in all_pattern_files.into_iter() {
562 562 let (patterns, warnings) =
563 563 get_patterns_from_file(pattern_file, &root_dir)?;
564 564
565 565 all_patterns.extend(patterns);
566 566 all_warnings.extend(warnings);
567 567 }
568 568 let (matcher, warnings) = IncludeMatcher::new(all_patterns, root_dir)?;
569 569 all_warnings.extend(warnings);
570 570 Ok((move |path: &HgPath| matcher.matches(path), all_warnings))
571 571 }
572 572
573 573 impl<'a> IncludeMatcher<'a> {
574 574 pub fn new(
575 575 ignore_patterns: Vec<IgnorePattern>,
576 576 root_dir: impl AsRef<Path>,
577 577 ) -> PatternResult<(Self, Vec<PatternFileWarning>)> {
578 578 let (patterns, match_fn, warnings) =
579 579 build_match(&ignore_patterns, root_dir)?;
580 580 let RootsDirsAndParents {
581 581 roots,
582 582 dirs,
583 583 parents,
584 584 } = roots_dirs_and_parents(&ignore_patterns)?;
585 585
586 586 let prefix = ignore_patterns.iter().any(|k| match k.syntax {
587 587 PatternSyntax::Path | PatternSyntax::RelPath => true,
588 588 _ => false,
589 589 });
590 590
591 591 Ok((
592 592 Self {
593 593 patterns,
594 594 match_fn,
595 595 prefix,
596 596 roots,
597 597 dirs,
598 598 parents,
599 599 },
600 600 warnings,
601 601 ))
602 602 }
603 603
604 604 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
605 605 // TODO cache
606 606 let thing = self
607 607 .dirs
608 608 .iter()
609 609 .chain(self.roots.iter())
610 610 .chain(self.parents.iter());
611 611 DirsChildrenMultiset::new(thing, Some(&self.parents))
612 612 }
613 613 }
614 614
615 615 impl<'a> Display for IncludeMatcher<'a> {
616 616 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
617 617 write!(
618 618 f,
619 619 "IncludeMatcher(includes='{}')",
620 620 String::from_utf8_lossy(&self.patterns.escaped_bytes())
621 621 )
622 622 }
623 623 }
624 624
625 625 #[cfg(test)]
626 626 mod tests {
627 627 use super::*;
628 628 use pretty_assertions::assert_eq;
629 629 use std::path::Path;
630 630
631 631 #[test]
632 632 fn test_roots_and_dirs() {
633 633 let pats = vec![
634 634 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
635 635 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
636 636 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
637 637 ];
638 638 let (roots, dirs) = roots_and_dirs(&pats);
639 639
640 640 assert_eq!(
641 641 roots,
642 642 vec!(
643 643 HgPathBuf::from_bytes(b"g/h"),
644 644 HgPathBuf::from_bytes(b"g/h"),
645 645 HgPathBuf::new()
646 646 ),
647 647 );
648 648 assert_eq!(dirs, vec!());
649 649 }
650 650
651 651 #[test]
652 652 fn test_roots_dirs_and_parents() {
653 653 let pats = vec![
654 654 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
655 655 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
656 656 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
657 657 ];
658 658
659 659 let mut roots = HashSet::new();
660 660 roots.insert(HgPathBuf::from_bytes(b"g/h"));
661 661 roots.insert(HgPathBuf::new());
662 662
663 663 let dirs = HashSet::new();
664 664
665 665 let mut parents = HashSet::new();
666 666 parents.insert(HgPathBuf::new());
667 667 parents.insert(HgPathBuf::from_bytes(b"g"));
668 668
669 669 assert_eq!(
670 670 roots_dirs_and_parents(&pats).unwrap(),
671 RootsDirsAndParents {roots, dirs, parents}
671 RootsDirsAndParents {
672 roots,
673 dirs,
674 parents
675 }
672 676 );
673 677 }
674 678
675 679 #[test]
676 680 fn test_filematcher_visit_children_set() {
677 681 // Visitchildrenset
678 682 let files = vec![HgPath::new(b"dir/subdir/foo.txt")];
679 683 let matcher = FileMatcher::new(&files).unwrap();
680 684
681 685 let mut set = HashSet::new();
682 686 set.insert(HgPath::new(b"dir"));
683 687 assert_eq!(
684 688 matcher.visit_children_set(HgPath::new(b"")),
685 689 VisitChildrenSet::Set(set)
686 690 );
687 691
688 692 let mut set = HashSet::new();
689 693 set.insert(HgPath::new(b"subdir"));
690 694 assert_eq!(
691 695 matcher.visit_children_set(HgPath::new(b"dir")),
692 696 VisitChildrenSet::Set(set)
693 697 );
694 698
695 699 let mut set = HashSet::new();
696 700 set.insert(HgPath::new(b"foo.txt"));
697 701 assert_eq!(
698 702 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
699 703 VisitChildrenSet::Set(set)
700 704 );
701 705
702 706 assert_eq!(
703 707 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
704 708 VisitChildrenSet::Empty
705 709 );
706 710 assert_eq!(
707 711 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
708 712 VisitChildrenSet::Empty
709 713 );
710 714 assert_eq!(
711 715 matcher.visit_children_set(HgPath::new(b"folder")),
712 716 VisitChildrenSet::Empty
713 717 );
714 718 }
715 719
716 720 #[test]
717 721 fn test_filematcher_visit_children_set_files_and_dirs() {
718 722 let files = vec![
719 723 HgPath::new(b"rootfile.txt"),
720 724 HgPath::new(b"a/file1.txt"),
721 725 HgPath::new(b"a/b/file2.txt"),
722 726 // No file in a/b/c
723 727 HgPath::new(b"a/b/c/d/file4.txt"),
724 728 ];
725 729 let matcher = FileMatcher::new(&files).unwrap();
726 730
727 731 let mut set = HashSet::new();
728 732 set.insert(HgPath::new(b"a"));
729 733 set.insert(HgPath::new(b"rootfile.txt"));
730 734 assert_eq!(
731 735 matcher.visit_children_set(HgPath::new(b"")),
732 736 VisitChildrenSet::Set(set)
733 737 );
734 738
735 739 let mut set = HashSet::new();
736 740 set.insert(HgPath::new(b"b"));
737 741 set.insert(HgPath::new(b"file1.txt"));
738 742 assert_eq!(
739 743 matcher.visit_children_set(HgPath::new(b"a")),
740 744 VisitChildrenSet::Set(set)
741 745 );
742 746
743 747 let mut set = HashSet::new();
744 748 set.insert(HgPath::new(b"c"));
745 749 set.insert(HgPath::new(b"file2.txt"));
746 750 assert_eq!(
747 751 matcher.visit_children_set(HgPath::new(b"a/b")),
748 752 VisitChildrenSet::Set(set)
749 753 );
750 754
751 755 let mut set = HashSet::new();
752 756 set.insert(HgPath::new(b"d"));
753 757 assert_eq!(
754 758 matcher.visit_children_set(HgPath::new(b"a/b/c")),
755 759 VisitChildrenSet::Set(set)
756 760 );
757 761 let mut set = HashSet::new();
758 762 set.insert(HgPath::new(b"file4.txt"));
759 763 assert_eq!(
760 764 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
761 765 VisitChildrenSet::Set(set)
762 766 );
763 767
764 768 assert_eq!(
765 769 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
766 770 VisitChildrenSet::Empty
767 771 );
768 772 assert_eq!(
769 773 matcher.visit_children_set(HgPath::new(b"folder")),
770 774 VisitChildrenSet::Empty
771 775 );
772 776 }
773 777
774 778 #[cfg(feature = "with-re2")]
775 779 #[test]
776 780 fn test_includematcher() {
777 781 // VisitchildrensetPrefix
778 782 let (matcher, _) = IncludeMatcher::new(
779 783 vec![IgnorePattern::new(
780 784 PatternSyntax::RelPath,
781 785 b"dir/subdir",
782 786 Path::new(""),
783 787 )],
784 788 "",
785 789 )
786 790 .unwrap();
787 791
788 792 let mut set = HashSet::new();
789 793 set.insert(HgPath::new(b"dir"));
790 794 assert_eq!(
791 795 matcher.visit_children_set(HgPath::new(b"")),
792 796 VisitChildrenSet::Set(set)
793 797 );
794 798
795 799 let mut set = HashSet::new();
796 800 set.insert(HgPath::new(b"subdir"));
797 801 assert_eq!(
798 802 matcher.visit_children_set(HgPath::new(b"dir")),
799 803 VisitChildrenSet::Set(set)
800 804 );
801 805 assert_eq!(
802 806 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
803 807 VisitChildrenSet::Recursive
804 808 );
805 809 // OPT: This should probably be 'all' if its parent is?
806 810 assert_eq!(
807 811 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
808 812 VisitChildrenSet::This
809 813 );
810 814 assert_eq!(
811 815 matcher.visit_children_set(HgPath::new(b"folder")),
812 816 VisitChildrenSet::Empty
813 817 );
814 818
815 819 // VisitchildrensetRootfilesin
816 820 let (matcher, _) = IncludeMatcher::new(
817 821 vec![IgnorePattern::new(
818 822 PatternSyntax::RootFiles,
819 823 b"dir/subdir",
820 824 Path::new(""),
821 825 )],
822 826 "",
823 827 )
824 828 .unwrap();
825 829
826 830 let mut set = HashSet::new();
827 831 set.insert(HgPath::new(b"dir"));
828 832 assert_eq!(
829 833 matcher.visit_children_set(HgPath::new(b"")),
830 834 VisitChildrenSet::Set(set)
831 835 );
832 836
833 837 let mut set = HashSet::new();
834 838 set.insert(HgPath::new(b"subdir"));
835 839 assert_eq!(
836 840 matcher.visit_children_set(HgPath::new(b"dir")),
837 841 VisitChildrenSet::Set(set)
838 842 );
839 843
840 844 assert_eq!(
841 845 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
842 846 VisitChildrenSet::This
843 847 );
844 848 assert_eq!(
845 849 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
846 850 VisitChildrenSet::Empty
847 851 );
848 852 assert_eq!(
849 853 matcher.visit_children_set(HgPath::new(b"folder")),
850 854 VisitChildrenSet::Empty
851 855 );
852 856
853 857 // VisitchildrensetGlob
854 858 let (matcher, _) = IncludeMatcher::new(
855 859 vec![IgnorePattern::new(
856 860 PatternSyntax::Glob,
857 861 b"dir/z*",
858 862 Path::new(""),
859 863 )],
860 864 "",
861 865 )
862 866 .unwrap();
863 867
864 868 let mut set = HashSet::new();
865 869 set.insert(HgPath::new(b"dir"));
866 870 assert_eq!(
867 871 matcher.visit_children_set(HgPath::new(b"")),
868 872 VisitChildrenSet::Set(set)
869 873 );
870 874 assert_eq!(
871 875 matcher.visit_children_set(HgPath::new(b"folder")),
872 876 VisitChildrenSet::Empty
873 877 );
874 878 assert_eq!(
875 879 matcher.visit_children_set(HgPath::new(b"dir")),
876 880 VisitChildrenSet::This
877 881 );
878 882 // OPT: these should probably be set().
879 883 assert_eq!(
880 884 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
881 885 VisitChildrenSet::This
882 886 );
883 887 assert_eq!(
884 888 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
885 889 VisitChildrenSet::This
886 890 );
887 891 }
888 892 }
General Comments 0
You need to be logged in to leave comments. Login now