##// END OF EJS Templates
matchers: fix the bug in rust PatternMatcher that made it cut off early...
Arseniy Alekseyev -
r52459:529a6558 stable
parent child Browse files
Show More
@@ -1,2434 +1,2431 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 use format_bytes::format_bytes;
10 use format_bytes::format_bytes;
11 use once_cell::sync::OnceCell;
11 use once_cell::sync::OnceCell;
12
12
13 use crate::{
13 use crate::{
14 dirstate::dirs_multiset::DirsChildrenMultiset,
14 dirstate::dirs_multiset::DirsChildrenMultiset,
15 filepatterns::{
15 filepatterns::{
16 build_single_regex, filter_subincludes, get_patterns_from_file,
16 build_single_regex, filter_subincludes, get_patterns_from_file,
17 PatternFileWarning, PatternResult,
17 PatternFileWarning, PatternResult,
18 },
18 },
19 utils::{
19 utils::{
20 files::find_dirs,
20 files::{dir_ancestors, find_dirs},
21 hg_path::{HgPath, HgPathBuf, HgPathError},
21 hg_path::{HgPath, HgPathBuf, HgPathError},
22 Escaped,
22 Escaped,
23 },
23 },
24 DirsMultiset, FastHashMap, IgnorePattern, PatternError, PatternSyntax,
24 DirsMultiset, FastHashMap, IgnorePattern, PatternError, PatternSyntax,
25 };
25 };
26
26
27 use crate::dirstate::status::IgnoreFnType;
27 use crate::dirstate::status::IgnoreFnType;
28 use crate::filepatterns::normalize_path_bytes;
28 use crate::filepatterns::normalize_path_bytes;
29 use std::collections::HashSet;
29 use std::collections::HashSet;
30 use std::fmt::{Display, Error, Formatter};
30 use std::fmt::{Display, Error, Formatter};
31 use std::path::{Path, PathBuf};
31 use std::path::{Path, PathBuf};
32 use std::{borrow::ToOwned, collections::BTreeSet};
32 use std::{borrow::ToOwned, collections::BTreeSet};
33
33
34 #[derive(Debug, PartialEq)]
34 #[derive(Debug, PartialEq)]
35 pub enum VisitChildrenSet {
35 pub enum VisitChildrenSet {
36 /// Don't visit anything
36 /// Don't visit anything
37 Empty,
37 Empty,
38 /// Visit this directory and probably its children
38 /// Visit this directory and probably its children
39 This,
39 This,
40 /// Only visit the children (both files and directories) if they
40 /// Only visit the children (both files and directories) if they
41 /// are mentioned in this set. (empty set corresponds to [Empty])
41 /// are mentioned in this set. (empty set corresponds to [Empty])
42 /// TODO Should we implement a `NonEmptyHashSet`?
42 /// TODO Should we implement a `NonEmptyHashSet`?
43 Set(HashSet<HgPathBuf>),
43 Set(HashSet<HgPathBuf>),
44 /// Visit this directory and all subdirectories
44 /// Visit this directory and all subdirectories
45 /// (you can stop asking about the children set)
45 /// (you can stop asking about the children set)
46 Recursive,
46 Recursive,
47 }
47 }
48
48
49 pub trait Matcher: core::fmt::Debug {
49 pub trait Matcher: core::fmt::Debug {
50 /// Explicitly listed files
50 /// Explicitly listed files
51 fn file_set(&self) -> Option<&HashSet<HgPathBuf>>;
51 fn file_set(&self) -> Option<&HashSet<HgPathBuf>>;
52 /// Returns whether `filename` is in `file_set`
52 /// Returns whether `filename` is in `file_set`
53 fn exact_match(&self, filename: &HgPath) -> bool;
53 fn exact_match(&self, filename: &HgPath) -> bool;
54 /// Returns whether `filename` is matched by this matcher
54 /// Returns whether `filename` is matched by this matcher
55 fn matches(&self, filename: &HgPath) -> bool;
55 fn matches(&self, filename: &HgPath) -> bool;
56 /// Decides whether a directory should be visited based on whether it
56 /// Decides whether a directory should be visited based on whether it
57 /// has potential matches in it or one of its subdirectories, and
57 /// has potential matches in it or one of its subdirectories, and
58 /// potentially lists which subdirectories of that directory should be
58 /// potentially lists which subdirectories of that directory should be
59 /// visited. This is based on the match's primary, included, and excluded
59 /// visited. This is based on the match's primary, included, and excluded
60 /// patterns.
60 /// patterns.
61 ///
61 ///
62 /// # Example
62 /// # Example
63 ///
63 ///
64 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
64 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
65 /// return the following values (assuming the implementation of
65 /// return the following values (assuming the implementation of
66 /// visit_children_set is capable of recognizing this; some implementations
66 /// visit_children_set is capable of recognizing this; some implementations
67 /// are not).
67 /// are not).
68 ///
68 ///
69 /// ```text
69 /// ```text
70 /// ```ignore
70 /// ```ignore
71 /// '' -> {'foo', 'qux'}
71 /// '' -> {'foo', 'qux'}
72 /// 'baz' -> set()
72 /// 'baz' -> set()
73 /// 'foo' -> {'bar'}
73 /// 'foo' -> {'bar'}
74 /// // Ideally this would be `Recursive`, but since the prefix nature of
74 /// // Ideally this would be `Recursive`, but since the prefix nature of
75 /// // matchers is applied to the entire matcher, we have to downgrade this
75 /// // matchers is applied to the entire matcher, we have to downgrade this
76 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
76 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
77 /// // `RootFilesIn'-kind matcher being mixed in.
77 /// // `RootFilesIn'-kind matcher being mixed in.
78 /// 'foo/bar' -> 'this'
78 /// 'foo/bar' -> 'this'
79 /// 'qux' -> 'this'
79 /// 'qux' -> 'this'
80 /// ```
80 /// ```
81 /// # Important
81 /// # Important
82 ///
82 ///
83 /// Most matchers do not know if they're representing files or
83 /// Most matchers do not know if they're representing files or
84 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
84 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
85 /// file or a directory, so `visit_children_set('dir')` for most matchers
85 /// file or a directory, so `visit_children_set('dir')` for most matchers
86 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
86 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
87 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
87 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
88 /// it may return `VisitChildrenSet::This`.
88 /// it may return `VisitChildrenSet::This`.
89 /// Do not rely on the return being a `HashSet` indicating that there are
89 /// Do not rely on the return being a `HashSet` indicating that there are
90 /// no files in this dir to investigate (or equivalently that if there are
90 /// no files in this dir to investigate (or equivalently that if there are
91 /// files to investigate in 'dir' that it will always return
91 /// files to investigate in 'dir' that it will always return
92 /// `VisitChildrenSet::This`).
92 /// `VisitChildrenSet::This`).
93 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet;
93 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet;
94 /// Matcher will match everything and `files_set()` will be empty:
94 /// Matcher will match everything and `files_set()` will be empty:
95 /// optimization might be possible.
95 /// optimization might be possible.
96 fn matches_everything(&self) -> bool;
96 fn matches_everything(&self) -> bool;
97 /// Matcher will match exactly the files in `files_set()`: optimization
97 /// Matcher will match exactly the files in `files_set()`: optimization
98 /// might be possible.
98 /// might be possible.
99 fn is_exact(&self) -> bool;
99 fn is_exact(&self) -> bool;
100 }
100 }
101
101
102 /// Matches everything.
102 /// Matches everything.
103 ///```
103 ///```
104 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
104 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
105 ///
105 ///
106 /// let matcher = AlwaysMatcher;
106 /// let matcher = AlwaysMatcher;
107 ///
107 ///
108 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
108 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
109 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
109 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
110 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
110 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
111 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
111 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
112 /// ```
112 /// ```
113 #[derive(Debug)]
113 #[derive(Debug)]
114 pub struct AlwaysMatcher;
114 pub struct AlwaysMatcher;
115
115
116 impl Matcher for AlwaysMatcher {
116 impl Matcher for AlwaysMatcher {
117 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
117 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
118 None
118 None
119 }
119 }
120 fn exact_match(&self, _filename: &HgPath) -> bool {
120 fn exact_match(&self, _filename: &HgPath) -> bool {
121 false
121 false
122 }
122 }
123 fn matches(&self, _filename: &HgPath) -> bool {
123 fn matches(&self, _filename: &HgPath) -> bool {
124 true
124 true
125 }
125 }
126 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
126 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
127 VisitChildrenSet::Recursive
127 VisitChildrenSet::Recursive
128 }
128 }
129 fn matches_everything(&self) -> bool {
129 fn matches_everything(&self) -> bool {
130 true
130 true
131 }
131 }
132 fn is_exact(&self) -> bool {
132 fn is_exact(&self) -> bool {
133 false
133 false
134 }
134 }
135 }
135 }
136
136
137 /// Matches nothing.
137 /// Matches nothing.
138 #[derive(Debug)]
138 #[derive(Debug)]
139 pub struct NeverMatcher;
139 pub struct NeverMatcher;
140
140
141 impl Matcher for NeverMatcher {
141 impl Matcher for NeverMatcher {
142 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
142 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
143 None
143 None
144 }
144 }
145 fn exact_match(&self, _filename: &HgPath) -> bool {
145 fn exact_match(&self, _filename: &HgPath) -> bool {
146 false
146 false
147 }
147 }
148 fn matches(&self, _filename: &HgPath) -> bool {
148 fn matches(&self, _filename: &HgPath) -> bool {
149 false
149 false
150 }
150 }
151 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
151 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
152 VisitChildrenSet::Empty
152 VisitChildrenSet::Empty
153 }
153 }
154 fn matches_everything(&self) -> bool {
154 fn matches_everything(&self) -> bool {
155 false
155 false
156 }
156 }
157 fn is_exact(&self) -> bool {
157 fn is_exact(&self) -> bool {
158 true
158 true
159 }
159 }
160 }
160 }
161
161
162 /// Matches the input files exactly. They are interpreted as paths, not
162 /// Matches the input files exactly. They are interpreted as paths, not
163 /// patterns.
163 /// patterns.
164 ///
164 ///
165 ///```
165 ///```
166 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::{HgPath, HgPathBuf} };
166 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::{HgPath, HgPathBuf} };
167 ///
167 ///
168 /// let files = vec![HgPathBuf::from_bytes(b"a.txt"), HgPathBuf::from_bytes(br"re:.*\.c$")];
168 /// let files = vec![HgPathBuf::from_bytes(b"a.txt"), HgPathBuf::from_bytes(br"re:.*\.c$")];
169 /// let matcher = FileMatcher::new(files).unwrap();
169 /// let matcher = FileMatcher::new(files).unwrap();
170 ///
170 ///
171 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
171 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
172 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
172 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
173 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
173 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
174 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
174 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
175 /// ```
175 /// ```
176 #[derive(Debug)]
176 #[derive(Debug)]
177 pub struct FileMatcher {
177 pub struct FileMatcher {
178 files: HashSet<HgPathBuf>,
178 files: HashSet<HgPathBuf>,
179 dirs: DirsMultiset,
179 dirs: DirsMultiset,
180 sorted_visitchildrenset_candidates: OnceCell<BTreeSet<HgPathBuf>>,
180 sorted_visitchildrenset_candidates: OnceCell<BTreeSet<HgPathBuf>>,
181 }
181 }
182
182
183 impl FileMatcher {
183 impl FileMatcher {
184 pub fn new(files: Vec<HgPathBuf>) -> Result<Self, HgPathError> {
184 pub fn new(files: Vec<HgPathBuf>) -> Result<Self, HgPathError> {
185 let dirs = DirsMultiset::from_manifest(&files)?;
185 let dirs = DirsMultiset::from_manifest(&files)?;
186 Ok(Self {
186 Ok(Self {
187 files: HashSet::from_iter(files),
187 files: HashSet::from_iter(files),
188 dirs,
188 dirs,
189 sorted_visitchildrenset_candidates: OnceCell::new(),
189 sorted_visitchildrenset_candidates: OnceCell::new(),
190 })
190 })
191 }
191 }
192 fn inner_matches(&self, filename: &HgPath) -> bool {
192 fn inner_matches(&self, filename: &HgPath) -> bool {
193 self.files.contains(filename.as_ref())
193 self.files.contains(filename.as_ref())
194 }
194 }
195 }
195 }
196
196
197 impl Matcher for FileMatcher {
197 impl Matcher for FileMatcher {
198 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
198 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
199 Some(&self.files)
199 Some(&self.files)
200 }
200 }
201 fn exact_match(&self, filename: &HgPath) -> bool {
201 fn exact_match(&self, filename: &HgPath) -> bool {
202 self.inner_matches(filename)
202 self.inner_matches(filename)
203 }
203 }
204 fn matches(&self, filename: &HgPath) -> bool {
204 fn matches(&self, filename: &HgPath) -> bool {
205 self.inner_matches(filename)
205 self.inner_matches(filename)
206 }
206 }
207 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
207 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
208 if self.files.is_empty() || !self.dirs.contains(directory) {
208 if self.files.is_empty() || !self.dirs.contains(directory) {
209 return VisitChildrenSet::Empty;
209 return VisitChildrenSet::Empty;
210 }
210 }
211
211
212 let compute_candidates = || -> BTreeSet<HgPathBuf> {
212 let compute_candidates = || -> BTreeSet<HgPathBuf> {
213 let mut candidates: BTreeSet<HgPathBuf> =
213 let mut candidates: BTreeSet<HgPathBuf> =
214 self.dirs.iter().cloned().collect();
214 self.dirs.iter().cloned().collect();
215 candidates.extend(self.files.iter().cloned());
215 candidates.extend(self.files.iter().cloned());
216 candidates.remove(HgPath::new(b""));
216 candidates.remove(HgPath::new(b""));
217 candidates
217 candidates
218 };
218 };
219 let candidates =
219 let candidates =
220 if directory.as_ref().is_empty() {
220 if directory.as_ref().is_empty() {
221 compute_candidates()
221 compute_candidates()
222 } else {
222 } else {
223 let sorted_candidates = self
223 let sorted_candidates = self
224 .sorted_visitchildrenset_candidates
224 .sorted_visitchildrenset_candidates
225 .get_or_init(compute_candidates);
225 .get_or_init(compute_candidates);
226 let directory_bytes = directory.as_ref().as_bytes();
226 let directory_bytes = directory.as_ref().as_bytes();
227 let start: HgPathBuf =
227 let start: HgPathBuf =
228 format_bytes!(b"{}/", directory_bytes).into();
228 format_bytes!(b"{}/", directory_bytes).into();
229 let start_len = start.len();
229 let start_len = start.len();
230 // `0` sorts after `/`
230 // `0` sorts after `/`
231 let end = format_bytes!(b"{}0", directory_bytes).into();
231 let end = format_bytes!(b"{}0", directory_bytes).into();
232 BTreeSet::from_iter(sorted_candidates.range(start..end).map(
232 BTreeSet::from_iter(sorted_candidates.range(start..end).map(
233 |c| HgPathBuf::from_bytes(&c.as_bytes()[start_len..]),
233 |c| HgPathBuf::from_bytes(&c.as_bytes()[start_len..]),
234 ))
234 ))
235 };
235 };
236
236
237 // `self.dirs` includes all of the directories, recursively, so if
237 // `self.dirs` includes all of the directories, recursively, so if
238 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
238 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
239 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
239 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
240 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
240 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
241 // subdir will be in there without a slash.
241 // subdir will be in there without a slash.
242 VisitChildrenSet::Set(
242 VisitChildrenSet::Set(
243 candidates
243 candidates
244 .into_iter()
244 .into_iter()
245 .filter_map(|c| {
245 .filter_map(|c| {
246 if c.bytes().all(|b| *b != b'/') {
246 if c.bytes().all(|b| *b != b'/') {
247 Some(c)
247 Some(c)
248 } else {
248 } else {
249 None
249 None
250 }
250 }
251 })
251 })
252 .collect(),
252 .collect(),
253 )
253 )
254 }
254 }
255 fn matches_everything(&self) -> bool {
255 fn matches_everything(&self) -> bool {
256 false
256 false
257 }
257 }
258 fn is_exact(&self) -> bool {
258 fn is_exact(&self) -> bool {
259 true
259 true
260 }
260 }
261 }
261 }
262
262
263 /// Matches a set of (kind, pat, source) against a 'root' directory.
263 /// Matches a set of (kind, pat, source) against a 'root' directory.
264 /// (Currently the 'root' directory is effectively always empty)
264 /// (Currently the 'root' directory is effectively always empty)
265 /// ```
265 /// ```
266 /// use hg::{
266 /// use hg::{
267 /// matchers::{PatternMatcher, Matcher},
267 /// matchers::{PatternMatcher, Matcher},
268 /// IgnorePattern,
268 /// IgnorePattern,
269 /// PatternSyntax,
269 /// PatternSyntax,
270 /// utils::hg_path::{HgPath, HgPathBuf}
270 /// utils::hg_path::{HgPath, HgPathBuf}
271 /// };
271 /// };
272 /// use std::collections::HashSet;
272 /// use std::collections::HashSet;
273 /// use std::path::Path;
273 /// use std::path::Path;
274 /// ///
274 /// ///
275 /// let ignore_patterns : Vec<IgnorePattern> =
275 /// let ignore_patterns : Vec<IgnorePattern> =
276 /// vec![IgnorePattern::new(PatternSyntax::Regexp, br".*\.c$", Path::new("")),
276 /// vec![IgnorePattern::new(PatternSyntax::Regexp, br".*\.c$", Path::new("")),
277 /// IgnorePattern::new(PatternSyntax::Path, b"foo/a", Path::new("")),
277 /// IgnorePattern::new(PatternSyntax::Path, b"foo/a", Path::new("")),
278 /// IgnorePattern::new(PatternSyntax::RelPath, b"b", Path::new("")),
278 /// IgnorePattern::new(PatternSyntax::RelPath, b"b", Path::new("")),
279 /// IgnorePattern::new(PatternSyntax::Glob, b"*.h", Path::new("")),
279 /// IgnorePattern::new(PatternSyntax::Glob, b"*.h", Path::new("")),
280 /// ];
280 /// ];
281 /// let matcher = PatternMatcher::new(ignore_patterns).unwrap();
281 /// let matcher = PatternMatcher::new(ignore_patterns).unwrap();
282 /// ///
282 /// ///
283 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true); // matches re:.*\.c$
283 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true); // matches re:.*\.c$
284 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
284 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
285 /// assert_eq!(matcher.matches(HgPath::new(b"foo/a")), true); // matches path:foo/a
285 /// assert_eq!(matcher.matches(HgPath::new(b"foo/a")), true); // matches path:foo/a
286 /// assert_eq!(matcher.matches(HgPath::new(b"a")), false); // does not match path:b, since 'root' is 'foo'
286 /// assert_eq!(matcher.matches(HgPath::new(b"a")), false); // does not match path:b, since 'root' is 'foo'
287 /// assert_eq!(matcher.matches(HgPath::new(b"b")), true); // matches relpath:b, since 'root' is 'foo'
287 /// assert_eq!(matcher.matches(HgPath::new(b"b")), true); // matches relpath:b, since 'root' is 'foo'
288 /// assert_eq!(matcher.matches(HgPath::new(b"lib.h")), true); // matches glob:*.h
288 /// assert_eq!(matcher.matches(HgPath::new(b"lib.h")), true); // matches glob:*.h
289 /// assert_eq!(matcher.file_set().unwrap(),
289 /// assert_eq!(matcher.file_set().unwrap(),
290 /// &HashSet::from([HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"foo/a"),
290 /// &HashSet::from([HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"foo/a"),
291 /// HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"b")]));
291 /// HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"b")]));
292 /// assert_eq!(matcher.exact_match(HgPath::new(b"foo/a")), true);
292 /// assert_eq!(matcher.exact_match(HgPath::new(b"foo/a")), true);
293 /// assert_eq!(matcher.exact_match(HgPath::new(b"b")), true);
293 /// assert_eq!(matcher.exact_match(HgPath::new(b"b")), true);
294 /// assert_eq!(matcher.exact_match(HgPath::new(b"lib.h")), false); // exact matches are for (rel)path kinds
294 /// assert_eq!(matcher.exact_match(HgPath::new(b"lib.h")), false); // exact matches are for (rel)path kinds
295 /// ```
295 /// ```
296 pub struct PatternMatcher<'a> {
296 pub struct PatternMatcher<'a> {
297 patterns: Vec<u8>,
297 patterns: Vec<u8>,
298 match_fn: IgnoreFnType<'a>,
298 match_fn: IgnoreFnType<'a>,
299 /// Whether all the patterns match a prefix (i.e. recursively)
299 /// Whether all the patterns match a prefix (i.e. recursively)
300 prefix: bool,
300 prefix: bool,
301 files: HashSet<HgPathBuf>,
301 files: HashSet<HgPathBuf>,
302 dirs: DirsMultiset,
302 dirs: DirsMultiset,
303 }
303 }
304
304
305 impl core::fmt::Debug for PatternMatcher<'_> {
305 impl core::fmt::Debug for PatternMatcher<'_> {
306 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
306 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
307 f.debug_struct("PatternMatcher")
307 f.debug_struct("PatternMatcher")
308 .field("patterns", &String::from_utf8_lossy(&self.patterns))
308 .field("patterns", &String::from_utf8_lossy(&self.patterns))
309 .field("prefix", &self.prefix)
309 .field("prefix", &self.prefix)
310 .field("files", &self.files)
310 .field("files", &self.files)
311 .field("dirs", &self.dirs)
311 .field("dirs", &self.dirs)
312 .finish()
312 .finish()
313 }
313 }
314 }
314 }
315
315
316 impl<'a> PatternMatcher<'a> {
316 impl<'a> PatternMatcher<'a> {
317 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
317 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
318 let (files, _) = roots_and_dirs(&ignore_patterns);
318 let (files, _) = roots_and_dirs(&ignore_patterns);
319 let dirs = DirsMultiset::from_manifest(&files)?;
319 let dirs = DirsMultiset::from_manifest(&files)?;
320 let files: HashSet<HgPathBuf> = HashSet::from_iter(files);
320 let files: HashSet<HgPathBuf> = HashSet::from_iter(files);
321
321
322 let prefix = ignore_patterns.iter().all(|k| {
322 let prefix = ignore_patterns.iter().all(|k| {
323 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
323 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
324 });
324 });
325 let (patterns, match_fn) = build_match(ignore_patterns, b"$")?;
325 let (patterns, match_fn) = build_match(ignore_patterns, b"$")?;
326
326
327 Ok(Self {
327 Ok(Self {
328 patterns,
328 patterns,
329 match_fn,
329 match_fn,
330 prefix,
330 prefix,
331 files,
331 files,
332 dirs,
332 dirs,
333 })
333 })
334 }
334 }
335 }
335 }
336
336
337 impl<'a> Matcher for PatternMatcher<'a> {
337 impl<'a> Matcher for PatternMatcher<'a> {
338 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
338 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
339 Some(&self.files)
339 Some(&self.files)
340 }
340 }
341
341
342 fn exact_match(&self, filename: &HgPath) -> bool {
342 fn exact_match(&self, filename: &HgPath) -> bool {
343 self.files.contains(filename)
343 self.files.contains(filename)
344 }
344 }
345
345
346 fn matches(&self, filename: &HgPath) -> bool {
346 fn matches(&self, filename: &HgPath) -> bool {
347 if self.files.contains(filename) {
347 if self.files.contains(filename) {
348 return true;
348 return true;
349 }
349 }
350 (self.match_fn)(filename)
350 (self.match_fn)(filename)
351 }
351 }
352
352
353 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
353 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
354 if self.prefix && self.files.contains(directory) {
354 if self.prefix && self.files.contains(directory) {
355 return VisitChildrenSet::Recursive;
355 return VisitChildrenSet::Recursive;
356 }
356 }
357 let path_or_parents_in_set = find_dirs(directory)
357 let path_or_parents_in_set = dir_ancestors(directory)
358 .any(|parent_dir| self.files.contains(parent_dir));
358 .any(|parent_dir| self.files.contains(parent_dir));
359 if self.dirs.contains(directory) || path_or_parents_in_set {
359 if self.dirs.contains(directory) || path_or_parents_in_set {
360 VisitChildrenSet::This
360 VisitChildrenSet::This
361 } else {
361 } else {
362 VisitChildrenSet::Empty
362 VisitChildrenSet::Empty
363 }
363 }
364 }
364 }
365
365
366 fn matches_everything(&self) -> bool {
366 fn matches_everything(&self) -> bool {
367 false
367 false
368 }
368 }
369
369
370 fn is_exact(&self) -> bool {
370 fn is_exact(&self) -> bool {
371 false
371 false
372 }
372 }
373 }
373 }
374
374
375 /// Matches files that are included in the ignore rules.
375 /// Matches files that are included in the ignore rules.
376 /// ```
376 /// ```
377 /// use hg::{
377 /// use hg::{
378 /// matchers::{IncludeMatcher, Matcher},
378 /// matchers::{IncludeMatcher, Matcher},
379 /// IgnorePattern,
379 /// IgnorePattern,
380 /// PatternSyntax,
380 /// PatternSyntax,
381 /// utils::hg_path::HgPath
381 /// utils::hg_path::HgPath
382 /// };
382 /// };
383 /// use std::path::Path;
383 /// use std::path::Path;
384 /// ///
384 /// ///
385 /// let ignore_patterns =
385 /// let ignore_patterns =
386 /// vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
386 /// vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
387 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
387 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
388 /// ///
388 /// ///
389 /// assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
389 /// assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
390 /// assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
390 /// assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
391 /// assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
391 /// assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
392 /// assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
392 /// assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
393 /// ///
393 /// ///
394 /// let ignore_patterns =
394 /// let ignore_patterns =
395 /// vec![IgnorePattern::new(PatternSyntax::RootFiles, b"dir/subdir", Path::new(""))];
395 /// vec![IgnorePattern::new(PatternSyntax::RootFiles, b"dir/subdir", Path::new(""))];
396 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
396 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
397 /// ///
397 /// ///
398 /// assert!(!matcher.matches(HgPath::new(b"file")));
398 /// assert!(!matcher.matches(HgPath::new(b"file")));
399 /// assert!(!matcher.matches(HgPath::new(b"dir/file")));
399 /// assert!(!matcher.matches(HgPath::new(b"dir/file")));
400 /// assert!(matcher.matches(HgPath::new(b"dir/subdir/file")));
400 /// assert!(matcher.matches(HgPath::new(b"dir/subdir/file")));
401 /// assert!(!matcher.matches(HgPath::new(b"dir/subdir/subsubdir/file")));
401 /// assert!(!matcher.matches(HgPath::new(b"dir/subdir/subsubdir/file")));
402 /// ```
402 /// ```
403 pub struct IncludeMatcher<'a> {
403 pub struct IncludeMatcher<'a> {
404 patterns: Vec<u8>,
404 patterns: Vec<u8>,
405 match_fn: IgnoreFnType<'a>,
405 match_fn: IgnoreFnType<'a>,
406 /// Whether all the patterns match a prefix (i.e. recursively)
406 /// Whether all the patterns match a prefix (i.e. recursively)
407 prefix: bool,
407 prefix: bool,
408 roots: HashSet<HgPathBuf>,
408 roots: HashSet<HgPathBuf>,
409 dirs: HashSet<HgPathBuf>,
409 dirs: HashSet<HgPathBuf>,
410 parents: HashSet<HgPathBuf>,
410 parents: HashSet<HgPathBuf>,
411 }
411 }
412
412
413 impl core::fmt::Debug for IncludeMatcher<'_> {
413 impl core::fmt::Debug for IncludeMatcher<'_> {
414 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
414 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
415 f.debug_struct("IncludeMatcher")
415 f.debug_struct("IncludeMatcher")
416 .field("patterns", &String::from_utf8_lossy(&self.patterns))
416 .field("patterns", &String::from_utf8_lossy(&self.patterns))
417 .field("prefix", &self.prefix)
417 .field("prefix", &self.prefix)
418 .field("roots", &self.roots)
418 .field("roots", &self.roots)
419 .field("dirs", &self.dirs)
419 .field("dirs", &self.dirs)
420 .field("parents", &self.parents)
420 .field("parents", &self.parents)
421 .finish()
421 .finish()
422 }
422 }
423 }
423 }
424
424
425 impl<'a> Matcher for IncludeMatcher<'a> {
425 impl<'a> Matcher for IncludeMatcher<'a> {
426 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
426 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
427 None
427 None
428 }
428 }
429
429
430 fn exact_match(&self, _filename: &HgPath) -> bool {
430 fn exact_match(&self, _filename: &HgPath) -> bool {
431 false
431 false
432 }
432 }
433
433
434 fn matches(&self, filename: &HgPath) -> bool {
434 fn matches(&self, filename: &HgPath) -> bool {
435 (self.match_fn)(filename)
435 (self.match_fn)(filename)
436 }
436 }
437
437
438 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
438 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
439 let dir = directory;
439 let dir = directory;
440 if self.prefix && self.roots.contains(dir) {
440 if self.prefix && self.roots.contains(dir) {
441 return VisitChildrenSet::Recursive;
441 return VisitChildrenSet::Recursive;
442 }
442 }
443 if self.roots.contains(HgPath::new(b""))
443 if self.roots.contains(HgPath::new(b""))
444 || self.roots.contains(dir)
444 || self.roots.contains(dir)
445 || self.dirs.contains(dir)
445 || self.dirs.contains(dir)
446 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
446 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
447 {
447 {
448 return VisitChildrenSet::This;
448 return VisitChildrenSet::This;
449 }
449 }
450
450
451 if self.parents.contains(dir.as_ref()) {
451 if self.parents.contains(dir.as_ref()) {
452 let multiset = self.get_all_parents_children();
452 let multiset = self.get_all_parents_children();
453 if let Some(children) = multiset.get(dir) {
453 if let Some(children) = multiset.get(dir) {
454 return VisitChildrenSet::Set(
454 return VisitChildrenSet::Set(
455 children.iter().map(HgPathBuf::from).collect(),
455 children.iter().map(HgPathBuf::from).collect(),
456 );
456 );
457 }
457 }
458 }
458 }
459 VisitChildrenSet::Empty
459 VisitChildrenSet::Empty
460 }
460 }
461
461
462 fn matches_everything(&self) -> bool {
462 fn matches_everything(&self) -> bool {
463 false
463 false
464 }
464 }
465
465
466 fn is_exact(&self) -> bool {
466 fn is_exact(&self) -> bool {
467 false
467 false
468 }
468 }
469 }
469 }
470
470
471 /// The union of multiple matchers. Will match if any of the matchers match.
471 /// The union of multiple matchers. Will match if any of the matchers match.
472 #[derive(Debug)]
472 #[derive(Debug)]
473 pub struct UnionMatcher {
473 pub struct UnionMatcher {
474 matchers: Vec<Box<dyn Matcher + Sync>>,
474 matchers: Vec<Box<dyn Matcher + Sync>>,
475 }
475 }
476
476
477 impl Matcher for UnionMatcher {
477 impl Matcher for UnionMatcher {
478 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
478 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
479 None
479 None
480 }
480 }
481
481
482 fn exact_match(&self, _filename: &HgPath) -> bool {
482 fn exact_match(&self, _filename: &HgPath) -> bool {
483 false
483 false
484 }
484 }
485
485
486 fn matches(&self, filename: &HgPath) -> bool {
486 fn matches(&self, filename: &HgPath) -> bool {
487 self.matchers.iter().any(|m| m.matches(filename))
487 self.matchers.iter().any(|m| m.matches(filename))
488 }
488 }
489
489
490 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
490 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
491 let mut result = HashSet::new();
491 let mut result = HashSet::new();
492 let mut this = false;
492 let mut this = false;
493 for matcher in self.matchers.iter() {
493 for matcher in self.matchers.iter() {
494 let visit = matcher.visit_children_set(directory);
494 let visit = matcher.visit_children_set(directory);
495 match visit {
495 match visit {
496 VisitChildrenSet::Empty => continue,
496 VisitChildrenSet::Empty => continue,
497 VisitChildrenSet::This => {
497 VisitChildrenSet::This => {
498 this = true;
498 this = true;
499 // Don't break, we might have an 'all' in here.
499 // Don't break, we might have an 'all' in here.
500 continue;
500 continue;
501 }
501 }
502 VisitChildrenSet::Set(set) => {
502 VisitChildrenSet::Set(set) => {
503 result.extend(set);
503 result.extend(set);
504 }
504 }
505 VisitChildrenSet::Recursive => {
505 VisitChildrenSet::Recursive => {
506 return visit;
506 return visit;
507 }
507 }
508 }
508 }
509 }
509 }
510 if this {
510 if this {
511 return VisitChildrenSet::This;
511 return VisitChildrenSet::This;
512 }
512 }
513 if result.is_empty() {
513 if result.is_empty() {
514 VisitChildrenSet::Empty
514 VisitChildrenSet::Empty
515 } else {
515 } else {
516 VisitChildrenSet::Set(result)
516 VisitChildrenSet::Set(result)
517 }
517 }
518 }
518 }
519
519
520 fn matches_everything(&self) -> bool {
520 fn matches_everything(&self) -> bool {
521 // TODO Maybe if all are AlwaysMatcher?
521 // TODO Maybe if all are AlwaysMatcher?
522 false
522 false
523 }
523 }
524
524
525 fn is_exact(&self) -> bool {
525 fn is_exact(&self) -> bool {
526 false
526 false
527 }
527 }
528 }
528 }
529
529
530 impl UnionMatcher {
530 impl UnionMatcher {
531 pub fn new(matchers: Vec<Box<dyn Matcher + Sync>>) -> Self {
531 pub fn new(matchers: Vec<Box<dyn Matcher + Sync>>) -> Self {
532 Self { matchers }
532 Self { matchers }
533 }
533 }
534 }
534 }
535
535
536 #[derive(Debug)]
536 #[derive(Debug)]
537 pub struct IntersectionMatcher {
537 pub struct IntersectionMatcher {
538 m1: Box<dyn Matcher + Sync>,
538 m1: Box<dyn Matcher + Sync>,
539 m2: Box<dyn Matcher + Sync>,
539 m2: Box<dyn Matcher + Sync>,
540 files: Option<HashSet<HgPathBuf>>,
540 files: Option<HashSet<HgPathBuf>>,
541 }
541 }
542
542
543 impl Matcher for IntersectionMatcher {
543 impl Matcher for IntersectionMatcher {
544 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
544 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
545 self.files.as_ref()
545 self.files.as_ref()
546 }
546 }
547
547
548 fn exact_match(&self, filename: &HgPath) -> bool {
548 fn exact_match(&self, filename: &HgPath) -> bool {
549 self.files.as_ref().map_or(false, |f| f.contains(filename))
549 self.files.as_ref().map_or(false, |f| f.contains(filename))
550 }
550 }
551
551
552 fn matches(&self, filename: &HgPath) -> bool {
552 fn matches(&self, filename: &HgPath) -> bool {
553 self.m1.matches(filename) && self.m2.matches(filename)
553 self.m1.matches(filename) && self.m2.matches(filename)
554 }
554 }
555
555
556 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
556 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
557 let m1_set = self.m1.visit_children_set(directory);
557 let m1_set = self.m1.visit_children_set(directory);
558 if m1_set == VisitChildrenSet::Empty {
558 if m1_set == VisitChildrenSet::Empty {
559 return VisitChildrenSet::Empty;
559 return VisitChildrenSet::Empty;
560 }
560 }
561 let m2_set = self.m2.visit_children_set(directory);
561 let m2_set = self.m2.visit_children_set(directory);
562 if m2_set == VisitChildrenSet::Empty {
562 if m2_set == VisitChildrenSet::Empty {
563 return VisitChildrenSet::Empty;
563 return VisitChildrenSet::Empty;
564 }
564 }
565
565
566 if m1_set == VisitChildrenSet::Recursive {
566 if m1_set == VisitChildrenSet::Recursive {
567 return m2_set;
567 return m2_set;
568 } else if m2_set == VisitChildrenSet::Recursive {
568 } else if m2_set == VisitChildrenSet::Recursive {
569 return m1_set;
569 return m1_set;
570 }
570 }
571
571
572 match (&m1_set, &m2_set) {
572 match (&m1_set, &m2_set) {
573 (VisitChildrenSet::Recursive, _) => m2_set,
573 (VisitChildrenSet::Recursive, _) => m2_set,
574 (_, VisitChildrenSet::Recursive) => m1_set,
574 (_, VisitChildrenSet::Recursive) => m1_set,
575 (VisitChildrenSet::This, _) | (_, VisitChildrenSet::This) => {
575 (VisitChildrenSet::This, _) | (_, VisitChildrenSet::This) => {
576 VisitChildrenSet::This
576 VisitChildrenSet::This
577 }
577 }
578 (VisitChildrenSet::Set(m1), VisitChildrenSet::Set(m2)) => {
578 (VisitChildrenSet::Set(m1), VisitChildrenSet::Set(m2)) => {
579 let set: HashSet<_> = m1.intersection(m2).cloned().collect();
579 let set: HashSet<_> = m1.intersection(m2).cloned().collect();
580 if set.is_empty() {
580 if set.is_empty() {
581 VisitChildrenSet::Empty
581 VisitChildrenSet::Empty
582 } else {
582 } else {
583 VisitChildrenSet::Set(set)
583 VisitChildrenSet::Set(set)
584 }
584 }
585 }
585 }
586 _ => unreachable!(),
586 _ => unreachable!(),
587 }
587 }
588 }
588 }
589
589
590 fn matches_everything(&self) -> bool {
590 fn matches_everything(&self) -> bool {
591 self.m1.matches_everything() && self.m2.matches_everything()
591 self.m1.matches_everything() && self.m2.matches_everything()
592 }
592 }
593
593
594 fn is_exact(&self) -> bool {
594 fn is_exact(&self) -> bool {
595 self.m1.is_exact() || self.m2.is_exact()
595 self.m1.is_exact() || self.m2.is_exact()
596 }
596 }
597 }
597 }
598
598
599 impl IntersectionMatcher {
599 impl IntersectionMatcher {
600 pub fn new(
600 pub fn new(
601 mut m1: Box<dyn Matcher + Sync>,
601 mut m1: Box<dyn Matcher + Sync>,
602 mut m2: Box<dyn Matcher + Sync>,
602 mut m2: Box<dyn Matcher + Sync>,
603 ) -> Self {
603 ) -> Self {
604 let files = if m1.is_exact() || m2.is_exact() {
604 let files = if m1.is_exact() || m2.is_exact() {
605 if !m1.is_exact() {
605 if !m1.is_exact() {
606 std::mem::swap(&mut m1, &mut m2);
606 std::mem::swap(&mut m1, &mut m2);
607 }
607 }
608 m1.file_set().map(|m1_files| {
608 m1.file_set().map(|m1_files| {
609 m1_files.iter().cloned().filter(|f| m2.matches(f)).collect()
609 m1_files.iter().cloned().filter(|f| m2.matches(f)).collect()
610 })
610 })
611 } else {
611 } else {
612 // without exact input file sets, we can't do an exact
612 // without exact input file sets, we can't do an exact
613 // intersection, so we must over-approximate by
613 // intersection, so we must over-approximate by
614 // unioning instead
614 // unioning instead
615 m1.file_set().map(|m1_files| match m2.file_set() {
615 m1.file_set().map(|m1_files| match m2.file_set() {
616 Some(m2_files) => m1_files.union(m2_files).cloned().collect(),
616 Some(m2_files) => m1_files.union(m2_files).cloned().collect(),
617 None => m1_files.iter().cloned().collect(),
617 None => m1_files.iter().cloned().collect(),
618 })
618 })
619 };
619 };
620 Self { m1, m2, files }
620 Self { m1, m2, files }
621 }
621 }
622 }
622 }
623
623
624 #[derive(Debug)]
624 #[derive(Debug)]
625 pub struct DifferenceMatcher {
625 pub struct DifferenceMatcher {
626 base: Box<dyn Matcher + Sync>,
626 base: Box<dyn Matcher + Sync>,
627 excluded: Box<dyn Matcher + Sync>,
627 excluded: Box<dyn Matcher + Sync>,
628 files: Option<HashSet<HgPathBuf>>,
628 files: Option<HashSet<HgPathBuf>>,
629 }
629 }
630
630
631 impl Matcher for DifferenceMatcher {
631 impl Matcher for DifferenceMatcher {
632 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
632 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
633 self.files.as_ref()
633 self.files.as_ref()
634 }
634 }
635
635
636 fn exact_match(&self, filename: &HgPath) -> bool {
636 fn exact_match(&self, filename: &HgPath) -> bool {
637 self.files.as_ref().map_or(false, |f| f.contains(filename))
637 self.files.as_ref().map_or(false, |f| f.contains(filename))
638 }
638 }
639
639
640 fn matches(&self, filename: &HgPath) -> bool {
640 fn matches(&self, filename: &HgPath) -> bool {
641 self.base.matches(filename) && !self.excluded.matches(filename)
641 self.base.matches(filename) && !self.excluded.matches(filename)
642 }
642 }
643
643
644 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
644 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
645 let excluded_set = self.excluded.visit_children_set(directory);
645 let excluded_set = self.excluded.visit_children_set(directory);
646 if excluded_set == VisitChildrenSet::Recursive {
646 if excluded_set == VisitChildrenSet::Recursive {
647 return VisitChildrenSet::Empty;
647 return VisitChildrenSet::Empty;
648 }
648 }
649 let base_set = self.base.visit_children_set(directory);
649 let base_set = self.base.visit_children_set(directory);
650 // Possible values for base: 'recursive', 'this', set(...), set()
650 // Possible values for base: 'recursive', 'this', set(...), set()
651 // Possible values for excluded: 'this', set(...), set()
651 // Possible values for excluded: 'this', set(...), set()
652 // If excluded has nothing under here that we care about, return base,
652 // If excluded has nothing under here that we care about, return base,
653 // even if it's 'recursive'.
653 // even if it's 'recursive'.
654 if excluded_set == VisitChildrenSet::Empty {
654 if excluded_set == VisitChildrenSet::Empty {
655 return base_set;
655 return base_set;
656 }
656 }
657 match base_set {
657 match base_set {
658 VisitChildrenSet::This | VisitChildrenSet::Recursive => {
658 VisitChildrenSet::This | VisitChildrenSet::Recursive => {
659 // Never return 'recursive' here if excluded_set is any kind of
659 // Never return 'recursive' here if excluded_set is any kind of
660 // non-empty (either 'this' or set(foo)), since excluded might
660 // non-empty (either 'this' or set(foo)), since excluded might
661 // return set() for a subdirectory.
661 // return set() for a subdirectory.
662 VisitChildrenSet::This
662 VisitChildrenSet::This
663 }
663 }
664 set => {
664 set => {
665 // Possible values for base: set(...), set()
665 // Possible values for base: set(...), set()
666 // Possible values for excluded: 'this', set(...)
666 // Possible values for excluded: 'this', set(...)
667 // We ignore excluded set results. They're possibly incorrect:
667 // We ignore excluded set results. They're possibly incorrect:
668 // base = path:dir/subdir
668 // base = path:dir/subdir
669 // excluded=rootfilesin:dir,
669 // excluded=rootfilesin:dir,
670 // visit_children_set(''):
670 // visit_children_set(''):
671 // base returns {'dir'}, excluded returns {'dir'}, if we
671 // base returns {'dir'}, excluded returns {'dir'}, if we
672 // subtracted we'd return set(), which is *not* correct, we
672 // subtracted we'd return set(), which is *not* correct, we
673 // still need to visit 'dir'!
673 // still need to visit 'dir'!
674 set
674 set
675 }
675 }
676 }
676 }
677 }
677 }
678
678
679 fn matches_everything(&self) -> bool {
679 fn matches_everything(&self) -> bool {
680 false
680 false
681 }
681 }
682
682
683 fn is_exact(&self) -> bool {
683 fn is_exact(&self) -> bool {
684 self.base.is_exact()
684 self.base.is_exact()
685 }
685 }
686 }
686 }
687
687
688 impl DifferenceMatcher {
688 impl DifferenceMatcher {
689 pub fn new(
689 pub fn new(
690 base: Box<dyn Matcher + Sync>,
690 base: Box<dyn Matcher + Sync>,
691 excluded: Box<dyn Matcher + Sync>,
691 excluded: Box<dyn Matcher + Sync>,
692 ) -> Self {
692 ) -> Self {
693 let base_is_exact = base.is_exact();
693 let base_is_exact = base.is_exact();
694 let base_files = base.file_set().map(ToOwned::to_owned);
694 let base_files = base.file_set().map(ToOwned::to_owned);
695 let mut new = Self {
695 let mut new = Self {
696 base,
696 base,
697 excluded,
697 excluded,
698 files: None,
698 files: None,
699 };
699 };
700 if base_is_exact {
700 if base_is_exact {
701 new.files = base_files.map(|files| {
701 new.files = base_files.map(|files| {
702 files.iter().cloned().filter(|f| new.matches(f)).collect()
702 files.iter().cloned().filter(|f| new.matches(f)).collect()
703 });
703 });
704 }
704 }
705 new
705 new
706 }
706 }
707 }
707 }
708
708
709 /// Wraps [`regex::bytes::Regex`] to improve performance in multithreaded
709 /// Wraps [`regex::bytes::Regex`] to improve performance in multithreaded
710 /// contexts.
710 /// contexts.
711 ///
711 ///
712 /// The `status` algorithm makes heavy use of threads, and calling `is_match`
712 /// The `status` algorithm makes heavy use of threads, and calling `is_match`
713 /// from many threads at once is prone to contention, probably within the
713 /// from many threads at once is prone to contention, probably within the
714 /// scratch space needed as the regex DFA is built lazily.
714 /// scratch space needed as the regex DFA is built lazily.
715 ///
715 ///
716 /// We are in the process of raising the issue upstream, but for now
716 /// We are in the process of raising the issue upstream, but for now
717 /// the workaround used here is to store the `Regex` in a lazily populated
717 /// the workaround used here is to store the `Regex` in a lazily populated
718 /// thread-local variable, sharing the initial read-only compilation, but
718 /// thread-local variable, sharing the initial read-only compilation, but
719 /// not the lazy dfa scratch space mentioned above.
719 /// not the lazy dfa scratch space mentioned above.
720 ///
720 ///
721 /// This reduces the contention observed with 16+ threads, but does not
721 /// This reduces the contention observed with 16+ threads, but does not
722 /// completely remove it. Hopefully this can be addressed upstream.
722 /// completely remove it. Hopefully this can be addressed upstream.
723 struct RegexMatcher {
723 struct RegexMatcher {
724 /// Compiled at the start of the status algorithm, used as a base for
724 /// Compiled at the start of the status algorithm, used as a base for
725 /// cloning in each thread-local `self.local`, thus sharing the expensive
725 /// cloning in each thread-local `self.local`, thus sharing the expensive
726 /// first compilation.
726 /// first compilation.
727 base: regex::bytes::Regex,
727 base: regex::bytes::Regex,
728 /// Thread-local variable that holds the `Regex` that is actually queried
728 /// Thread-local variable that holds the `Regex` that is actually queried
729 /// from each thread.
729 /// from each thread.
730 local: thread_local::ThreadLocal<regex::bytes::Regex>,
730 local: thread_local::ThreadLocal<regex::bytes::Regex>,
731 }
731 }
732
732
733 impl RegexMatcher {
733 impl RegexMatcher {
734 /// Returns whether the path matches the stored `Regex`.
734 /// Returns whether the path matches the stored `Regex`.
735 pub fn is_match(&self, path: &HgPath) -> bool {
735 pub fn is_match(&self, path: &HgPath) -> bool {
736 self.local
736 self.local
737 .get_or(|| self.base.clone())
737 .get_or(|| self.base.clone())
738 .is_match(path.as_bytes())
738 .is_match(path.as_bytes())
739 }
739 }
740 }
740 }
741
741
742 /// Return a `RegexBuilder` from a bytes pattern
742 /// Return a `RegexBuilder` from a bytes pattern
743 ///
743 ///
744 /// This works around the fact that even if it works on byte haysacks,
744 /// This works around the fact that even if it works on byte haysacks,
745 /// [`regex::bytes::Regex`] still uses UTF-8 patterns.
745 /// [`regex::bytes::Regex`] still uses UTF-8 patterns.
746 pub fn re_bytes_builder(pattern: &[u8]) -> regex::bytes::RegexBuilder {
746 pub fn re_bytes_builder(pattern: &[u8]) -> regex::bytes::RegexBuilder {
747 use std::io::Write;
747 use std::io::Write;
748
748
749 // The `regex` crate adds `.*` to the start and end of expressions if there
749 // The `regex` crate adds `.*` to the start and end of expressions if there
750 // are no anchors, so add the start anchor.
750 // are no anchors, so add the start anchor.
751 let mut escaped_bytes = vec![b'^', b'(', b'?', b':'];
751 let mut escaped_bytes = vec![b'^', b'(', b'?', b':'];
752 for byte in pattern {
752 for byte in pattern {
753 if *byte > 127 {
753 if *byte > 127 {
754 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
754 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
755 } else {
755 } else {
756 escaped_bytes.push(*byte);
756 escaped_bytes.push(*byte);
757 }
757 }
758 }
758 }
759 escaped_bytes.push(b')');
759 escaped_bytes.push(b')');
760
760
761 // Avoid the cost of UTF8 checking
761 // Avoid the cost of UTF8 checking
762 //
762 //
763 // # Safety
763 // # Safety
764 // This is safe because we escaped all non-ASCII bytes.
764 // This is safe because we escaped all non-ASCII bytes.
765 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
765 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
766 regex::bytes::RegexBuilder::new(&pattern_string)
766 regex::bytes::RegexBuilder::new(&pattern_string)
767 }
767 }
768
768
769 /// Returns a function that matches an `HgPath` against the given regex
769 /// Returns a function that matches an `HgPath` against the given regex
770 /// pattern.
770 /// pattern.
771 ///
771 ///
772 /// This can fail when the pattern is invalid or not supported by the
772 /// This can fail when the pattern is invalid or not supported by the
773 /// underlying engine (the `regex` crate), for instance anything with
773 /// underlying engine (the `regex` crate), for instance anything with
774 /// back-references.
774 /// back-references.
775 #[logging_timer::time("trace")]
775 #[logging_timer::time("trace")]
776 fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
776 fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
777 let re = re_bytes_builder(pattern)
777 let re = re_bytes_builder(pattern)
778 .unicode(false)
778 .unicode(false)
779 // Big repos with big `.hgignore` will hit the default limit and
779 // Big repos with big `.hgignore` will hit the default limit and
780 // incur a significant performance hit. One repo's `hg status` hit
780 // incur a significant performance hit. One repo's `hg status` hit
781 // multiple *minutes*.
781 // multiple *minutes*.
782 .dfa_size_limit(50 * (1 << 20))
782 .dfa_size_limit(50 * (1 << 20))
783 .build()
783 .build()
784 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
784 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
785
785
786 Ok(RegexMatcher {
786 Ok(RegexMatcher {
787 base: re,
787 base: re,
788 local: Default::default(),
788 local: Default::default(),
789 })
789 })
790 }
790 }
791
791
792 /// Returns the regex pattern and a function that matches an `HgPath` against
792 /// Returns the regex pattern and a function that matches an `HgPath` against
793 /// said regex formed by the given ignore patterns.
793 /// said regex formed by the given ignore patterns.
794 fn build_regex_match<'a>(
794 fn build_regex_match<'a>(
795 ignore_patterns: &[IgnorePattern],
795 ignore_patterns: &[IgnorePattern],
796 glob_suffix: &[u8],
796 glob_suffix: &[u8],
797 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
797 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
798 let mut regexps = vec![];
798 let mut regexps = vec![];
799 let mut exact_set = HashSet::new();
799 let mut exact_set = HashSet::new();
800
800
801 for pattern in ignore_patterns {
801 for pattern in ignore_patterns {
802 if let Some(re) = build_single_regex(pattern, glob_suffix)? {
802 if let Some(re) = build_single_regex(pattern, glob_suffix)? {
803 regexps.push(re);
803 regexps.push(re);
804 } else {
804 } else {
805 let exact = normalize_path_bytes(&pattern.pattern);
805 let exact = normalize_path_bytes(&pattern.pattern);
806 exact_set.insert(HgPathBuf::from_bytes(&exact));
806 exact_set.insert(HgPathBuf::from_bytes(&exact));
807 }
807 }
808 }
808 }
809
809
810 let full_regex = regexps.join(&b'|');
810 let full_regex = regexps.join(&b'|');
811
811
812 // An empty pattern would cause the regex engine to incorrectly match the
812 // An empty pattern would cause the regex engine to incorrectly match the
813 // (empty) root directory
813 // (empty) root directory
814 let func = if !(regexps.is_empty()) {
814 let func = if !(regexps.is_empty()) {
815 let matcher = re_matcher(&full_regex)?;
815 let matcher = re_matcher(&full_regex)?;
816 let func = move |filename: &HgPath| {
816 let func = move |filename: &HgPath| {
817 exact_set.contains(filename) || matcher.is_match(filename)
817 exact_set.contains(filename) || matcher.is_match(filename)
818 };
818 };
819 Box::new(func) as IgnoreFnType
819 Box::new(func) as IgnoreFnType
820 } else {
820 } else {
821 let func = move |filename: &HgPath| exact_set.contains(filename);
821 let func = move |filename: &HgPath| exact_set.contains(filename);
822 Box::new(func) as IgnoreFnType
822 Box::new(func) as IgnoreFnType
823 };
823 };
824
824
825 Ok((full_regex, func))
825 Ok((full_regex, func))
826 }
826 }
827
827
828 /// Returns roots and directories corresponding to each pattern.
828 /// Returns roots and directories corresponding to each pattern.
829 ///
829 ///
830 /// This calculates the roots and directories exactly matching the patterns and
830 /// This calculates the roots and directories exactly matching the patterns and
831 /// returns a tuple of (roots, dirs). It does not return other directories
831 /// returns a tuple of (roots, dirs). It does not return other directories
832 /// which may also need to be considered, like the parent directories.
832 /// which may also need to be considered, like the parent directories.
833 fn roots_and_dirs(
833 fn roots_and_dirs(
834 ignore_patterns: &[IgnorePattern],
834 ignore_patterns: &[IgnorePattern],
835 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
835 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
836 let mut roots = Vec::new();
836 let mut roots = Vec::new();
837 let mut dirs = Vec::new();
837 let mut dirs = Vec::new();
838
838
839 for ignore_pattern in ignore_patterns {
839 for ignore_pattern in ignore_patterns {
840 let IgnorePattern {
840 let IgnorePattern {
841 syntax, pattern, ..
841 syntax, pattern, ..
842 } = ignore_pattern;
842 } = ignore_pattern;
843 match syntax {
843 match syntax {
844 PatternSyntax::RootGlob | PatternSyntax::Glob => {
844 PatternSyntax::RootGlob | PatternSyntax::Glob => {
845 let mut root = HgPathBuf::new();
845 let mut root = HgPathBuf::new();
846 for p in pattern.split(|c| *c == b'/') {
846 for p in pattern.split(|c| *c == b'/') {
847 if p.iter()
847 if p.iter()
848 .any(|c| matches!(*c, b'[' | b'{' | b'*' | b'?'))
848 .any(|c| matches!(*c, b'[' | b'{' | b'*' | b'?'))
849 {
849 {
850 break;
850 break;
851 }
851 }
852 root.push(HgPathBuf::from_bytes(p).as_ref());
852 root.push(HgPathBuf::from_bytes(p).as_ref());
853 }
853 }
854 roots.push(root);
854 roots.push(root);
855 }
855 }
856 PatternSyntax::Path
856 PatternSyntax::Path
857 | PatternSyntax::RelPath
857 | PatternSyntax::RelPath
858 | PatternSyntax::FilePath => {
858 | PatternSyntax::FilePath => {
859 let pat = HgPath::new(if pattern == b"." {
859 let pat = HgPath::new(if pattern == b"." {
860 &[] as &[u8]
860 &[] as &[u8]
861 } else {
861 } else {
862 pattern
862 pattern
863 });
863 });
864 roots.push(pat.to_owned());
864 roots.push(pat.to_owned());
865 }
865 }
866 PatternSyntax::RootFiles => {
866 PatternSyntax::RootFiles => {
867 let pat = if pattern == b"." {
867 let pat = if pattern == b"." {
868 &[] as &[u8]
868 &[] as &[u8]
869 } else {
869 } else {
870 pattern
870 pattern
871 };
871 };
872 dirs.push(HgPathBuf::from_bytes(pat));
872 dirs.push(HgPathBuf::from_bytes(pat));
873 }
873 }
874 _ => {
874 _ => {
875 roots.push(HgPathBuf::new());
875 roots.push(HgPathBuf::new());
876 }
876 }
877 }
877 }
878 }
878 }
879 (roots, dirs)
879 (roots, dirs)
880 }
880 }
881
881
882 /// Paths extracted from patterns
882 /// Paths extracted from patterns
883 #[derive(Debug, PartialEq)]
883 #[derive(Debug, PartialEq)]
884 struct RootsDirsAndParents {
884 struct RootsDirsAndParents {
885 /// Directories to match recursively
885 /// Directories to match recursively
886 pub roots: HashSet<HgPathBuf>,
886 pub roots: HashSet<HgPathBuf>,
887 /// Directories to match non-recursively
887 /// Directories to match non-recursively
888 pub dirs: HashSet<HgPathBuf>,
888 pub dirs: HashSet<HgPathBuf>,
889 /// Implicitly required directories to go to items in either roots or dirs
889 /// Implicitly required directories to go to items in either roots or dirs
890 pub parents: HashSet<HgPathBuf>,
890 pub parents: HashSet<HgPathBuf>,
891 }
891 }
892
892
893 /// Extract roots, dirs and parents from patterns.
893 /// Extract roots, dirs and parents from patterns.
894 fn roots_dirs_and_parents(
894 fn roots_dirs_and_parents(
895 ignore_patterns: &[IgnorePattern],
895 ignore_patterns: &[IgnorePattern],
896 ) -> PatternResult<RootsDirsAndParents> {
896 ) -> PatternResult<RootsDirsAndParents> {
897 let (roots, dirs) = roots_and_dirs(ignore_patterns);
897 let (roots, dirs) = roots_and_dirs(ignore_patterns);
898
898
899 let mut parents = HashSet::new();
899 let mut parents = HashSet::new();
900
900
901 parents.extend(
901 parents.extend(
902 DirsMultiset::from_manifest(&dirs)?
902 DirsMultiset::from_manifest(&dirs)?
903 .iter()
903 .iter()
904 .map(ToOwned::to_owned),
904 .map(ToOwned::to_owned),
905 );
905 );
906 parents.extend(
906 parents.extend(
907 DirsMultiset::from_manifest(&roots)?
907 DirsMultiset::from_manifest(&roots)?
908 .iter()
908 .iter()
909 .map(ToOwned::to_owned),
909 .map(ToOwned::to_owned),
910 );
910 );
911
911
912 Ok(RootsDirsAndParents {
912 Ok(RootsDirsAndParents {
913 roots: HashSet::from_iter(roots),
913 roots: HashSet::from_iter(roots),
914 dirs: HashSet::from_iter(dirs),
914 dirs: HashSet::from_iter(dirs),
915 parents,
915 parents,
916 })
916 })
917 }
917 }
918
918
919 /// Returns a function that checks whether a given file (in the general sense)
919 /// Returns a function that checks whether a given file (in the general sense)
920 /// should be matched.
920 /// should be matched.
921 fn build_match<'a>(
921 fn build_match<'a>(
922 ignore_patterns: Vec<IgnorePattern>,
922 ignore_patterns: Vec<IgnorePattern>,
923 glob_suffix: &[u8],
923 glob_suffix: &[u8],
924 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
924 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
925 let mut match_funcs: Vec<IgnoreFnType<'a>> = vec![];
925 let mut match_funcs: Vec<IgnoreFnType<'a>> = vec![];
926 // For debugging and printing
926 // For debugging and printing
927 let mut patterns = vec![];
927 let mut patterns = vec![];
928
928
929 let (subincludes, ignore_patterns) = filter_subincludes(ignore_patterns)?;
929 let (subincludes, ignore_patterns) = filter_subincludes(ignore_patterns)?;
930
930
931 if !subincludes.is_empty() {
931 if !subincludes.is_empty() {
932 // Build prefix-based matcher functions for subincludes
932 // Build prefix-based matcher functions for subincludes
933 let mut submatchers = FastHashMap::default();
933 let mut submatchers = FastHashMap::default();
934 let mut prefixes = vec![];
934 let mut prefixes = vec![];
935
935
936 for sub_include in subincludes {
936 for sub_include in subincludes {
937 let matcher = IncludeMatcher::new(sub_include.included_patterns)?;
937 let matcher = IncludeMatcher::new(sub_include.included_patterns)?;
938 let match_fn =
938 let match_fn =
939 Box::new(move |path: &HgPath| matcher.matches(path));
939 Box::new(move |path: &HgPath| matcher.matches(path));
940 prefixes.push(sub_include.prefix.clone());
940 prefixes.push(sub_include.prefix.clone());
941 submatchers.insert(sub_include.prefix.clone(), match_fn);
941 submatchers.insert(sub_include.prefix.clone(), match_fn);
942 }
942 }
943
943
944 let match_subinclude = move |filename: &HgPath| {
944 let match_subinclude = move |filename: &HgPath| {
945 for prefix in prefixes.iter() {
945 for prefix in prefixes.iter() {
946 if let Some(rel) = filename.relative_to(prefix) {
946 if let Some(rel) = filename.relative_to(prefix) {
947 if (submatchers[prefix])(rel) {
947 if (submatchers[prefix])(rel) {
948 return true;
948 return true;
949 }
949 }
950 }
950 }
951 }
951 }
952 false
952 false
953 };
953 };
954
954
955 match_funcs.push(Box::new(match_subinclude));
955 match_funcs.push(Box::new(match_subinclude));
956 }
956 }
957
957
958 if !ignore_patterns.is_empty() {
958 if !ignore_patterns.is_empty() {
959 // Either do dumb matching if all patterns are rootfiles, or match
959 // Either do dumb matching if all patterns are rootfiles, or match
960 // with a regex.
960 // with a regex.
961 if ignore_patterns
961 if ignore_patterns
962 .iter()
962 .iter()
963 .all(|k| k.syntax == PatternSyntax::RootFiles)
963 .all(|k| k.syntax == PatternSyntax::RootFiles)
964 {
964 {
965 let dirs: HashSet<_> = ignore_patterns
965 let dirs: HashSet<_> = ignore_patterns
966 .iter()
966 .iter()
967 .map(|k| k.pattern.to_owned())
967 .map(|k| k.pattern.to_owned())
968 .collect();
968 .collect();
969 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
969 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
970
970
971 let match_func = move |path: &HgPath| -> bool {
971 let match_func = move |path: &HgPath| -> bool {
972 let path = path.as_bytes();
972 let path = path.as_bytes();
973 let i = path.iter().rposition(|a| *a == b'/');
973 let i = path.iter().rposition(|a| *a == b'/');
974 let dir = if let Some(i) = i { &path[..i] } else { b"." };
974 let dir = if let Some(i) = i { &path[..i] } else { b"." };
975 dirs.contains(dir)
975 dirs.contains(dir)
976 };
976 };
977 match_funcs.push(Box::new(match_func));
977 match_funcs.push(Box::new(match_func));
978
978
979 patterns.extend(b"rootfilesin: ");
979 patterns.extend(b"rootfilesin: ");
980 dirs_vec.sort();
980 dirs_vec.sort();
981 patterns.extend(dirs_vec.escaped_bytes());
981 patterns.extend(dirs_vec.escaped_bytes());
982 } else {
982 } else {
983 let (new_re, match_func) =
983 let (new_re, match_func) =
984 build_regex_match(&ignore_patterns, glob_suffix)?;
984 build_regex_match(&ignore_patterns, glob_suffix)?;
985 patterns = new_re;
985 patterns = new_re;
986 match_funcs.push(match_func)
986 match_funcs.push(match_func)
987 }
987 }
988 }
988 }
989
989
990 Ok(if match_funcs.len() == 1 {
990 Ok(if match_funcs.len() == 1 {
991 (patterns, match_funcs.remove(0))
991 (patterns, match_funcs.remove(0))
992 } else {
992 } else {
993 (
993 (
994 patterns,
994 patterns,
995 Box::new(move |f: &HgPath| -> bool {
995 Box::new(move |f: &HgPath| -> bool {
996 match_funcs.iter().any(|match_func| match_func(f))
996 match_funcs.iter().any(|match_func| match_func(f))
997 }),
997 }),
998 )
998 )
999 })
999 })
1000 }
1000 }
1001
1001
1002 /// Parses all "ignore" files with their recursive includes and returns a
1002 /// Parses all "ignore" files with their recursive includes and returns a
1003 /// function that checks whether a given file (in the general sense) should be
1003 /// function that checks whether a given file (in the general sense) should be
1004 /// ignored.
1004 /// ignored.
1005 pub fn get_ignore_matcher<'a>(
1005 pub fn get_ignore_matcher<'a>(
1006 mut all_pattern_files: Vec<PathBuf>,
1006 mut all_pattern_files: Vec<PathBuf>,
1007 root_dir: &Path,
1007 root_dir: &Path,
1008 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
1008 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
1009 ) -> PatternResult<(IncludeMatcher<'a>, Vec<PatternFileWarning>)> {
1009 ) -> PatternResult<(IncludeMatcher<'a>, Vec<PatternFileWarning>)> {
1010 let mut all_patterns = vec![];
1010 let mut all_patterns = vec![];
1011 let mut all_warnings = vec![];
1011 let mut all_warnings = vec![];
1012
1012
1013 // Sort to make the ordering of calls to `inspect_pattern_bytes`
1013 // Sort to make the ordering of calls to `inspect_pattern_bytes`
1014 // deterministic even if the ordering of `all_pattern_files` is not (such
1014 // deterministic even if the ordering of `all_pattern_files` is not (such
1015 // as when a iteration order of a Python dict or Rust HashMap is involved).
1015 // as when a iteration order of a Python dict or Rust HashMap is involved).
1016 // Sort by "string" representation instead of the default by component
1016 // Sort by "string" representation instead of the default by component
1017 // (with a Rust-specific definition of a component)
1017 // (with a Rust-specific definition of a component)
1018 all_pattern_files
1018 all_pattern_files
1019 .sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
1019 .sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
1020
1020
1021 for pattern_file in &all_pattern_files {
1021 for pattern_file in &all_pattern_files {
1022 let (patterns, warnings) = get_patterns_from_file(
1022 let (patterns, warnings) = get_patterns_from_file(
1023 pattern_file,
1023 pattern_file,
1024 root_dir,
1024 root_dir,
1025 inspect_pattern_bytes,
1025 inspect_pattern_bytes,
1026 )?;
1026 )?;
1027
1027
1028 all_patterns.extend(patterns.to_owned());
1028 all_patterns.extend(patterns.to_owned());
1029 all_warnings.extend(warnings);
1029 all_warnings.extend(warnings);
1030 }
1030 }
1031 let matcher = IncludeMatcher::new(all_patterns)?;
1031 let matcher = IncludeMatcher::new(all_patterns)?;
1032 Ok((matcher, all_warnings))
1032 Ok((matcher, all_warnings))
1033 }
1033 }
1034
1034
1035 /// Parses all "ignore" files with their recursive includes and returns a
1035 /// Parses all "ignore" files with their recursive includes and returns a
1036 /// function that checks whether a given file (in the general sense) should be
1036 /// function that checks whether a given file (in the general sense) should be
1037 /// ignored.
1037 /// ignored.
1038 pub fn get_ignore_function<'a>(
1038 pub fn get_ignore_function<'a>(
1039 all_pattern_files: Vec<PathBuf>,
1039 all_pattern_files: Vec<PathBuf>,
1040 root_dir: &Path,
1040 root_dir: &Path,
1041 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
1041 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
1042 ) -> PatternResult<(IgnoreFnType<'a>, Vec<PatternFileWarning>)> {
1042 ) -> PatternResult<(IgnoreFnType<'a>, Vec<PatternFileWarning>)> {
1043 let res =
1043 let res =
1044 get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes);
1044 get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes);
1045 res.map(|(matcher, all_warnings)| {
1045 res.map(|(matcher, all_warnings)| {
1046 let res: IgnoreFnType<'a> =
1046 let res: IgnoreFnType<'a> =
1047 Box::new(move |path: &HgPath| matcher.matches(path));
1047 Box::new(move |path: &HgPath| matcher.matches(path));
1048
1048
1049 (res, all_warnings)
1049 (res, all_warnings)
1050 })
1050 })
1051 }
1051 }
1052
1052
1053 impl<'a> IncludeMatcher<'a> {
1053 impl<'a> IncludeMatcher<'a> {
1054 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
1054 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
1055 let RootsDirsAndParents {
1055 let RootsDirsAndParents {
1056 roots,
1056 roots,
1057 dirs,
1057 dirs,
1058 parents,
1058 parents,
1059 } = roots_dirs_and_parents(&ignore_patterns)?;
1059 } = roots_dirs_and_parents(&ignore_patterns)?;
1060 let prefix = ignore_patterns.iter().all(|k| {
1060 let prefix = ignore_patterns.iter().all(|k| {
1061 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
1061 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
1062 });
1062 });
1063 let (patterns, match_fn) = build_match(ignore_patterns, b"(?:/|$)")?;
1063 let (patterns, match_fn) = build_match(ignore_patterns, b"(?:/|$)")?;
1064
1064
1065 Ok(Self {
1065 Ok(Self {
1066 patterns,
1066 patterns,
1067 match_fn,
1067 match_fn,
1068 prefix,
1068 prefix,
1069 roots,
1069 roots,
1070 dirs,
1070 dirs,
1071 parents,
1071 parents,
1072 })
1072 })
1073 }
1073 }
1074
1074
1075 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
1075 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
1076 // TODO cache
1076 // TODO cache
1077 let thing = self
1077 let thing = self
1078 .dirs
1078 .dirs
1079 .iter()
1079 .iter()
1080 .chain(self.roots.iter())
1080 .chain(self.roots.iter())
1081 .chain(self.parents.iter());
1081 .chain(self.parents.iter());
1082 DirsChildrenMultiset::new(thing, Some(&self.parents))
1082 DirsChildrenMultiset::new(thing, Some(&self.parents))
1083 }
1083 }
1084
1084
1085 pub fn debug_get_patterns(&self) -> &[u8] {
1085 pub fn debug_get_patterns(&self) -> &[u8] {
1086 self.patterns.as_ref()
1086 self.patterns.as_ref()
1087 }
1087 }
1088 }
1088 }
1089
1089
1090 impl<'a> Display for IncludeMatcher<'a> {
1090 impl<'a> Display for IncludeMatcher<'a> {
1091 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
1091 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
1092 // XXX What about exact matches?
1092 // XXX What about exact matches?
1093 // I'm not sure it's worth it to clone the HashSet and keep it
1093 // I'm not sure it's worth it to clone the HashSet and keep it
1094 // around just in case someone wants to display the matcher, plus
1094 // around just in case someone wants to display the matcher, plus
1095 // it's going to be unreadable after a few entries, but we need to
1095 // it's going to be unreadable after a few entries, but we need to
1096 // inform in this display that exact matches are being used and are
1096 // inform in this display that exact matches are being used and are
1097 // (on purpose) missing from the `includes`.
1097 // (on purpose) missing from the `includes`.
1098 write!(
1098 write!(
1099 f,
1099 f,
1100 "IncludeMatcher(includes='{}')",
1100 "IncludeMatcher(includes='{}')",
1101 String::from_utf8_lossy(&self.patterns.escaped_bytes())
1101 String::from_utf8_lossy(&self.patterns.escaped_bytes())
1102 )
1102 )
1103 }
1103 }
1104 }
1104 }
1105
1105
1106 #[cfg(test)]
1106 #[cfg(test)]
1107 mod tests {
1107 mod tests {
1108 use super::*;
1108 use super::*;
1109 use pretty_assertions::assert_eq;
1109 use pretty_assertions::assert_eq;
1110 use std::collections::BTreeMap;
1110 use std::collections::BTreeMap;
1111 use std::collections::BTreeSet;
1111 use std::collections::BTreeSet;
1112 use std::fmt::Debug;
1112 use std::fmt::Debug;
1113 use std::path::Path;
1113 use std::path::Path;
1114
1114
1115 #[test]
1115 #[test]
1116 fn test_roots_and_dirs() {
1116 fn test_roots_and_dirs() {
1117 let pats = vec![
1117 let pats = vec![
1118 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1118 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1119 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1119 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1120 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1120 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1121 ];
1121 ];
1122 let (roots, dirs) = roots_and_dirs(&pats);
1122 let (roots, dirs) = roots_and_dirs(&pats);
1123
1123
1124 assert_eq!(
1124 assert_eq!(
1125 roots,
1125 roots,
1126 vec!(
1126 vec!(
1127 HgPathBuf::from_bytes(b"g/h"),
1127 HgPathBuf::from_bytes(b"g/h"),
1128 HgPathBuf::from_bytes(b"g/h"),
1128 HgPathBuf::from_bytes(b"g/h"),
1129 HgPathBuf::new()
1129 HgPathBuf::new()
1130 ),
1130 ),
1131 );
1131 );
1132 assert_eq!(dirs, vec!());
1132 assert_eq!(dirs, vec!());
1133 }
1133 }
1134
1134
1135 #[test]
1135 #[test]
1136 fn test_roots_dirs_and_parents() {
1136 fn test_roots_dirs_and_parents() {
1137 let pats = vec![
1137 let pats = vec![
1138 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1138 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1139 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1139 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1140 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1140 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1141 ];
1141 ];
1142
1142
1143 let mut roots = HashSet::new();
1143 let mut roots = HashSet::new();
1144 roots.insert(HgPathBuf::from_bytes(b"g/h"));
1144 roots.insert(HgPathBuf::from_bytes(b"g/h"));
1145 roots.insert(HgPathBuf::new());
1145 roots.insert(HgPathBuf::new());
1146
1146
1147 let dirs = HashSet::new();
1147 let dirs = HashSet::new();
1148
1148
1149 let mut parents = HashSet::new();
1149 let mut parents = HashSet::new();
1150 parents.insert(HgPathBuf::new());
1150 parents.insert(HgPathBuf::new());
1151 parents.insert(HgPathBuf::from_bytes(b"g"));
1151 parents.insert(HgPathBuf::from_bytes(b"g"));
1152
1152
1153 assert_eq!(
1153 assert_eq!(
1154 roots_dirs_and_parents(&pats).unwrap(),
1154 roots_dirs_and_parents(&pats).unwrap(),
1155 RootsDirsAndParents {
1155 RootsDirsAndParents {
1156 roots,
1156 roots,
1157 dirs,
1157 dirs,
1158 parents
1158 parents
1159 }
1159 }
1160 );
1160 );
1161 }
1161 }
1162
1162
1163 #[test]
1163 #[test]
1164 fn test_filematcher_visit_children_set() {
1164 fn test_filematcher_visit_children_set() {
1165 // Visitchildrenset
1165 // Visitchildrenset
1166 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/foo.txt")];
1166 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/foo.txt")];
1167 let matcher = FileMatcher::new(files).unwrap();
1167 let matcher = FileMatcher::new(files).unwrap();
1168
1168
1169 let mut set = HashSet::new();
1169 let mut set = HashSet::new();
1170 set.insert(HgPathBuf::from_bytes(b"dir"));
1170 set.insert(HgPathBuf::from_bytes(b"dir"));
1171 assert_eq!(
1171 assert_eq!(
1172 matcher.visit_children_set(HgPath::new(b"")),
1172 matcher.visit_children_set(HgPath::new(b"")),
1173 VisitChildrenSet::Set(set)
1173 VisitChildrenSet::Set(set)
1174 );
1174 );
1175
1175
1176 let mut set = HashSet::new();
1176 let mut set = HashSet::new();
1177 set.insert(HgPathBuf::from_bytes(b"subdir"));
1177 set.insert(HgPathBuf::from_bytes(b"subdir"));
1178 assert_eq!(
1178 assert_eq!(
1179 matcher.visit_children_set(HgPath::new(b"dir")),
1179 matcher.visit_children_set(HgPath::new(b"dir")),
1180 VisitChildrenSet::Set(set)
1180 VisitChildrenSet::Set(set)
1181 );
1181 );
1182
1182
1183 let mut set = HashSet::new();
1183 let mut set = HashSet::new();
1184 set.insert(HgPathBuf::from_bytes(b"foo.txt"));
1184 set.insert(HgPathBuf::from_bytes(b"foo.txt"));
1185 assert_eq!(
1185 assert_eq!(
1186 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1186 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1187 VisitChildrenSet::Set(set)
1187 VisitChildrenSet::Set(set)
1188 );
1188 );
1189
1189
1190 assert_eq!(
1190 assert_eq!(
1191 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1191 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1192 VisitChildrenSet::Empty
1192 VisitChildrenSet::Empty
1193 );
1193 );
1194 assert_eq!(
1194 assert_eq!(
1195 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
1195 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
1196 VisitChildrenSet::Empty
1196 VisitChildrenSet::Empty
1197 );
1197 );
1198 assert_eq!(
1198 assert_eq!(
1199 matcher.visit_children_set(HgPath::new(b"folder")),
1199 matcher.visit_children_set(HgPath::new(b"folder")),
1200 VisitChildrenSet::Empty
1200 VisitChildrenSet::Empty
1201 );
1201 );
1202 }
1202 }
1203
1203
1204 #[test]
1204 #[test]
1205 fn test_filematcher_visit_children_set_files_and_dirs() {
1205 fn test_filematcher_visit_children_set_files_and_dirs() {
1206 let files = vec![
1206 let files = vec![
1207 HgPathBuf::from_bytes(b"rootfile.txt"),
1207 HgPathBuf::from_bytes(b"rootfile.txt"),
1208 HgPathBuf::from_bytes(b"a/file1.txt"),
1208 HgPathBuf::from_bytes(b"a/file1.txt"),
1209 HgPathBuf::from_bytes(b"a/b/file2.txt"),
1209 HgPathBuf::from_bytes(b"a/b/file2.txt"),
1210 // No file in a/b/c
1210 // No file in a/b/c
1211 HgPathBuf::from_bytes(b"a/b/c/d/file4.txt"),
1211 HgPathBuf::from_bytes(b"a/b/c/d/file4.txt"),
1212 ];
1212 ];
1213 let matcher = FileMatcher::new(files).unwrap();
1213 let matcher = FileMatcher::new(files).unwrap();
1214
1214
1215 let mut set = HashSet::new();
1215 let mut set = HashSet::new();
1216 set.insert(HgPathBuf::from_bytes(b"a"));
1216 set.insert(HgPathBuf::from_bytes(b"a"));
1217 set.insert(HgPathBuf::from_bytes(b"rootfile.txt"));
1217 set.insert(HgPathBuf::from_bytes(b"rootfile.txt"));
1218 assert_eq!(
1218 assert_eq!(
1219 matcher.visit_children_set(HgPath::new(b"")),
1219 matcher.visit_children_set(HgPath::new(b"")),
1220 VisitChildrenSet::Set(set)
1220 VisitChildrenSet::Set(set)
1221 );
1221 );
1222
1222
1223 let mut set = HashSet::new();
1223 let mut set = HashSet::new();
1224 set.insert(HgPathBuf::from_bytes(b"b"));
1224 set.insert(HgPathBuf::from_bytes(b"b"));
1225 set.insert(HgPathBuf::from_bytes(b"file1.txt"));
1225 set.insert(HgPathBuf::from_bytes(b"file1.txt"));
1226 assert_eq!(
1226 assert_eq!(
1227 matcher.visit_children_set(HgPath::new(b"a")),
1227 matcher.visit_children_set(HgPath::new(b"a")),
1228 VisitChildrenSet::Set(set)
1228 VisitChildrenSet::Set(set)
1229 );
1229 );
1230
1230
1231 let mut set = HashSet::new();
1231 let mut set = HashSet::new();
1232 set.insert(HgPathBuf::from_bytes(b"c"));
1232 set.insert(HgPathBuf::from_bytes(b"c"));
1233 set.insert(HgPathBuf::from_bytes(b"file2.txt"));
1233 set.insert(HgPathBuf::from_bytes(b"file2.txt"));
1234 assert_eq!(
1234 assert_eq!(
1235 matcher.visit_children_set(HgPath::new(b"a/b")),
1235 matcher.visit_children_set(HgPath::new(b"a/b")),
1236 VisitChildrenSet::Set(set)
1236 VisitChildrenSet::Set(set)
1237 );
1237 );
1238
1238
1239 let mut set = HashSet::new();
1239 let mut set = HashSet::new();
1240 set.insert(HgPathBuf::from_bytes(b"d"));
1240 set.insert(HgPathBuf::from_bytes(b"d"));
1241 assert_eq!(
1241 assert_eq!(
1242 matcher.visit_children_set(HgPath::new(b"a/b/c")),
1242 matcher.visit_children_set(HgPath::new(b"a/b/c")),
1243 VisitChildrenSet::Set(set)
1243 VisitChildrenSet::Set(set)
1244 );
1244 );
1245 let mut set = HashSet::new();
1245 let mut set = HashSet::new();
1246 set.insert(HgPathBuf::from_bytes(b"file4.txt"));
1246 set.insert(HgPathBuf::from_bytes(b"file4.txt"));
1247 assert_eq!(
1247 assert_eq!(
1248 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
1248 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
1249 VisitChildrenSet::Set(set)
1249 VisitChildrenSet::Set(set)
1250 );
1250 );
1251
1251
1252 assert_eq!(
1252 assert_eq!(
1253 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
1253 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
1254 VisitChildrenSet::Empty
1254 VisitChildrenSet::Empty
1255 );
1255 );
1256 assert_eq!(
1256 assert_eq!(
1257 matcher.visit_children_set(HgPath::new(b"folder")),
1257 matcher.visit_children_set(HgPath::new(b"folder")),
1258 VisitChildrenSet::Empty
1258 VisitChildrenSet::Empty
1259 );
1259 );
1260 }
1260 }
1261
1261
1262 #[test]
1262 #[test]
1263 fn test_patternmatcher() {
1263 fn test_patternmatcher() {
1264 // VisitdirPrefix
1264 // VisitdirPrefix
1265 let m = PatternMatcher::new(vec![IgnorePattern::new(
1265 let m = PatternMatcher::new(vec![IgnorePattern::new(
1266 PatternSyntax::Path,
1266 PatternSyntax::Path,
1267 b"dir/subdir",
1267 b"dir/subdir",
1268 Path::new(""),
1268 Path::new(""),
1269 )])
1269 )])
1270 .unwrap();
1270 .unwrap();
1271 assert_eq!(
1271 assert_eq!(
1272 m.visit_children_set(HgPath::new(b"")),
1272 m.visit_children_set(HgPath::new(b"")),
1273 VisitChildrenSet::This
1273 VisitChildrenSet::This
1274 );
1274 );
1275 assert_eq!(
1275 assert_eq!(
1276 m.visit_children_set(HgPath::new(b"dir")),
1276 m.visit_children_set(HgPath::new(b"dir")),
1277 VisitChildrenSet::This
1277 VisitChildrenSet::This
1278 );
1278 );
1279 assert_eq!(
1279 assert_eq!(
1280 m.visit_children_set(HgPath::new(b"dir/subdir")),
1280 m.visit_children_set(HgPath::new(b"dir/subdir")),
1281 VisitChildrenSet::Recursive
1281 VisitChildrenSet::Recursive
1282 );
1282 );
1283 // OPT: This should probably be Recursive if its parent is?
1283 // OPT: This should probably be Recursive if its parent is?
1284 assert_eq!(
1284 assert_eq!(
1285 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1285 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1286 VisitChildrenSet::This
1286 VisitChildrenSet::This
1287 );
1287 );
1288 assert_eq!(
1288 assert_eq!(
1289 m.visit_children_set(HgPath::new(b"folder")),
1289 m.visit_children_set(HgPath::new(b"folder")),
1290 VisitChildrenSet::Empty
1290 VisitChildrenSet::Empty
1291 );
1291 );
1292
1292
1293 // VisitchildrensetPrefix
1293 // VisitchildrensetPrefix
1294 let m = PatternMatcher::new(vec![IgnorePattern::new(
1294 let m = PatternMatcher::new(vec![IgnorePattern::new(
1295 PatternSyntax::Path,
1295 PatternSyntax::Path,
1296 b"dir/subdir",
1296 b"dir/subdir",
1297 Path::new(""),
1297 Path::new(""),
1298 )])
1298 )])
1299 .unwrap();
1299 .unwrap();
1300 assert_eq!(
1300 assert_eq!(
1301 m.visit_children_set(HgPath::new(b"")),
1301 m.visit_children_set(HgPath::new(b"")),
1302 VisitChildrenSet::This
1302 VisitChildrenSet::This
1303 );
1303 );
1304 assert_eq!(
1304 assert_eq!(
1305 m.visit_children_set(HgPath::new(b"dir")),
1305 m.visit_children_set(HgPath::new(b"dir")),
1306 VisitChildrenSet::This
1306 VisitChildrenSet::This
1307 );
1307 );
1308 assert_eq!(
1308 assert_eq!(
1309 m.visit_children_set(HgPath::new(b"dir/subdir")),
1309 m.visit_children_set(HgPath::new(b"dir/subdir")),
1310 VisitChildrenSet::Recursive
1310 VisitChildrenSet::Recursive
1311 );
1311 );
1312 // OPT: This should probably be Recursive if its parent is?
1312 // OPT: This should probably be Recursive if its parent is?
1313 assert_eq!(
1313 assert_eq!(
1314 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1314 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1315 VisitChildrenSet::This
1315 VisitChildrenSet::This
1316 );
1316 );
1317 assert_eq!(
1317 assert_eq!(
1318 m.visit_children_set(HgPath::new(b"folder")),
1318 m.visit_children_set(HgPath::new(b"folder")),
1319 VisitChildrenSet::Empty
1319 VisitChildrenSet::Empty
1320 );
1320 );
1321
1321
1322 // VisitdirRootfilesin
1322 // VisitdirRootfilesin
1323 let m = PatternMatcher::new(vec![IgnorePattern::new(
1323 let m = PatternMatcher::new(vec![IgnorePattern::new(
1324 PatternSyntax::RootFiles,
1324 PatternSyntax::RootFiles,
1325 b"dir/subdir",
1325 b"dir/subdir",
1326 Path::new(""),
1326 Path::new(""),
1327 )])
1327 )])
1328 .unwrap();
1328 .unwrap();
1329 assert_eq!(
1329 assert_eq!(
1330 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1330 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1331 VisitChildrenSet::Empty
1331 VisitChildrenSet::Empty
1332 );
1332 );
1333 assert_eq!(
1333 assert_eq!(
1334 m.visit_children_set(HgPath::new(b"folder")),
1334 m.visit_children_set(HgPath::new(b"folder")),
1335 VisitChildrenSet::Empty
1335 VisitChildrenSet::Empty
1336 );
1336 );
1337 // FIXME: These should probably be This.
1337 // FIXME: These should probably be This.
1338 assert_eq!(
1338 assert_eq!(
1339 m.visit_children_set(HgPath::new(b"")),
1339 m.visit_children_set(HgPath::new(b"")),
1340 VisitChildrenSet::Empty
1340 VisitChildrenSet::Empty
1341 );
1341 );
1342 assert_eq!(
1342 assert_eq!(
1343 m.visit_children_set(HgPath::new(b"dir")),
1343 m.visit_children_set(HgPath::new(b"dir")),
1344 VisitChildrenSet::Empty
1344 VisitChildrenSet::Empty
1345 );
1345 );
1346 assert_eq!(
1346 assert_eq!(
1347 m.visit_children_set(HgPath::new(b"dir/subdir")),
1347 m.visit_children_set(HgPath::new(b"dir/subdir")),
1348 VisitChildrenSet::Empty
1348 VisitChildrenSet::Empty
1349 );
1349 );
1350
1350
1351 // VisitchildrensetRootfilesin
1351 // VisitchildrensetRootfilesin
1352 let m = PatternMatcher::new(vec![IgnorePattern::new(
1352 let m = PatternMatcher::new(vec![IgnorePattern::new(
1353 PatternSyntax::RootFiles,
1353 PatternSyntax::RootFiles,
1354 b"dir/subdir",
1354 b"dir/subdir",
1355 Path::new(""),
1355 Path::new(""),
1356 )])
1356 )])
1357 .unwrap();
1357 .unwrap();
1358 assert_eq!(
1358 assert_eq!(
1359 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1359 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1360 VisitChildrenSet::Empty
1360 VisitChildrenSet::Empty
1361 );
1361 );
1362 assert_eq!(
1362 assert_eq!(
1363 m.visit_children_set(HgPath::new(b"folder")),
1363 m.visit_children_set(HgPath::new(b"folder")),
1364 VisitChildrenSet::Empty
1364 VisitChildrenSet::Empty
1365 );
1365 );
1366 // FIXME: These should probably be {'dir'}, {'subdir'} and This,
1366 // FIXME: These should probably be {'dir'}, {'subdir'} and This,
1367 // respectively, or at least This for all three.
1367 // respectively, or at least This for all three.
1368 assert_eq!(
1368 assert_eq!(
1369 m.visit_children_set(HgPath::new(b"")),
1369 m.visit_children_set(HgPath::new(b"")),
1370 VisitChildrenSet::Empty
1370 VisitChildrenSet::Empty
1371 );
1371 );
1372 assert_eq!(
1372 assert_eq!(
1373 m.visit_children_set(HgPath::new(b"dir")),
1373 m.visit_children_set(HgPath::new(b"dir")),
1374 VisitChildrenSet::Empty
1374 VisitChildrenSet::Empty
1375 );
1375 );
1376 assert_eq!(
1376 assert_eq!(
1377 m.visit_children_set(HgPath::new(b"dir/subdir")),
1377 m.visit_children_set(HgPath::new(b"dir/subdir")),
1378 VisitChildrenSet::Empty
1378 VisitChildrenSet::Empty
1379 );
1379 );
1380
1380
1381 // VisitdirGlob
1381 // VisitdirGlob
1382 let m = PatternMatcher::new(vec![IgnorePattern::new(
1382 let m = PatternMatcher::new(vec![IgnorePattern::new(
1383 PatternSyntax::Glob,
1383 PatternSyntax::Glob,
1384 b"dir/z*",
1384 b"dir/z*",
1385 Path::new(""),
1385 Path::new(""),
1386 )])
1386 )])
1387 .unwrap();
1387 .unwrap();
1388 assert_eq!(
1388 assert_eq!(
1389 m.visit_children_set(HgPath::new(b"")),
1389 m.visit_children_set(HgPath::new(b"")),
1390 VisitChildrenSet::This
1390 VisitChildrenSet::This
1391 );
1391 );
1392 // FIXME: This probably should be This
1392 // FIXME: This probably should be This
1393 assert_eq!(
1393 assert_eq!(
1394 m.visit_children_set(HgPath::new(b"dir")),
1394 m.visit_children_set(HgPath::new(b"dir")),
1395 VisitChildrenSet::Empty
1395 VisitChildrenSet::Empty
1396 );
1396 );
1397 assert_eq!(
1397 assert_eq!(
1398 m.visit_children_set(HgPath::new(b"folder")),
1398 m.visit_children_set(HgPath::new(b"folder")),
1399 VisitChildrenSet::Empty
1399 VisitChildrenSet::Empty
1400 );
1400 );
1401 // OPT: these should probably be False.
1401 // OPT: these should probably be False.
1402 assert_eq!(
1402 assert_eq!(
1403 m.visit_children_set(HgPath::new(b"dir/subdir")),
1403 m.visit_children_set(HgPath::new(b"dir/subdir")),
1404 VisitChildrenSet::This
1404 VisitChildrenSet::This
1405 );
1405 );
1406 assert_eq!(
1406 assert_eq!(
1407 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1407 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1408 VisitChildrenSet::This
1408 VisitChildrenSet::This
1409 );
1409 );
1410
1410
1411 // VisitchildrensetGlob
1411 // VisitchildrensetGlob
1412 let m = PatternMatcher::new(vec![IgnorePattern::new(
1412 let m = PatternMatcher::new(vec![IgnorePattern::new(
1413 PatternSyntax::Glob,
1413 PatternSyntax::Glob,
1414 b"dir/z*",
1414 b"dir/z*",
1415 Path::new(""),
1415 Path::new(""),
1416 )])
1416 )])
1417 .unwrap();
1417 .unwrap();
1418 assert_eq!(
1418 assert_eq!(
1419 m.visit_children_set(HgPath::new(b"")),
1419 m.visit_children_set(HgPath::new(b"")),
1420 VisitChildrenSet::This
1420 VisitChildrenSet::This
1421 );
1421 );
1422 assert_eq!(
1422 assert_eq!(
1423 m.visit_children_set(HgPath::new(b"folder")),
1423 m.visit_children_set(HgPath::new(b"folder")),
1424 VisitChildrenSet::Empty
1424 VisitChildrenSet::Empty
1425 );
1425 );
1426 // FIXME: This probably should be This
1426 // FIXME: This probably should be This
1427 assert_eq!(
1427 assert_eq!(
1428 m.visit_children_set(HgPath::new(b"dir")),
1428 m.visit_children_set(HgPath::new(b"dir")),
1429 VisitChildrenSet::Empty
1429 VisitChildrenSet::Empty
1430 );
1430 );
1431 // OPT: these should probably be Empty
1431 // OPT: these should probably be Empty
1432 assert_eq!(
1432 assert_eq!(
1433 m.visit_children_set(HgPath::new(b"dir/subdir")),
1433 m.visit_children_set(HgPath::new(b"dir/subdir")),
1434 VisitChildrenSet::This
1434 VisitChildrenSet::This
1435 );
1435 );
1436 assert_eq!(
1436 assert_eq!(
1437 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1437 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1438 VisitChildrenSet::This
1438 VisitChildrenSet::This
1439 );
1439 );
1440
1440
1441 // VisitdirFilepath
1441 // VisitdirFilepath
1442 let m = PatternMatcher::new(vec![IgnorePattern::new(
1442 let m = PatternMatcher::new(vec![IgnorePattern::new(
1443 PatternSyntax::FilePath,
1443 PatternSyntax::FilePath,
1444 b"dir/z",
1444 b"dir/z",
1445 Path::new(""),
1445 Path::new(""),
1446 )])
1446 )])
1447 .unwrap();
1447 .unwrap();
1448 assert_eq!(
1448 assert_eq!(
1449 m.visit_children_set(HgPath::new(b"")),
1449 m.visit_children_set(HgPath::new(b"")),
1450 VisitChildrenSet::This
1450 VisitChildrenSet::This
1451 );
1451 );
1452 assert_eq!(
1452 assert_eq!(
1453 m.visit_children_set(HgPath::new(b"dir")),
1453 m.visit_children_set(HgPath::new(b"dir")),
1454 VisitChildrenSet::This
1454 VisitChildrenSet::This
1455 );
1455 );
1456 assert_eq!(
1456 assert_eq!(
1457 m.visit_children_set(HgPath::new(b"folder")),
1457 m.visit_children_set(HgPath::new(b"folder")),
1458 VisitChildrenSet::Empty
1458 VisitChildrenSet::Empty
1459 );
1459 );
1460 assert_eq!(
1460 assert_eq!(
1461 m.visit_children_set(HgPath::new(b"dir/subdir")),
1461 m.visit_children_set(HgPath::new(b"dir/subdir")),
1462 VisitChildrenSet::Empty
1462 VisitChildrenSet::Empty
1463 );
1463 );
1464 assert_eq!(
1464 assert_eq!(
1465 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1465 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1466 VisitChildrenSet::Empty
1466 VisitChildrenSet::Empty
1467 );
1467 );
1468
1468
1469 // VisitchildrensetFilepath
1469 // VisitchildrensetFilepath
1470 let m = PatternMatcher::new(vec![IgnorePattern::new(
1470 let m = PatternMatcher::new(vec![IgnorePattern::new(
1471 PatternSyntax::FilePath,
1471 PatternSyntax::FilePath,
1472 b"dir/z",
1472 b"dir/z",
1473 Path::new(""),
1473 Path::new(""),
1474 )])
1474 )])
1475 .unwrap();
1475 .unwrap();
1476 assert_eq!(
1476 assert_eq!(
1477 m.visit_children_set(HgPath::new(b"")),
1477 m.visit_children_set(HgPath::new(b"")),
1478 VisitChildrenSet::This
1478 VisitChildrenSet::This
1479 );
1479 );
1480 assert_eq!(
1480 assert_eq!(
1481 m.visit_children_set(HgPath::new(b"folder")),
1481 m.visit_children_set(HgPath::new(b"folder")),
1482 VisitChildrenSet::Empty
1482 VisitChildrenSet::Empty
1483 );
1483 );
1484 assert_eq!(
1484 assert_eq!(
1485 m.visit_children_set(HgPath::new(b"dir")),
1485 m.visit_children_set(HgPath::new(b"dir")),
1486 VisitChildrenSet::This
1486 VisitChildrenSet::This
1487 );
1487 );
1488 assert_eq!(
1488 assert_eq!(
1489 m.visit_children_set(HgPath::new(b"dir/subdir")),
1489 m.visit_children_set(HgPath::new(b"dir/subdir")),
1490 VisitChildrenSet::Empty
1490 VisitChildrenSet::Empty
1491 );
1491 );
1492 assert_eq!(
1492 assert_eq!(
1493 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1493 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1494 VisitChildrenSet::Empty
1494 VisitChildrenSet::Empty
1495 );
1495 );
1496 }
1496 }
1497
1497
1498 #[test]
1498 #[test]
1499 fn test_includematcher() {
1499 fn test_includematcher() {
1500 // VisitchildrensetPrefix
1500 // VisitchildrensetPrefix
1501 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1501 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1502 PatternSyntax::RelPath,
1502 PatternSyntax::RelPath,
1503 b"dir/subdir",
1503 b"dir/subdir",
1504 Path::new(""),
1504 Path::new(""),
1505 )])
1505 )])
1506 .unwrap();
1506 .unwrap();
1507
1507
1508 let mut set = HashSet::new();
1508 let mut set = HashSet::new();
1509 set.insert(HgPathBuf::from_bytes(b"dir"));
1509 set.insert(HgPathBuf::from_bytes(b"dir"));
1510 assert_eq!(
1510 assert_eq!(
1511 matcher.visit_children_set(HgPath::new(b"")),
1511 matcher.visit_children_set(HgPath::new(b"")),
1512 VisitChildrenSet::Set(set)
1512 VisitChildrenSet::Set(set)
1513 );
1513 );
1514
1514
1515 let mut set = HashSet::new();
1515 let mut set = HashSet::new();
1516 set.insert(HgPathBuf::from_bytes(b"subdir"));
1516 set.insert(HgPathBuf::from_bytes(b"subdir"));
1517 assert_eq!(
1517 assert_eq!(
1518 matcher.visit_children_set(HgPath::new(b"dir")),
1518 matcher.visit_children_set(HgPath::new(b"dir")),
1519 VisitChildrenSet::Set(set)
1519 VisitChildrenSet::Set(set)
1520 );
1520 );
1521 assert_eq!(
1521 assert_eq!(
1522 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1522 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1523 VisitChildrenSet::Recursive
1523 VisitChildrenSet::Recursive
1524 );
1524 );
1525 // OPT: This should probably be 'all' if its parent is?
1525 // OPT: This should probably be 'all' if its parent is?
1526 assert_eq!(
1526 assert_eq!(
1527 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1527 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1528 VisitChildrenSet::This
1528 VisitChildrenSet::This
1529 );
1529 );
1530 assert_eq!(
1530 assert_eq!(
1531 matcher.visit_children_set(HgPath::new(b"folder")),
1531 matcher.visit_children_set(HgPath::new(b"folder")),
1532 VisitChildrenSet::Empty
1532 VisitChildrenSet::Empty
1533 );
1533 );
1534
1534
1535 // VisitchildrensetRootfilesin
1535 // VisitchildrensetRootfilesin
1536 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1536 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1537 PatternSyntax::RootFiles,
1537 PatternSyntax::RootFiles,
1538 b"dir/subdir",
1538 b"dir/subdir",
1539 Path::new(""),
1539 Path::new(""),
1540 )])
1540 )])
1541 .unwrap();
1541 .unwrap();
1542
1542
1543 let mut set = HashSet::new();
1543 let mut set = HashSet::new();
1544 set.insert(HgPathBuf::from_bytes(b"dir"));
1544 set.insert(HgPathBuf::from_bytes(b"dir"));
1545 assert_eq!(
1545 assert_eq!(
1546 matcher.visit_children_set(HgPath::new(b"")),
1546 matcher.visit_children_set(HgPath::new(b"")),
1547 VisitChildrenSet::Set(set)
1547 VisitChildrenSet::Set(set)
1548 );
1548 );
1549
1549
1550 let mut set = HashSet::new();
1550 let mut set = HashSet::new();
1551 set.insert(HgPathBuf::from_bytes(b"subdir"));
1551 set.insert(HgPathBuf::from_bytes(b"subdir"));
1552 assert_eq!(
1552 assert_eq!(
1553 matcher.visit_children_set(HgPath::new(b"dir")),
1553 matcher.visit_children_set(HgPath::new(b"dir")),
1554 VisitChildrenSet::Set(set)
1554 VisitChildrenSet::Set(set)
1555 );
1555 );
1556
1556
1557 assert_eq!(
1557 assert_eq!(
1558 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1558 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1559 VisitChildrenSet::This
1559 VisitChildrenSet::This
1560 );
1560 );
1561 assert_eq!(
1561 assert_eq!(
1562 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1562 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1563 VisitChildrenSet::Empty
1563 VisitChildrenSet::Empty
1564 );
1564 );
1565 assert_eq!(
1565 assert_eq!(
1566 matcher.visit_children_set(HgPath::new(b"folder")),
1566 matcher.visit_children_set(HgPath::new(b"folder")),
1567 VisitChildrenSet::Empty
1567 VisitChildrenSet::Empty
1568 );
1568 );
1569
1569
1570 // VisitchildrensetGlob
1570 // VisitchildrensetGlob
1571 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1571 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1572 PatternSyntax::Glob,
1572 PatternSyntax::Glob,
1573 b"dir/z*",
1573 b"dir/z*",
1574 Path::new(""),
1574 Path::new(""),
1575 )])
1575 )])
1576 .unwrap();
1576 .unwrap();
1577
1577
1578 let mut set = HashSet::new();
1578 let mut set = HashSet::new();
1579 set.insert(HgPathBuf::from_bytes(b"dir"));
1579 set.insert(HgPathBuf::from_bytes(b"dir"));
1580 assert_eq!(
1580 assert_eq!(
1581 matcher.visit_children_set(HgPath::new(b"")),
1581 matcher.visit_children_set(HgPath::new(b"")),
1582 VisitChildrenSet::Set(set)
1582 VisitChildrenSet::Set(set)
1583 );
1583 );
1584 assert_eq!(
1584 assert_eq!(
1585 matcher.visit_children_set(HgPath::new(b"folder")),
1585 matcher.visit_children_set(HgPath::new(b"folder")),
1586 VisitChildrenSet::Empty
1586 VisitChildrenSet::Empty
1587 );
1587 );
1588 assert_eq!(
1588 assert_eq!(
1589 matcher.visit_children_set(HgPath::new(b"dir")),
1589 matcher.visit_children_set(HgPath::new(b"dir")),
1590 VisitChildrenSet::This
1590 VisitChildrenSet::This
1591 );
1591 );
1592 // OPT: these should probably be set().
1592 // OPT: these should probably be set().
1593 assert_eq!(
1593 assert_eq!(
1594 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1594 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1595 VisitChildrenSet::This
1595 VisitChildrenSet::This
1596 );
1596 );
1597 assert_eq!(
1597 assert_eq!(
1598 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1598 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1599 VisitChildrenSet::This
1599 VisitChildrenSet::This
1600 );
1600 );
1601
1601
1602 // VisitchildrensetFilePath
1602 // VisitchildrensetFilePath
1603 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1603 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1604 PatternSyntax::FilePath,
1604 PatternSyntax::FilePath,
1605 b"dir/z",
1605 b"dir/z",
1606 Path::new(""),
1606 Path::new(""),
1607 )])
1607 )])
1608 .unwrap();
1608 .unwrap();
1609
1609
1610 let mut set = HashSet::new();
1610 let mut set = HashSet::new();
1611 set.insert(HgPathBuf::from_bytes(b"dir"));
1611 set.insert(HgPathBuf::from_bytes(b"dir"));
1612 assert_eq!(
1612 assert_eq!(
1613 matcher.visit_children_set(HgPath::new(b"")),
1613 matcher.visit_children_set(HgPath::new(b"")),
1614 VisitChildrenSet::Set(set)
1614 VisitChildrenSet::Set(set)
1615 );
1615 );
1616 assert_eq!(
1616 assert_eq!(
1617 matcher.visit_children_set(HgPath::new(b"folder")),
1617 matcher.visit_children_set(HgPath::new(b"folder")),
1618 VisitChildrenSet::Empty
1618 VisitChildrenSet::Empty
1619 );
1619 );
1620 let mut set = HashSet::new();
1620 let mut set = HashSet::new();
1621 set.insert(HgPathBuf::from_bytes(b"z"));
1621 set.insert(HgPathBuf::from_bytes(b"z"));
1622 assert_eq!(
1622 assert_eq!(
1623 matcher.visit_children_set(HgPath::new(b"dir")),
1623 matcher.visit_children_set(HgPath::new(b"dir")),
1624 VisitChildrenSet::Set(set)
1624 VisitChildrenSet::Set(set)
1625 );
1625 );
1626 // OPT: these should probably be set().
1626 // OPT: these should probably be set().
1627 assert_eq!(
1627 assert_eq!(
1628 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1628 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1629 VisitChildrenSet::Empty
1629 VisitChildrenSet::Empty
1630 );
1630 );
1631 assert_eq!(
1631 assert_eq!(
1632 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1632 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1633 VisitChildrenSet::Empty
1633 VisitChildrenSet::Empty
1634 );
1634 );
1635
1635
1636 // Test multiple patterns
1636 // Test multiple patterns
1637 let matcher = IncludeMatcher::new(vec![
1637 let matcher = IncludeMatcher::new(vec![
1638 IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")),
1638 IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")),
1639 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1639 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1640 ])
1640 ])
1641 .unwrap();
1641 .unwrap();
1642
1642
1643 assert_eq!(
1643 assert_eq!(
1644 matcher.visit_children_set(HgPath::new(b"")),
1644 matcher.visit_children_set(HgPath::new(b"")),
1645 VisitChildrenSet::This
1645 VisitChildrenSet::This
1646 );
1646 );
1647
1647
1648 // Test multiple patterns
1648 // Test multiple patterns
1649 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1649 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1650 PatternSyntax::Glob,
1650 PatternSyntax::Glob,
1651 b"**/*.exe",
1651 b"**/*.exe",
1652 Path::new(""),
1652 Path::new(""),
1653 )])
1653 )])
1654 .unwrap();
1654 .unwrap();
1655
1655
1656 assert_eq!(
1656 assert_eq!(
1657 matcher.visit_children_set(HgPath::new(b"")),
1657 matcher.visit_children_set(HgPath::new(b"")),
1658 VisitChildrenSet::This
1658 VisitChildrenSet::This
1659 );
1659 );
1660 }
1660 }
1661
1661
1662 #[test]
1662 #[test]
1663 fn test_unionmatcher() {
1663 fn test_unionmatcher() {
1664 // Path + Rootfiles
1664 // Path + Rootfiles
1665 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1665 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1666 PatternSyntax::RelPath,
1666 PatternSyntax::RelPath,
1667 b"dir/subdir",
1667 b"dir/subdir",
1668 Path::new(""),
1668 Path::new(""),
1669 )])
1669 )])
1670 .unwrap();
1670 .unwrap();
1671 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1671 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1672 PatternSyntax::RootFiles,
1672 PatternSyntax::RootFiles,
1673 b"dir",
1673 b"dir",
1674 Path::new(""),
1674 Path::new(""),
1675 )])
1675 )])
1676 .unwrap();
1676 .unwrap();
1677 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1677 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1678
1678
1679 let mut set = HashSet::new();
1679 let mut set = HashSet::new();
1680 set.insert(HgPathBuf::from_bytes(b"dir"));
1680 set.insert(HgPathBuf::from_bytes(b"dir"));
1681 assert_eq!(
1681 assert_eq!(
1682 matcher.visit_children_set(HgPath::new(b"")),
1682 matcher.visit_children_set(HgPath::new(b"")),
1683 VisitChildrenSet::Set(set)
1683 VisitChildrenSet::Set(set)
1684 );
1684 );
1685 assert_eq!(
1685 assert_eq!(
1686 matcher.visit_children_set(HgPath::new(b"dir")),
1686 matcher.visit_children_set(HgPath::new(b"dir")),
1687 VisitChildrenSet::This
1687 VisitChildrenSet::This
1688 );
1688 );
1689 assert_eq!(
1689 assert_eq!(
1690 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1690 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1691 VisitChildrenSet::Recursive
1691 VisitChildrenSet::Recursive
1692 );
1692 );
1693 assert_eq!(
1693 assert_eq!(
1694 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1694 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1695 VisitChildrenSet::Empty
1695 VisitChildrenSet::Empty
1696 );
1696 );
1697 assert_eq!(
1697 assert_eq!(
1698 matcher.visit_children_set(HgPath::new(b"folder")),
1698 matcher.visit_children_set(HgPath::new(b"folder")),
1699 VisitChildrenSet::Empty
1699 VisitChildrenSet::Empty
1700 );
1700 );
1701 assert_eq!(
1701 assert_eq!(
1702 matcher.visit_children_set(HgPath::new(b"folder")),
1702 matcher.visit_children_set(HgPath::new(b"folder")),
1703 VisitChildrenSet::Empty
1703 VisitChildrenSet::Empty
1704 );
1704 );
1705
1705
1706 // OPT: These next two could be 'all' instead of 'this'.
1706 // OPT: These next two could be 'all' instead of 'this'.
1707 assert_eq!(
1707 assert_eq!(
1708 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1708 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1709 VisitChildrenSet::This
1709 VisitChildrenSet::This
1710 );
1710 );
1711 assert_eq!(
1711 assert_eq!(
1712 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1712 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1713 VisitChildrenSet::This
1713 VisitChildrenSet::This
1714 );
1714 );
1715
1715
1716 // Path + unrelated Path
1716 // Path + unrelated Path
1717 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1717 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1718 PatternSyntax::RelPath,
1718 PatternSyntax::RelPath,
1719 b"dir/subdir",
1719 b"dir/subdir",
1720 Path::new(""),
1720 Path::new(""),
1721 )])
1721 )])
1722 .unwrap();
1722 .unwrap();
1723 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1723 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1724 PatternSyntax::RelPath,
1724 PatternSyntax::RelPath,
1725 b"folder",
1725 b"folder",
1726 Path::new(""),
1726 Path::new(""),
1727 )])
1727 )])
1728 .unwrap();
1728 .unwrap();
1729 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1729 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1730
1730
1731 let mut set = HashSet::new();
1731 let mut set = HashSet::new();
1732 set.insert(HgPathBuf::from_bytes(b"folder"));
1732 set.insert(HgPathBuf::from_bytes(b"folder"));
1733 set.insert(HgPathBuf::from_bytes(b"dir"));
1733 set.insert(HgPathBuf::from_bytes(b"dir"));
1734 assert_eq!(
1734 assert_eq!(
1735 matcher.visit_children_set(HgPath::new(b"")),
1735 matcher.visit_children_set(HgPath::new(b"")),
1736 VisitChildrenSet::Set(set)
1736 VisitChildrenSet::Set(set)
1737 );
1737 );
1738 let mut set = HashSet::new();
1738 let mut set = HashSet::new();
1739 set.insert(HgPathBuf::from_bytes(b"subdir"));
1739 set.insert(HgPathBuf::from_bytes(b"subdir"));
1740 assert_eq!(
1740 assert_eq!(
1741 matcher.visit_children_set(HgPath::new(b"dir")),
1741 matcher.visit_children_set(HgPath::new(b"dir")),
1742 VisitChildrenSet::Set(set)
1742 VisitChildrenSet::Set(set)
1743 );
1743 );
1744
1744
1745 assert_eq!(
1745 assert_eq!(
1746 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1746 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1747 VisitChildrenSet::Recursive
1747 VisitChildrenSet::Recursive
1748 );
1748 );
1749 assert_eq!(
1749 assert_eq!(
1750 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1750 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1751 VisitChildrenSet::Empty
1751 VisitChildrenSet::Empty
1752 );
1752 );
1753
1753
1754 assert_eq!(
1754 assert_eq!(
1755 matcher.visit_children_set(HgPath::new(b"folder")),
1755 matcher.visit_children_set(HgPath::new(b"folder")),
1756 VisitChildrenSet::Recursive
1756 VisitChildrenSet::Recursive
1757 );
1757 );
1758 // OPT: These next two could be 'all' instead of 'this'.
1758 // OPT: These next two could be 'all' instead of 'this'.
1759 assert_eq!(
1759 assert_eq!(
1760 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1760 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1761 VisitChildrenSet::This
1761 VisitChildrenSet::This
1762 );
1762 );
1763 assert_eq!(
1763 assert_eq!(
1764 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1764 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1765 VisitChildrenSet::This
1765 VisitChildrenSet::This
1766 );
1766 );
1767
1767
1768 // Path + subpath
1768 // Path + subpath
1769 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1769 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1770 PatternSyntax::RelPath,
1770 PatternSyntax::RelPath,
1771 b"dir/subdir/x",
1771 b"dir/subdir/x",
1772 Path::new(""),
1772 Path::new(""),
1773 )])
1773 )])
1774 .unwrap();
1774 .unwrap();
1775 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1775 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1776 PatternSyntax::RelPath,
1776 PatternSyntax::RelPath,
1777 b"dir/subdir",
1777 b"dir/subdir",
1778 Path::new(""),
1778 Path::new(""),
1779 )])
1779 )])
1780 .unwrap();
1780 .unwrap();
1781 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1781 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1782
1782
1783 let mut set = HashSet::new();
1783 let mut set = HashSet::new();
1784 set.insert(HgPathBuf::from_bytes(b"dir"));
1784 set.insert(HgPathBuf::from_bytes(b"dir"));
1785 assert_eq!(
1785 assert_eq!(
1786 matcher.visit_children_set(HgPath::new(b"")),
1786 matcher.visit_children_set(HgPath::new(b"")),
1787 VisitChildrenSet::Set(set)
1787 VisitChildrenSet::Set(set)
1788 );
1788 );
1789 let mut set = HashSet::new();
1789 let mut set = HashSet::new();
1790 set.insert(HgPathBuf::from_bytes(b"subdir"));
1790 set.insert(HgPathBuf::from_bytes(b"subdir"));
1791 assert_eq!(
1791 assert_eq!(
1792 matcher.visit_children_set(HgPath::new(b"dir")),
1792 matcher.visit_children_set(HgPath::new(b"dir")),
1793 VisitChildrenSet::Set(set)
1793 VisitChildrenSet::Set(set)
1794 );
1794 );
1795
1795
1796 assert_eq!(
1796 assert_eq!(
1797 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1797 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1798 VisitChildrenSet::Recursive
1798 VisitChildrenSet::Recursive
1799 );
1799 );
1800 assert_eq!(
1800 assert_eq!(
1801 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1801 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1802 VisitChildrenSet::Empty
1802 VisitChildrenSet::Empty
1803 );
1803 );
1804
1804
1805 assert_eq!(
1805 assert_eq!(
1806 matcher.visit_children_set(HgPath::new(b"folder")),
1806 matcher.visit_children_set(HgPath::new(b"folder")),
1807 VisitChildrenSet::Empty
1807 VisitChildrenSet::Empty
1808 );
1808 );
1809 assert_eq!(
1809 assert_eq!(
1810 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1810 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1811 VisitChildrenSet::Recursive
1811 VisitChildrenSet::Recursive
1812 );
1812 );
1813 // OPT: this should probably be 'all' not 'this'.
1813 // OPT: this should probably be 'all' not 'this'.
1814 assert_eq!(
1814 assert_eq!(
1815 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1815 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1816 VisitChildrenSet::This
1816 VisitChildrenSet::This
1817 );
1817 );
1818 }
1818 }
1819
1819
1820 #[test]
1820 #[test]
1821 fn test_intersectionmatcher() {
1821 fn test_intersectionmatcher() {
1822 // Include path + Include rootfiles
1822 // Include path + Include rootfiles
1823 let m1 = Box::new(
1823 let m1 = Box::new(
1824 IncludeMatcher::new(vec![IgnorePattern::new(
1824 IncludeMatcher::new(vec![IgnorePattern::new(
1825 PatternSyntax::RelPath,
1825 PatternSyntax::RelPath,
1826 b"dir/subdir",
1826 b"dir/subdir",
1827 Path::new(""),
1827 Path::new(""),
1828 )])
1828 )])
1829 .unwrap(),
1829 .unwrap(),
1830 );
1830 );
1831 let m2 = Box::new(
1831 let m2 = Box::new(
1832 IncludeMatcher::new(vec![IgnorePattern::new(
1832 IncludeMatcher::new(vec![IgnorePattern::new(
1833 PatternSyntax::RootFiles,
1833 PatternSyntax::RootFiles,
1834 b"dir",
1834 b"dir",
1835 Path::new(""),
1835 Path::new(""),
1836 )])
1836 )])
1837 .unwrap(),
1837 .unwrap(),
1838 );
1838 );
1839 let matcher = IntersectionMatcher::new(m1, m2);
1839 let matcher = IntersectionMatcher::new(m1, m2);
1840
1840
1841 let mut set = HashSet::new();
1841 let mut set = HashSet::new();
1842 set.insert(HgPathBuf::from_bytes(b"dir"));
1842 set.insert(HgPathBuf::from_bytes(b"dir"));
1843 assert_eq!(
1843 assert_eq!(
1844 matcher.visit_children_set(HgPath::new(b"")),
1844 matcher.visit_children_set(HgPath::new(b"")),
1845 VisitChildrenSet::Set(set)
1845 VisitChildrenSet::Set(set)
1846 );
1846 );
1847 assert_eq!(
1847 assert_eq!(
1848 matcher.visit_children_set(HgPath::new(b"dir")),
1848 matcher.visit_children_set(HgPath::new(b"dir")),
1849 VisitChildrenSet::This
1849 VisitChildrenSet::This
1850 );
1850 );
1851 assert_eq!(
1851 assert_eq!(
1852 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1852 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1853 VisitChildrenSet::Empty
1853 VisitChildrenSet::Empty
1854 );
1854 );
1855 assert_eq!(
1855 assert_eq!(
1856 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1856 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1857 VisitChildrenSet::Empty
1857 VisitChildrenSet::Empty
1858 );
1858 );
1859 assert_eq!(
1859 assert_eq!(
1860 matcher.visit_children_set(HgPath::new(b"folder")),
1860 matcher.visit_children_set(HgPath::new(b"folder")),
1861 VisitChildrenSet::Empty
1861 VisitChildrenSet::Empty
1862 );
1862 );
1863 assert_eq!(
1863 assert_eq!(
1864 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1864 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1865 VisitChildrenSet::Empty
1865 VisitChildrenSet::Empty
1866 );
1866 );
1867 assert_eq!(
1867 assert_eq!(
1868 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1868 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1869 VisitChildrenSet::Empty
1869 VisitChildrenSet::Empty
1870 );
1870 );
1871
1871
1872 // Non intersecting paths
1872 // Non intersecting paths
1873 let m1 = Box::new(
1873 let m1 = Box::new(
1874 IncludeMatcher::new(vec![IgnorePattern::new(
1874 IncludeMatcher::new(vec![IgnorePattern::new(
1875 PatternSyntax::RelPath,
1875 PatternSyntax::RelPath,
1876 b"dir/subdir",
1876 b"dir/subdir",
1877 Path::new(""),
1877 Path::new(""),
1878 )])
1878 )])
1879 .unwrap(),
1879 .unwrap(),
1880 );
1880 );
1881 let m2 = Box::new(
1881 let m2 = Box::new(
1882 IncludeMatcher::new(vec![IgnorePattern::new(
1882 IncludeMatcher::new(vec![IgnorePattern::new(
1883 PatternSyntax::RelPath,
1883 PatternSyntax::RelPath,
1884 b"folder",
1884 b"folder",
1885 Path::new(""),
1885 Path::new(""),
1886 )])
1886 )])
1887 .unwrap(),
1887 .unwrap(),
1888 );
1888 );
1889 let matcher = IntersectionMatcher::new(m1, m2);
1889 let matcher = IntersectionMatcher::new(m1, m2);
1890
1890
1891 assert_eq!(
1891 assert_eq!(
1892 matcher.visit_children_set(HgPath::new(b"")),
1892 matcher.visit_children_set(HgPath::new(b"")),
1893 VisitChildrenSet::Empty
1893 VisitChildrenSet::Empty
1894 );
1894 );
1895 assert_eq!(
1895 assert_eq!(
1896 matcher.visit_children_set(HgPath::new(b"dir")),
1896 matcher.visit_children_set(HgPath::new(b"dir")),
1897 VisitChildrenSet::Empty
1897 VisitChildrenSet::Empty
1898 );
1898 );
1899 assert_eq!(
1899 assert_eq!(
1900 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1900 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1901 VisitChildrenSet::Empty
1901 VisitChildrenSet::Empty
1902 );
1902 );
1903 assert_eq!(
1903 assert_eq!(
1904 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1904 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1905 VisitChildrenSet::Empty
1905 VisitChildrenSet::Empty
1906 );
1906 );
1907 assert_eq!(
1907 assert_eq!(
1908 matcher.visit_children_set(HgPath::new(b"folder")),
1908 matcher.visit_children_set(HgPath::new(b"folder")),
1909 VisitChildrenSet::Empty
1909 VisitChildrenSet::Empty
1910 );
1910 );
1911 assert_eq!(
1911 assert_eq!(
1912 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1912 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1913 VisitChildrenSet::Empty
1913 VisitChildrenSet::Empty
1914 );
1914 );
1915 assert_eq!(
1915 assert_eq!(
1916 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1916 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1917 VisitChildrenSet::Empty
1917 VisitChildrenSet::Empty
1918 );
1918 );
1919
1919
1920 // Nested paths
1920 // Nested paths
1921 let m1 = Box::new(
1921 let m1 = Box::new(
1922 IncludeMatcher::new(vec![IgnorePattern::new(
1922 IncludeMatcher::new(vec![IgnorePattern::new(
1923 PatternSyntax::RelPath,
1923 PatternSyntax::RelPath,
1924 b"dir/subdir/x",
1924 b"dir/subdir/x",
1925 Path::new(""),
1925 Path::new(""),
1926 )])
1926 )])
1927 .unwrap(),
1927 .unwrap(),
1928 );
1928 );
1929 let m2 = Box::new(
1929 let m2 = Box::new(
1930 IncludeMatcher::new(vec![IgnorePattern::new(
1930 IncludeMatcher::new(vec![IgnorePattern::new(
1931 PatternSyntax::RelPath,
1931 PatternSyntax::RelPath,
1932 b"dir/subdir",
1932 b"dir/subdir",
1933 Path::new(""),
1933 Path::new(""),
1934 )])
1934 )])
1935 .unwrap(),
1935 .unwrap(),
1936 );
1936 );
1937 let matcher = IntersectionMatcher::new(m1, m2);
1937 let matcher = IntersectionMatcher::new(m1, m2);
1938
1938
1939 let mut set = HashSet::new();
1939 let mut set = HashSet::new();
1940 set.insert(HgPathBuf::from_bytes(b"dir"));
1940 set.insert(HgPathBuf::from_bytes(b"dir"));
1941 assert_eq!(
1941 assert_eq!(
1942 matcher.visit_children_set(HgPath::new(b"")),
1942 matcher.visit_children_set(HgPath::new(b"")),
1943 VisitChildrenSet::Set(set)
1943 VisitChildrenSet::Set(set)
1944 );
1944 );
1945
1945
1946 let mut set = HashSet::new();
1946 let mut set = HashSet::new();
1947 set.insert(HgPathBuf::from_bytes(b"subdir"));
1947 set.insert(HgPathBuf::from_bytes(b"subdir"));
1948 assert_eq!(
1948 assert_eq!(
1949 matcher.visit_children_set(HgPath::new(b"dir")),
1949 matcher.visit_children_set(HgPath::new(b"dir")),
1950 VisitChildrenSet::Set(set)
1950 VisitChildrenSet::Set(set)
1951 );
1951 );
1952 let mut set = HashSet::new();
1952 let mut set = HashSet::new();
1953 set.insert(HgPathBuf::from_bytes(b"x"));
1953 set.insert(HgPathBuf::from_bytes(b"x"));
1954 assert_eq!(
1954 assert_eq!(
1955 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1955 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1956 VisitChildrenSet::Set(set)
1956 VisitChildrenSet::Set(set)
1957 );
1957 );
1958 assert_eq!(
1958 assert_eq!(
1959 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1959 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1960 VisitChildrenSet::Empty
1960 VisitChildrenSet::Empty
1961 );
1961 );
1962 assert_eq!(
1962 assert_eq!(
1963 matcher.visit_children_set(HgPath::new(b"folder")),
1963 matcher.visit_children_set(HgPath::new(b"folder")),
1964 VisitChildrenSet::Empty
1964 VisitChildrenSet::Empty
1965 );
1965 );
1966 assert_eq!(
1966 assert_eq!(
1967 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1967 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1968 VisitChildrenSet::Empty
1968 VisitChildrenSet::Empty
1969 );
1969 );
1970 // OPT: this should probably be 'all' not 'this'.
1970 // OPT: this should probably be 'all' not 'this'.
1971 assert_eq!(
1971 assert_eq!(
1972 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1972 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1973 VisitChildrenSet::This
1973 VisitChildrenSet::This
1974 );
1974 );
1975
1975
1976 // Diverging paths
1976 // Diverging paths
1977 let m1 = Box::new(
1977 let m1 = Box::new(
1978 IncludeMatcher::new(vec![IgnorePattern::new(
1978 IncludeMatcher::new(vec![IgnorePattern::new(
1979 PatternSyntax::RelPath,
1979 PatternSyntax::RelPath,
1980 b"dir/subdir/x",
1980 b"dir/subdir/x",
1981 Path::new(""),
1981 Path::new(""),
1982 )])
1982 )])
1983 .unwrap(),
1983 .unwrap(),
1984 );
1984 );
1985 let m2 = Box::new(
1985 let m2 = Box::new(
1986 IncludeMatcher::new(vec![IgnorePattern::new(
1986 IncludeMatcher::new(vec![IgnorePattern::new(
1987 PatternSyntax::RelPath,
1987 PatternSyntax::RelPath,
1988 b"dir/subdir/z",
1988 b"dir/subdir/z",
1989 Path::new(""),
1989 Path::new(""),
1990 )])
1990 )])
1991 .unwrap(),
1991 .unwrap(),
1992 );
1992 );
1993 let matcher = IntersectionMatcher::new(m1, m2);
1993 let matcher = IntersectionMatcher::new(m1, m2);
1994
1994
1995 // OPT: these next two could probably be Empty as well.
1995 // OPT: these next two could probably be Empty as well.
1996 let mut set = HashSet::new();
1996 let mut set = HashSet::new();
1997 set.insert(HgPathBuf::from_bytes(b"dir"));
1997 set.insert(HgPathBuf::from_bytes(b"dir"));
1998 assert_eq!(
1998 assert_eq!(
1999 matcher.visit_children_set(HgPath::new(b"")),
1999 matcher.visit_children_set(HgPath::new(b"")),
2000 VisitChildrenSet::Set(set)
2000 VisitChildrenSet::Set(set)
2001 );
2001 );
2002 // OPT: these next two could probably be Empty as well.
2002 // OPT: these next two could probably be Empty as well.
2003 let mut set = HashSet::new();
2003 let mut set = HashSet::new();
2004 set.insert(HgPathBuf::from_bytes(b"subdir"));
2004 set.insert(HgPathBuf::from_bytes(b"subdir"));
2005 assert_eq!(
2005 assert_eq!(
2006 matcher.visit_children_set(HgPath::new(b"dir")),
2006 matcher.visit_children_set(HgPath::new(b"dir")),
2007 VisitChildrenSet::Set(set)
2007 VisitChildrenSet::Set(set)
2008 );
2008 );
2009 assert_eq!(
2009 assert_eq!(
2010 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
2010 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
2011 VisitChildrenSet::Empty
2011 VisitChildrenSet::Empty
2012 );
2012 );
2013 assert_eq!(
2013 assert_eq!(
2014 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2014 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2015 VisitChildrenSet::Empty
2015 VisitChildrenSet::Empty
2016 );
2016 );
2017 assert_eq!(
2017 assert_eq!(
2018 matcher.visit_children_set(HgPath::new(b"folder")),
2018 matcher.visit_children_set(HgPath::new(b"folder")),
2019 VisitChildrenSet::Empty
2019 VisitChildrenSet::Empty
2020 );
2020 );
2021 assert_eq!(
2021 assert_eq!(
2022 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2022 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2023 VisitChildrenSet::Empty
2023 VisitChildrenSet::Empty
2024 );
2024 );
2025 assert_eq!(
2025 assert_eq!(
2026 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2026 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2027 VisitChildrenSet::Empty
2027 VisitChildrenSet::Empty
2028 );
2028 );
2029 }
2029 }
2030
2030
2031 #[test]
2031 #[test]
2032 fn test_differencematcher() {
2032 fn test_differencematcher() {
2033 // Two alwaysmatchers should function like a nevermatcher
2033 // Two alwaysmatchers should function like a nevermatcher
2034 let m1 = AlwaysMatcher;
2034 let m1 = AlwaysMatcher;
2035 let m2 = AlwaysMatcher;
2035 let m2 = AlwaysMatcher;
2036 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2036 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2037
2037
2038 for case in &[
2038 for case in &[
2039 &b""[..],
2039 &b""[..],
2040 b"dir",
2040 b"dir",
2041 b"dir/subdir",
2041 b"dir/subdir",
2042 b"dir/subdir/z",
2042 b"dir/subdir/z",
2043 b"dir/foo",
2043 b"dir/foo",
2044 b"dir/subdir/x",
2044 b"dir/subdir/x",
2045 b"folder",
2045 b"folder",
2046 ] {
2046 ] {
2047 assert_eq!(
2047 assert_eq!(
2048 matcher.visit_children_set(HgPath::new(case)),
2048 matcher.visit_children_set(HgPath::new(case)),
2049 VisitChildrenSet::Empty
2049 VisitChildrenSet::Empty
2050 );
2050 );
2051 }
2051 }
2052
2052
2053 // One always and one never should behave the same as an always
2053 // One always and one never should behave the same as an always
2054 let m1 = AlwaysMatcher;
2054 let m1 = AlwaysMatcher;
2055 let m2 = NeverMatcher;
2055 let m2 = NeverMatcher;
2056 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2056 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2057
2057
2058 for case in &[
2058 for case in &[
2059 &b""[..],
2059 &b""[..],
2060 b"dir",
2060 b"dir",
2061 b"dir/subdir",
2061 b"dir/subdir",
2062 b"dir/subdir/z",
2062 b"dir/subdir/z",
2063 b"dir/foo",
2063 b"dir/foo",
2064 b"dir/subdir/x",
2064 b"dir/subdir/x",
2065 b"folder",
2065 b"folder",
2066 ] {
2066 ] {
2067 assert_eq!(
2067 assert_eq!(
2068 matcher.visit_children_set(HgPath::new(case)),
2068 matcher.visit_children_set(HgPath::new(case)),
2069 VisitChildrenSet::Recursive
2069 VisitChildrenSet::Recursive
2070 );
2070 );
2071 }
2071 }
2072
2072
2073 // Two include matchers
2073 // Two include matchers
2074 let m1 = Box::new(
2074 let m1 = Box::new(
2075 IncludeMatcher::new(vec![IgnorePattern::new(
2075 IncludeMatcher::new(vec![IgnorePattern::new(
2076 PatternSyntax::RelPath,
2076 PatternSyntax::RelPath,
2077 b"dir/subdir",
2077 b"dir/subdir",
2078 Path::new("/repo"),
2078 Path::new("/repo"),
2079 )])
2079 )])
2080 .unwrap(),
2080 .unwrap(),
2081 );
2081 );
2082 let m2 = Box::new(
2082 let m2 = Box::new(
2083 IncludeMatcher::new(vec![IgnorePattern::new(
2083 IncludeMatcher::new(vec![IgnorePattern::new(
2084 PatternSyntax::RootFiles,
2084 PatternSyntax::RootFiles,
2085 b"dir",
2085 b"dir",
2086 Path::new("/repo"),
2086 Path::new("/repo"),
2087 )])
2087 )])
2088 .unwrap(),
2088 .unwrap(),
2089 );
2089 );
2090
2090
2091 let matcher = DifferenceMatcher::new(m1, m2);
2091 let matcher = DifferenceMatcher::new(m1, m2);
2092
2092
2093 let mut set = HashSet::new();
2093 let mut set = HashSet::new();
2094 set.insert(HgPathBuf::from_bytes(b"dir"));
2094 set.insert(HgPathBuf::from_bytes(b"dir"));
2095 assert_eq!(
2095 assert_eq!(
2096 matcher.visit_children_set(HgPath::new(b"")),
2096 matcher.visit_children_set(HgPath::new(b"")),
2097 VisitChildrenSet::Set(set)
2097 VisitChildrenSet::Set(set)
2098 );
2098 );
2099
2099
2100 let mut set = HashSet::new();
2100 let mut set = HashSet::new();
2101 set.insert(HgPathBuf::from_bytes(b"subdir"));
2101 set.insert(HgPathBuf::from_bytes(b"subdir"));
2102 assert_eq!(
2102 assert_eq!(
2103 matcher.visit_children_set(HgPath::new(b"dir")),
2103 matcher.visit_children_set(HgPath::new(b"dir")),
2104 VisitChildrenSet::Set(set)
2104 VisitChildrenSet::Set(set)
2105 );
2105 );
2106 assert_eq!(
2106 assert_eq!(
2107 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
2107 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
2108 VisitChildrenSet::Recursive
2108 VisitChildrenSet::Recursive
2109 );
2109 );
2110 assert_eq!(
2110 assert_eq!(
2111 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2111 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2112 VisitChildrenSet::Empty
2112 VisitChildrenSet::Empty
2113 );
2113 );
2114 assert_eq!(
2114 assert_eq!(
2115 matcher.visit_children_set(HgPath::new(b"folder")),
2115 matcher.visit_children_set(HgPath::new(b"folder")),
2116 VisitChildrenSet::Empty
2116 VisitChildrenSet::Empty
2117 );
2117 );
2118 assert_eq!(
2118 assert_eq!(
2119 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2119 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2120 VisitChildrenSet::This
2120 VisitChildrenSet::This
2121 );
2121 );
2122 assert_eq!(
2122 assert_eq!(
2123 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2123 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2124 VisitChildrenSet::This
2124 VisitChildrenSet::This
2125 );
2125 );
2126 }
2126 }
2127
2127
2128 mod invariants {
2128 mod invariants {
2129 pub mod visit_children_set {
2129 pub mod visit_children_set {
2130
2130
2131 use crate::{
2131 use crate::{
2132 matchers::{tests::Tree, Matcher, VisitChildrenSet},
2132 matchers::{tests::Tree, Matcher, VisitChildrenSet},
2133 utils::hg_path::HgPath,
2133 utils::hg_path::HgPath,
2134 };
2134 };
2135
2135
2136 #[allow(dead_code)]
2136 #[allow(dead_code)]
2137 #[derive(Debug)]
2137 #[derive(Debug)]
2138 struct Error<'a, M> {
2138 struct Error<'a, M> {
2139 matcher: &'a M,
2139 matcher: &'a M,
2140 path: &'a HgPath,
2140 path: &'a HgPath,
2141 matching: &'a Tree,
2141 matching: &'a Tree,
2142 visit_children_set: &'a VisitChildrenSet,
2142 visit_children_set: &'a VisitChildrenSet,
2143 }
2143 }
2144
2144
2145 fn holds(matching: &Tree, vcs: &VisitChildrenSet) -> bool {
2145 fn holds(matching: &Tree, vcs: &VisitChildrenSet) -> bool {
2146 match vcs {
2146 match vcs {
2147 VisitChildrenSet::Empty => matching.is_empty(),
2147 VisitChildrenSet::Empty => matching.is_empty(),
2148 VisitChildrenSet::This => {
2148 VisitChildrenSet::This => {
2149 // `This` does not come with any obligations.
2149 // `This` does not come with any obligations.
2150 true
2150 true
2151 }
2151 }
2152 VisitChildrenSet::Recursive => {
2152 VisitChildrenSet::Recursive => {
2153 // `Recursive` does not come with any correctness
2153 // `Recursive` does not come with any correctness
2154 // obligations.
2154 // obligations.
2155 // It instructs the caller to stop calling
2155 // It instructs the caller to stop calling
2156 // `visit_children_set` for all
2156 // `visit_children_set` for all
2157 // descendants, so may have negative performance
2157 // descendants, so may have negative performance
2158 // implications, but we're not testing against that
2158 // implications, but we're not testing against that
2159 // here.
2159 // here.
2160 true
2160 true
2161 }
2161 }
2162 VisitChildrenSet::Set(allowed_children) => {
2162 VisitChildrenSet::Set(allowed_children) => {
2163 // `allowed_children` does not distinguish between
2163 // `allowed_children` does not distinguish between
2164 // files and directories: if it's not included, it
2164 // files and directories: if it's not included, it
2165 // must not be matched.
2165 // must not be matched.
2166 for k in matching.dirs.keys() {
2166 for k in matching.dirs.keys() {
2167 if !(allowed_children.contains(k)) {
2167 if !(allowed_children.contains(k)) {
2168 return false;
2168 return false;
2169 }
2169 }
2170 }
2170 }
2171 for k in matching.files.iter() {
2171 for k in matching.files.iter() {
2172 if !(allowed_children.contains(k)) {
2172 if !(allowed_children.contains(k)) {
2173 return false;
2173 return false;
2174 }
2174 }
2175 }
2175 }
2176 true
2176 true
2177 }
2177 }
2178 }
2178 }
2179 }
2179 }
2180
2180
2181 pub fn check<M: Matcher + std::fmt::Debug>(
2181 pub fn check<M: Matcher + std::fmt::Debug>(
2182 matcher: &M,
2182 matcher: &M,
2183 path: &HgPath,
2183 path: &HgPath,
2184 matching: &Tree,
2184 matching: &Tree,
2185 visit_children_set: &VisitChildrenSet,
2185 visit_children_set: &VisitChildrenSet,
2186 ) {
2186 ) {
2187 if !holds(matching, visit_children_set) {
2187 if !holds(matching, visit_children_set) {
2188 panic!(
2188 panic!(
2189 "{:#?}",
2189 "{:#?}",
2190 Error {
2190 Error {
2191 matcher,
2191 matcher,
2192 path,
2192 path,
2193 visit_children_set,
2193 visit_children_set,
2194 matching
2194 matching
2195 }
2195 }
2196 )
2196 )
2197 }
2197 }
2198 }
2198 }
2199 }
2199 }
2200 }
2200 }
2201
2201
2202 #[derive(Debug, Clone)]
2202 #[derive(Debug, Clone)]
2203 pub struct Tree {
2203 pub struct Tree {
2204 files: BTreeSet<HgPathBuf>,
2204 files: BTreeSet<HgPathBuf>,
2205 dirs: BTreeMap<HgPathBuf, Tree>,
2205 dirs: BTreeMap<HgPathBuf, Tree>,
2206 }
2206 }
2207
2207
2208 impl Tree {
2208 impl Tree {
2209 fn len(&self) -> usize {
2209 fn len(&self) -> usize {
2210 let mut n = 0;
2210 let mut n = 0;
2211 n += self.files.len();
2211 n += self.files.len();
2212 for d in self.dirs.values() {
2212 for d in self.dirs.values() {
2213 n += d.len();
2213 n += d.len();
2214 }
2214 }
2215 n
2215 n
2216 }
2216 }
2217
2217
2218 fn is_empty(&self) -> bool {
2218 fn is_empty(&self) -> bool {
2219 self.files.is_empty() && self.dirs.is_empty()
2219 self.files.is_empty() && self.dirs.is_empty()
2220 }
2220 }
2221
2221
2222 fn filter_and_check<M: Matcher + Debug>(
2222 fn filter_and_check<M: Matcher + Debug>(
2223 &self,
2223 &self,
2224 m: &M,
2224 m: &M,
2225 path: &HgPath,
2225 path: &HgPath,
2226 ) -> Self {
2226 ) -> Self {
2227 let files: BTreeSet<HgPathBuf> = self
2227 let files: BTreeSet<HgPathBuf> = self
2228 .files
2228 .files
2229 .iter()
2229 .iter()
2230 .filter(|v| m.matches(&path.join(v)))
2230 .filter(|v| m.matches(&path.join(v)))
2231 .map(|f| f.to_owned())
2231 .map(|f| f.to_owned())
2232 .collect();
2232 .collect();
2233 let dirs: BTreeMap<HgPathBuf, Tree> = self
2233 let dirs: BTreeMap<HgPathBuf, Tree> = self
2234 .dirs
2234 .dirs
2235 .iter()
2235 .iter()
2236 .filter_map(|(k, v)| {
2236 .filter_map(|(k, v)| {
2237 let path = path.join(k);
2237 let path = path.join(k);
2238 let v = v.filter_and_check(m, &path);
2238 let v = v.filter_and_check(m, &path);
2239 if v.is_empty() {
2239 if v.is_empty() {
2240 None
2240 None
2241 } else {
2241 } else {
2242 Some((k.to_owned(), v))
2242 Some((k.to_owned(), v))
2243 }
2243 }
2244 })
2244 })
2245 .collect();
2245 .collect();
2246 let matching = Self { files, dirs };
2246 let matching = Self { files, dirs };
2247 let vcs = m.visit_children_set(path);
2247 let vcs = m.visit_children_set(path);
2248 invariants::visit_children_set::check(m, path, &matching, &vcs);
2248 invariants::visit_children_set::check(m, path, &matching, &vcs);
2249 matching
2249 matching
2250 }
2250 }
2251
2251
2252 fn check_matcher<M: Matcher + Debug>(
2252 fn check_matcher<M: Matcher + Debug>(
2253 &self,
2253 &self,
2254 m: &M,
2254 m: &M,
2255 expect_count: usize,
2255 expect_count: usize,
2256 ) {
2256 ) {
2257 let res = self.filter_and_check(m, &HgPathBuf::new());
2257 let res = self.filter_and_check(m, &HgPathBuf::new());
2258 if expect_count != res.len() {
2258 if expect_count != res.len() {
2259 eprintln!(
2259 eprintln!(
2260 "warning: expected {} matches, got {} for {:#?}",
2260 "warning: expected {} matches, got {} for {:#?}",
2261 expect_count,
2261 expect_count,
2262 res.len(),
2262 res.len(),
2263 m
2263 m
2264 );
2264 );
2265 }
2265 }
2266 }
2266 }
2267 }
2267 }
2268
2268
2269 fn mkdir(children: &[(&[u8], &Tree)]) -> Tree {
2269 fn mkdir(children: &[(&[u8], &Tree)]) -> Tree {
2270 let p = HgPathBuf::from_bytes;
2270 let p = HgPathBuf::from_bytes;
2271 let names = [
2271 let names = [
2272 p(b"a"),
2272 p(b"a"),
2273 p(b"b.txt"),
2273 p(b"b.txt"),
2274 p(b"file.txt"),
2274 p(b"file.txt"),
2275 p(b"c.c"),
2275 p(b"c.c"),
2276 p(b"c.h"),
2276 p(b"c.h"),
2277 p(b"dir1"),
2277 p(b"dir1"),
2278 p(b"dir2"),
2278 p(b"dir2"),
2279 p(b"subdir"),
2279 p(b"subdir"),
2280 ];
2280 ];
2281 let files: BTreeSet<HgPathBuf> = BTreeSet::from(names);
2281 let files: BTreeSet<HgPathBuf> = BTreeSet::from(names);
2282 let dirs = children
2282 let dirs = children
2283 .iter()
2283 .iter()
2284 .map(|(name, t)| (p(name), (*t).clone()))
2284 .map(|(name, t)| (p(name), (*t).clone()))
2285 .collect();
2285 .collect();
2286 Tree { files, dirs }
2286 Tree { files, dirs }
2287 }
2287 }
2288
2288
2289 fn make_example_tree() -> Tree {
2289 fn make_example_tree() -> Tree {
2290 let leaf = mkdir(&[]);
2290 let leaf = mkdir(&[]);
2291 let abc = mkdir(&[(b"d", &leaf)]);
2291 let abc = mkdir(&[(b"d", &leaf)]);
2292 let ab = mkdir(&[(b"c", &abc)]);
2292 let ab = mkdir(&[(b"c", &abc)]);
2293 let a = mkdir(&[(b"b", &ab)]);
2293 let a = mkdir(&[(b"b", &ab)]);
2294 let dir = mkdir(&[(b"subdir", &leaf), (b"subdir.c", &leaf)]);
2294 let dir = mkdir(&[(b"subdir", &leaf), (b"subdir.c", &leaf)]);
2295 mkdir(&[(b"dir", &dir), (b"dir1", &dir), (b"dir2", &dir), (b"a", &a)])
2295 mkdir(&[(b"dir", &dir), (b"dir1", &dir), (b"dir2", &dir), (b"a", &a)])
2296 }
2296 }
2297
2297
2298 #[test]
2298 #[test]
2299 fn test_pattern_matcher_visit_children_set() {
2299 fn test_pattern_matcher_visit_children_set() {
2300 let tree = make_example_tree();
2300 let tree = make_example_tree();
2301 let _pattern_dir1_glob_c =
2301 let pattern_dir1_glob_c =
2302 PatternMatcher::new(vec![IgnorePattern::new(
2302 PatternMatcher::new(vec![IgnorePattern::new(
2303 PatternSyntax::Glob,
2303 PatternSyntax::Glob,
2304 b"dir1/*.c",
2304 b"dir1/*.c",
2305 Path::new(""),
2305 Path::new(""),
2306 )])
2306 )])
2307 .unwrap();
2307 .unwrap();
2308 let pattern_dir1 = || {
2308 let pattern_dir1 = || {
2309 PatternMatcher::new(vec![IgnorePattern::new(
2309 PatternMatcher::new(vec![IgnorePattern::new(
2310 PatternSyntax::Path,
2310 PatternSyntax::Path,
2311 b"dir1",
2311 b"dir1",
2312 Path::new(""),
2312 Path::new(""),
2313 )])
2313 )])
2314 .unwrap()
2314 .unwrap()
2315 };
2315 };
2316 let pattern_dir1_a = PatternMatcher::new(vec![IgnorePattern::new(
2316 let pattern_dir1_a = PatternMatcher::new(vec![IgnorePattern::new(
2317 PatternSyntax::Glob,
2317 PatternSyntax::Glob,
2318 b"dir1/a",
2318 b"dir1/a",
2319 Path::new(""),
2319 Path::new(""),
2320 )])
2320 )])
2321 .unwrap();
2321 .unwrap();
2322 let pattern_relglob_c = || {
2322 let pattern_relglob_c = || {
2323 PatternMatcher::new(vec![IgnorePattern::new(
2323 PatternMatcher::new(vec![IgnorePattern::new(
2324 PatternSyntax::RelGlob,
2324 PatternSyntax::RelGlob,
2325 b"*.c",
2325 b"*.c",
2326 Path::new(""),
2326 Path::new(""),
2327 )])
2327 )])
2328 .unwrap()
2328 .unwrap()
2329 };
2329 };
2330 // // TODO: re-enable this test when the corresponding bug is
2331 // fixed if false {
2332 // tree.check_matcher(&pattern_dir1_glob_c);
2333 // }
2334 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/b.txt")];
2330 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/b.txt")];
2335 let file_dir_subdir_b = FileMatcher::new(files).unwrap();
2331 let file_dir_subdir_b = FileMatcher::new(files).unwrap();
2336
2332
2337 let files = vec![
2333 let files = vec![
2338 HgPathBuf::from_bytes(b"file.txt"),
2334 HgPathBuf::from_bytes(b"file.txt"),
2339 HgPathBuf::from_bytes(b"a/file.txt"),
2335 HgPathBuf::from_bytes(b"a/file.txt"),
2340 HgPathBuf::from_bytes(b"a/b/file.txt"),
2336 HgPathBuf::from_bytes(b"a/b/file.txt"),
2341 // No file in a/b/c
2337 // No file in a/b/c
2342 HgPathBuf::from_bytes(b"a/b/c/d/file.txt"),
2338 HgPathBuf::from_bytes(b"a/b/c/d/file.txt"),
2343 ];
2339 ];
2344 let file_abcdfile = FileMatcher::new(files).unwrap();
2340 let file_abcdfile = FileMatcher::new(files).unwrap();
2345 let _rootfilesin_dir = PatternMatcher::new(vec![IgnorePattern::new(
2341 let _rootfilesin_dir = PatternMatcher::new(vec![IgnorePattern::new(
2346 PatternSyntax::RootFiles,
2342 PatternSyntax::RootFiles,
2347 b"dir",
2343 b"dir",
2348 Path::new(""),
2344 Path::new(""),
2349 )])
2345 )])
2350 .unwrap();
2346 .unwrap();
2351
2347
2352 let pattern_filepath_dir_subdir =
2348 let pattern_filepath_dir_subdir =
2353 PatternMatcher::new(vec![IgnorePattern::new(
2349 PatternMatcher::new(vec![IgnorePattern::new(
2354 PatternSyntax::FilePath,
2350 PatternSyntax::FilePath,
2355 b"dir/subdir",
2351 b"dir/subdir",
2356 Path::new(""),
2352 Path::new(""),
2357 )])
2353 )])
2358 .unwrap();
2354 .unwrap();
2359
2355
2360 let include_dir_subdir =
2356 let include_dir_subdir =
2361 IncludeMatcher::new(vec![IgnorePattern::new(
2357 IncludeMatcher::new(vec![IgnorePattern::new(
2362 PatternSyntax::RelPath,
2358 PatternSyntax::RelPath,
2363 b"dir/subdir",
2359 b"dir/subdir",
2364 Path::new(""),
2360 Path::new(""),
2365 )])
2361 )])
2366 .unwrap();
2362 .unwrap();
2367
2363
2368 let more_includematchers = [
2364 let more_includematchers = [
2369 IncludeMatcher::new(vec![IgnorePattern::new(
2365 IncludeMatcher::new(vec![IgnorePattern::new(
2370 PatternSyntax::Glob,
2366 PatternSyntax::Glob,
2371 b"dir/s*",
2367 b"dir/s*",
2372 Path::new(""),
2368 Path::new(""),
2373 )])
2369 )])
2374 .unwrap(),
2370 .unwrap(),
2375 // Test multiple patterns
2371 // Test multiple patterns
2376 IncludeMatcher::new(vec![
2372 IncludeMatcher::new(vec![
2377 IgnorePattern::new(
2373 IgnorePattern::new(
2378 PatternSyntax::RelPath,
2374 PatternSyntax::RelPath,
2379 b"dir",
2375 b"dir",
2380 Path::new(""),
2376 Path::new(""),
2381 ),
2377 ),
2382 IgnorePattern::new(PatternSyntax::Glob, b"s*", Path::new("")),
2378 IgnorePattern::new(PatternSyntax::Glob, b"s*", Path::new("")),
2383 ])
2379 ])
2384 .unwrap(),
2380 .unwrap(),
2385 // Test multiple patterns
2381 // Test multiple patterns
2386 IncludeMatcher::new(vec![IgnorePattern::new(
2382 IncludeMatcher::new(vec![IgnorePattern::new(
2387 PatternSyntax::Glob,
2383 PatternSyntax::Glob,
2388 b"**/*.c",
2384 b"**/*.c",
2389 Path::new(""),
2385 Path::new(""),
2390 )])
2386 )])
2391 .unwrap(),
2387 .unwrap(),
2392 ];
2388 ];
2393
2389
2394 tree.check_matcher(&pattern_dir1(), 25);
2390 tree.check_matcher(&pattern_dir1(), 25);
2395 tree.check_matcher(&pattern_dir1_a, 1);
2391 tree.check_matcher(&pattern_dir1_a, 1);
2392 tree.check_matcher(&pattern_dir1_glob_c, 2);
2396 tree.check_matcher(&pattern_relglob_c(), 14);
2393 tree.check_matcher(&pattern_relglob_c(), 14);
2397 tree.check_matcher(&AlwaysMatcher, 112);
2394 tree.check_matcher(&AlwaysMatcher, 112);
2398 tree.check_matcher(&NeverMatcher, 0);
2395 tree.check_matcher(&NeverMatcher, 0);
2399 tree.check_matcher(
2396 tree.check_matcher(
2400 &IntersectionMatcher::new(
2397 &IntersectionMatcher::new(
2401 Box::new(pattern_relglob_c()),
2398 Box::new(pattern_relglob_c()),
2402 Box::new(pattern_dir1()),
2399 Box::new(pattern_dir1()),
2403 ),
2400 ),
2404 3,
2401 3,
2405 );
2402 );
2406 tree.check_matcher(
2403 tree.check_matcher(
2407 &UnionMatcher::new(vec![
2404 &UnionMatcher::new(vec![
2408 Box::new(pattern_relglob_c()),
2405 Box::new(pattern_relglob_c()),
2409 Box::new(pattern_dir1()),
2406 Box::new(pattern_dir1()),
2410 ]),
2407 ]),
2411 36,
2408 36,
2412 );
2409 );
2413 tree.check_matcher(
2410 tree.check_matcher(
2414 &DifferenceMatcher::new(
2411 &DifferenceMatcher::new(
2415 Box::new(pattern_relglob_c()),
2412 Box::new(pattern_relglob_c()),
2416 Box::new(pattern_dir1()),
2413 Box::new(pattern_dir1()),
2417 ),
2414 ),
2418 11,
2415 11,
2419 );
2416 );
2420 tree.check_matcher(&file_dir_subdir_b, 1);
2417 tree.check_matcher(&file_dir_subdir_b, 1);
2421 tree.check_matcher(&file_abcdfile, 4);
2418 tree.check_matcher(&file_abcdfile, 4);
2422 // // TODO: re-enable this test when the corresponding bug is
2419 // // TODO: re-enable this test when the corresponding bug is
2423 // fixed
2420 // fixed
2424 //
2421 //
2425 // if false {
2422 // if false {
2426 // tree.check_matcher(&rootfilesin_dir, 6);
2423 // tree.check_matcher(&rootfilesin_dir, 6);
2427 // }
2424 // }
2428 tree.check_matcher(&pattern_filepath_dir_subdir, 1);
2425 tree.check_matcher(&pattern_filepath_dir_subdir, 1);
2429 tree.check_matcher(&include_dir_subdir, 9);
2426 tree.check_matcher(&include_dir_subdir, 9);
2430 tree.check_matcher(&more_includematchers[0], 17);
2427 tree.check_matcher(&more_includematchers[0], 17);
2431 tree.check_matcher(&more_includematchers[1], 25);
2428 tree.check_matcher(&more_includematchers[1], 25);
2432 tree.check_matcher(&more_includematchers[2], 35);
2429 tree.check_matcher(&more_includematchers[2], 35);
2433 }
2430 }
2434 }
2431 }
@@ -1,436 +1,440 b''
1 // files.rs
1 // files.rs
2 //
2 //
3 // Copyright 2019
3 // Copyright 2019
4 // Raphaël Gomès <rgomes@octobus.net>,
4 // Raphaël Gomès <rgomes@octobus.net>,
5 // Yuya Nishihara <yuya@tcha.org>
5 // Yuya Nishihara <yuya@tcha.org>
6 //
6 //
7 // This software may be used and distributed according to the terms of the
7 // This software may be used and distributed according to the terms of the
8 // GNU General Public License version 2 or any later version.
8 // GNU General Public License version 2 or any later version.
9
9
10 //! Functions for fiddling with files.
10 //! Functions for fiddling with files.
11
11
12 use crate::utils::{
12 use crate::utils::{
13 hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError},
13 hg_path::{path_to_hg_path_buf, HgPath, HgPathBuf, HgPathError},
14 path_auditor::PathAuditor,
14 path_auditor::PathAuditor,
15 replace_slice,
15 replace_slice,
16 };
16 };
17 use lazy_static::lazy_static;
17 use lazy_static::lazy_static;
18 use same_file::is_same_file;
18 use same_file::is_same_file;
19 use std::borrow::{Cow, ToOwned};
19 use std::borrow::{Cow, ToOwned};
20 use std::ffi::{OsStr, OsString};
20 use std::ffi::{OsStr, OsString};
21 use std::iter::FusedIterator;
21 use std::iter::FusedIterator;
22 use std::ops::Deref;
22 use std::ops::Deref;
23 use std::path::{Path, PathBuf};
23 use std::path::{Path, PathBuf};
24
24
25 pub fn get_os_str_from_bytes(bytes: &[u8]) -> &OsStr {
25 pub fn get_os_str_from_bytes(bytes: &[u8]) -> &OsStr {
26 let os_str;
26 let os_str;
27 #[cfg(unix)]
27 #[cfg(unix)]
28 {
28 {
29 use std::os::unix::ffi::OsStrExt;
29 use std::os::unix::ffi::OsStrExt;
30 os_str = std::ffi::OsStr::from_bytes(bytes);
30 os_str = std::ffi::OsStr::from_bytes(bytes);
31 }
31 }
32 // TODO Handle other platforms
32 // TODO Handle other platforms
33 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
33 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
34 // Perhaps, the return type would have to be Result<PathBuf>.
34 // Perhaps, the return type would have to be Result<PathBuf>.
35 os_str
35 os_str
36 }
36 }
37
37
38 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
38 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
39 Path::new(get_os_str_from_bytes(bytes))
39 Path::new(get_os_str_from_bytes(bytes))
40 }
40 }
41
41
42 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
42 // TODO: need to convert from WTF8 to MBCS bytes on Windows.
43 // that's why Vec<u8> is returned.
43 // that's why Vec<u8> is returned.
44 #[cfg(unix)]
44 #[cfg(unix)]
45 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
45 pub fn get_bytes_from_path(path: impl AsRef<Path>) -> Vec<u8> {
46 get_bytes_from_os_str(path.as_ref())
46 get_bytes_from_os_str(path.as_ref())
47 }
47 }
48
48
49 #[cfg(unix)]
49 #[cfg(unix)]
50 pub fn get_bytes_from_os_str(str: impl AsRef<OsStr>) -> Vec<u8> {
50 pub fn get_bytes_from_os_str(str: impl AsRef<OsStr>) -> Vec<u8> {
51 use std::os::unix::ffi::OsStrExt;
51 use std::os::unix::ffi::OsStrExt;
52 str.as_ref().as_bytes().to_vec()
52 str.as_ref().as_bytes().to_vec()
53 }
53 }
54
54
55 #[cfg(unix)]
55 #[cfg(unix)]
56 pub fn get_bytes_from_os_string(str: OsString) -> Vec<u8> {
56 pub fn get_bytes_from_os_string(str: OsString) -> Vec<u8> {
57 use std::os::unix::ffi::OsStringExt;
57 use std::os::unix::ffi::OsStringExt;
58 str.into_vec()
58 str.into_vec()
59 }
59 }
60
60
61 /// An iterator over repository path yielding itself and its ancestors.
61 /// An iterator over repository path yielding itself and its ancestors.
62 #[derive(Copy, Clone, Debug)]
62 #[derive(Copy, Clone, Debug)]
63 pub struct Ancestors<'a> {
63 pub struct Ancestors<'a> {
64 next: Option<&'a HgPath>,
64 next: Option<&'a HgPath>,
65 }
65 }
66
66
67 impl<'a> Iterator for Ancestors<'a> {
67 impl<'a> Iterator for Ancestors<'a> {
68 type Item = &'a HgPath;
68 type Item = &'a HgPath;
69
69
70 fn next(&mut self) -> Option<Self::Item> {
70 fn next(&mut self) -> Option<Self::Item> {
71 let next = self.next;
71 let next = self.next;
72 self.next = match self.next {
72 self.next = match self.next {
73 Some(s) if s.is_empty() => None,
73 Some(s) if s.is_empty() => None,
74 Some(s) => {
74 Some(s) => {
75 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
75 let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
76 Some(HgPath::new(&s.as_bytes()[..p]))
76 Some(HgPath::new(&s.as_bytes()[..p]))
77 }
77 }
78 None => None,
78 None => None,
79 };
79 };
80 next
80 next
81 }
81 }
82 }
82 }
83
83
84 impl<'a> FusedIterator for Ancestors<'a> {}
84 impl<'a> FusedIterator for Ancestors<'a> {}
85
85
86 /// An iterator over repository path yielding itself and its ancestors.
86 /// An iterator over repository path yielding itself and its ancestors.
87 #[derive(Copy, Clone, Debug)]
87 #[derive(Copy, Clone, Debug)]
88 pub(crate) struct AncestorsWithBase<'a> {
88 pub(crate) struct AncestorsWithBase<'a> {
89 next: Option<(&'a HgPath, &'a HgPath)>,
89 next: Option<(&'a HgPath, &'a HgPath)>,
90 }
90 }
91
91
92 impl<'a> Iterator for AncestorsWithBase<'a> {
92 impl<'a> Iterator for AncestorsWithBase<'a> {
93 type Item = (&'a HgPath, &'a HgPath);
93 type Item = (&'a HgPath, &'a HgPath);
94
94
95 fn next(&mut self) -> Option<Self::Item> {
95 fn next(&mut self) -> Option<Self::Item> {
96 let next = self.next;
96 let next = self.next;
97 self.next = match self.next {
97 self.next = match self.next {
98 Some((s, _)) if s.is_empty() => None,
98 Some((s, _)) if s.is_empty() => None,
99 Some((s, _)) => Some(s.split_filename()),
99 Some((s, _)) => Some(s.split_filename()),
100 None => None,
100 None => None,
101 };
101 };
102 next
102 next
103 }
103 }
104 }
104 }
105
105
106 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
106 impl<'a> FusedIterator for AncestorsWithBase<'a> {}
107
107
108 /// Returns an iterator yielding ancestor directories of the given repository
108 /// Returns an iterator yielding ancestor directories of the given repository
109 /// path.
109 /// path.
110 ///
110 ///
111 /// The path is separated by '/', and must not start with '/'.
111 /// The path is separated by '/', and must not start with '/'.
112 ///
112 ///
113 /// The path itself isn't included unless it is b"" (meaning the root
113 /// The path itself isn't included unless it is b"" (meaning the root
114 /// directory.)
114 /// directory.)
115 pub fn find_dirs(path: &HgPath) -> Ancestors {
115 pub fn find_dirs(path: &HgPath) -> Ancestors {
116 let mut dirs = Ancestors { next: Some(path) };
116 let mut dirs = Ancestors { next: Some(path) };
117 if !path.is_empty() {
117 if !path.is_empty() {
118 dirs.next(); // skip itself
118 dirs.next(); // skip itself
119 }
119 }
120 dirs
120 dirs
121 }
121 }
122
122
123 pub fn dir_ancestors(path: &HgPath) -> Ancestors {
124 Ancestors { next: Some(path) }
125 }
126
123 /// Returns an iterator yielding ancestor directories of the given repository
127 /// Returns an iterator yielding ancestor directories of the given repository
124 /// path.
128 /// path.
125 ///
129 ///
126 /// The path is separated by '/', and must not start with '/'.
130 /// The path is separated by '/', and must not start with '/'.
127 ///
131 ///
128 /// The path itself isn't included unless it is b"" (meaning the root
132 /// The path itself isn't included unless it is b"" (meaning the root
129 /// directory.)
133 /// directory.)
130 pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase {
134 pub(crate) fn find_dirs_with_base(path: &HgPath) -> AncestorsWithBase {
131 let mut dirs = AncestorsWithBase {
135 let mut dirs = AncestorsWithBase {
132 next: Some((path, HgPath::new(b""))),
136 next: Some((path, HgPath::new(b""))),
133 };
137 };
134 if !path.is_empty() {
138 if !path.is_empty() {
135 dirs.next(); // skip itself
139 dirs.next(); // skip itself
136 }
140 }
137 dirs
141 dirs
138 }
142 }
139
143
140 /// TODO more than ASCII?
144 /// TODO more than ASCII?
141 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
145 pub fn normalize_case(path: &HgPath) -> HgPathBuf {
142 #[cfg(windows)] // NTFS compares via upper()
146 #[cfg(windows)] // NTFS compares via upper()
143 return path.to_ascii_uppercase();
147 return path.to_ascii_uppercase();
144 #[cfg(unix)]
148 #[cfg(unix)]
145 path.to_ascii_lowercase()
149 path.to_ascii_lowercase()
146 }
150 }
147
151
148 lazy_static! {
152 lazy_static! {
149 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
153 static ref IGNORED_CHARS: Vec<Vec<u8>> = {
150 [
154 [
151 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
155 0x200c, 0x200d, 0x200e, 0x200f, 0x202a, 0x202b, 0x202c, 0x202d,
152 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
156 0x202e, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0xfeff,
153 ]
157 ]
154 .iter()
158 .iter()
155 .map(|code| {
159 .map(|code| {
156 std::char::from_u32(*code)
160 std::char::from_u32(*code)
157 .unwrap()
161 .unwrap()
158 .encode_utf8(&mut [0; 3])
162 .encode_utf8(&mut [0; 3])
159 .bytes()
163 .bytes()
160 .collect()
164 .collect()
161 })
165 })
162 .collect()
166 .collect()
163 };
167 };
164 }
168 }
165
169
166 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
170 fn hfs_ignore_clean(bytes: &[u8]) -> Vec<u8> {
167 let mut buf = bytes.to_owned();
171 let mut buf = bytes.to_owned();
168 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
172 let needs_escaping = bytes.iter().any(|b| *b == b'\xe2' || *b == b'\xef');
169 if needs_escaping {
173 if needs_escaping {
170 for forbidden in IGNORED_CHARS.iter() {
174 for forbidden in IGNORED_CHARS.iter() {
171 replace_slice(&mut buf, forbidden, &[])
175 replace_slice(&mut buf, forbidden, &[])
172 }
176 }
173 buf
177 buf
174 } else {
178 } else {
175 buf
179 buf
176 }
180 }
177 }
181 }
178
182
179 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
183 pub fn lower_clean(bytes: &[u8]) -> Vec<u8> {
180 hfs_ignore_clean(&bytes.to_ascii_lowercase())
184 hfs_ignore_clean(&bytes.to_ascii_lowercase())
181 }
185 }
182
186
183 /// Returns the canonical path of `name`, given `cwd` and `root`
187 /// Returns the canonical path of `name`, given `cwd` and `root`
184 pub fn canonical_path(
188 pub fn canonical_path(
185 root: impl AsRef<Path>,
189 root: impl AsRef<Path>,
186 cwd: impl AsRef<Path>,
190 cwd: impl AsRef<Path>,
187 name: impl AsRef<Path>,
191 name: impl AsRef<Path>,
188 ) -> Result<PathBuf, HgPathError> {
192 ) -> Result<PathBuf, HgPathError> {
189 // TODO add missing normalization for other platforms
193 // TODO add missing normalization for other platforms
190 let root = root.as_ref();
194 let root = root.as_ref();
191 let cwd = cwd.as_ref();
195 let cwd = cwd.as_ref();
192 let name = name.as_ref();
196 let name = name.as_ref();
193
197
194 let name = if !name.is_absolute() {
198 let name = if !name.is_absolute() {
195 root.join(cwd).join(name)
199 root.join(cwd).join(name)
196 } else {
200 } else {
197 name.to_owned()
201 name.to_owned()
198 };
202 };
199 let auditor = PathAuditor::new(root);
203 let auditor = PathAuditor::new(root);
200 if name != root && name.starts_with(root) {
204 if name != root && name.starts_with(root) {
201 let name = name.strip_prefix(root).unwrap();
205 let name = name.strip_prefix(root).unwrap();
202 auditor.audit_path(path_to_hg_path_buf(name)?)?;
206 auditor.audit_path(path_to_hg_path_buf(name)?)?;
203 Ok(name.to_owned())
207 Ok(name.to_owned())
204 } else if name == root {
208 } else if name == root {
205 Ok("".into())
209 Ok("".into())
206 } else {
210 } else {
207 // Determine whether `name' is in the hierarchy at or beneath `root',
211 // Determine whether `name' is in the hierarchy at or beneath `root',
208 // by iterating name=name.parent() until it returns `None` (can't
212 // by iterating name=name.parent() until it returns `None` (can't
209 // check name == '/', because that doesn't work on windows).
213 // check name == '/', because that doesn't work on windows).
210 let mut name = name.deref();
214 let mut name = name.deref();
211 let original_name = name.to_owned();
215 let original_name = name.to_owned();
212 loop {
216 loop {
213 let same = is_same_file(name, root).unwrap_or(false);
217 let same = is_same_file(name, root).unwrap_or(false);
214 if same {
218 if same {
215 if name == original_name {
219 if name == original_name {
216 // `name` was actually the same as root (maybe a symlink)
220 // `name` was actually the same as root (maybe a symlink)
217 return Ok("".into());
221 return Ok("".into());
218 }
222 }
219 // `name` is a symlink to root, so `original_name` is under
223 // `name` is a symlink to root, so `original_name` is under
220 // root
224 // root
221 let rel_path = original_name.strip_prefix(name).unwrap();
225 let rel_path = original_name.strip_prefix(name).unwrap();
222 auditor.audit_path(path_to_hg_path_buf(rel_path)?)?;
226 auditor.audit_path(path_to_hg_path_buf(rel_path)?)?;
223 return Ok(rel_path.to_owned());
227 return Ok(rel_path.to_owned());
224 }
228 }
225 name = match name.parent() {
229 name = match name.parent() {
226 None => break,
230 None => break,
227 Some(p) => p,
231 Some(p) => p,
228 };
232 };
229 }
233 }
230 // TODO hint to the user about using --cwd
234 // TODO hint to the user about using --cwd
231 // Bubble up the responsibility to Python for now
235 // Bubble up the responsibility to Python for now
232 Err(HgPathError::NotUnderRoot {
236 Err(HgPathError::NotUnderRoot {
233 path: original_name,
237 path: original_name,
234 root: root.to_owned(),
238 root: root.to_owned(),
235 })
239 })
236 }
240 }
237 }
241 }
238
242
239 /// Returns the representation of the path relative to the current working
243 /// Returns the representation of the path relative to the current working
240 /// directory for display purposes.
244 /// directory for display purposes.
241 ///
245 ///
242 /// `cwd` is a `HgPath`, so it is considered relative to the root directory
246 /// `cwd` is a `HgPath`, so it is considered relative to the root directory
243 /// of the repository.
247 /// of the repository.
244 ///
248 ///
245 /// # Examples
249 /// # Examples
246 ///
250 ///
247 /// ```
251 /// ```
248 /// use hg::utils::hg_path::HgPath;
252 /// use hg::utils::hg_path::HgPath;
249 /// use hg::utils::files::relativize_path;
253 /// use hg::utils::files::relativize_path;
250 /// use std::borrow::Cow;
254 /// use std::borrow::Cow;
251 ///
255 ///
252 /// let file = HgPath::new(b"nested/file");
256 /// let file = HgPath::new(b"nested/file");
253 /// let cwd = HgPath::new(b"");
257 /// let cwd = HgPath::new(b"");
254 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file"));
258 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file"));
255 ///
259 ///
256 /// let cwd = HgPath::new(b"nested");
260 /// let cwd = HgPath::new(b"nested");
257 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file"));
261 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file"));
258 ///
262 ///
259 /// let cwd = HgPath::new(b"other");
263 /// let cwd = HgPath::new(b"other");
260 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file"));
264 /// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file"));
261 /// ```
265 /// ```
262 pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> {
266 pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> {
263 if cwd.as_ref().is_empty() {
267 if cwd.as_ref().is_empty() {
264 Cow::Borrowed(path.as_bytes())
268 Cow::Borrowed(path.as_bytes())
265 } else {
269 } else {
266 // This is not all accurate as to how large `res` will actually be, but
270 // This is not all accurate as to how large `res` will actually be, but
267 // profiling `rhg files` on a large-ish repo shows it’s better than
271 // profiling `rhg files` on a large-ish repo shows it’s better than
268 // starting from a zero-capacity `Vec` and letting `extend` reallocate
272 // starting from a zero-capacity `Vec` and letting `extend` reallocate
269 // repeatedly.
273 // repeatedly.
270 let guesstimate = path.as_bytes().len();
274 let guesstimate = path.as_bytes().len();
271
275
272 let mut res: Vec<u8> = Vec::with_capacity(guesstimate);
276 let mut res: Vec<u8> = Vec::with_capacity(guesstimate);
273 let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable();
277 let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable();
274 let mut cwd_iter =
278 let mut cwd_iter =
275 cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable();
279 cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable();
276 loop {
280 loop {
277 match (path_iter.peek(), cwd_iter.peek()) {
281 match (path_iter.peek(), cwd_iter.peek()) {
278 (Some(a), Some(b)) if a == b => (),
282 (Some(a), Some(b)) if a == b => (),
279 _ => break,
283 _ => break,
280 }
284 }
281 path_iter.next();
285 path_iter.next();
282 cwd_iter.next();
286 cwd_iter.next();
283 }
287 }
284 let mut need_sep = false;
288 let mut need_sep = false;
285 for _ in cwd_iter {
289 for _ in cwd_iter {
286 if need_sep {
290 if need_sep {
287 res.extend(b"/")
291 res.extend(b"/")
288 } else {
292 } else {
289 need_sep = true
293 need_sep = true
290 };
294 };
291 res.extend(b"..");
295 res.extend(b"..");
292 }
296 }
293 for c in path_iter {
297 for c in path_iter {
294 if need_sep {
298 if need_sep {
295 res.extend(b"/")
299 res.extend(b"/")
296 } else {
300 } else {
297 need_sep = true
301 need_sep = true
298 };
302 };
299 res.extend(c);
303 res.extend(c);
300 }
304 }
301 Cow::Owned(res)
305 Cow::Owned(res)
302 }
306 }
303 }
307 }
304
308
305 #[cfg(test)]
309 #[cfg(test)]
306 mod tests {
310 mod tests {
307 use super::*;
311 use super::*;
308 use pretty_assertions::assert_eq;
312 use pretty_assertions::assert_eq;
309
313
310 #[test]
314 #[test]
311 fn find_dirs_some() {
315 fn find_dirs_some() {
312 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
316 let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
313 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
317 assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
314 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
318 assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
315 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
319 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
316 assert_eq!(dirs.next(), None);
320 assert_eq!(dirs.next(), None);
317 assert_eq!(dirs.next(), None);
321 assert_eq!(dirs.next(), None);
318 }
322 }
319
323
320 #[test]
324 #[test]
321 fn find_dirs_empty() {
325 fn find_dirs_empty() {
322 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
326 // looks weird, but mercurial.pathutil.finddirs(b"") yields b""
323 let mut dirs = super::find_dirs(HgPath::new(b""));
327 let mut dirs = super::find_dirs(HgPath::new(b""));
324 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
328 assert_eq!(dirs.next(), Some(HgPath::new(b"")));
325 assert_eq!(dirs.next(), None);
329 assert_eq!(dirs.next(), None);
326 assert_eq!(dirs.next(), None);
330 assert_eq!(dirs.next(), None);
327 }
331 }
328
332
329 #[test]
333 #[test]
330 fn test_find_dirs_with_base_some() {
334 fn test_find_dirs_with_base_some() {
331 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
335 let mut dirs = super::find_dirs_with_base(HgPath::new(b"foo/bar/baz"));
332 assert_eq!(
336 assert_eq!(
333 dirs.next(),
337 dirs.next(),
334 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
338 Some((HgPath::new(b"foo/bar"), HgPath::new(b"baz")))
335 );
339 );
336 assert_eq!(
340 assert_eq!(
337 dirs.next(),
341 dirs.next(),
338 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
342 Some((HgPath::new(b"foo"), HgPath::new(b"bar")))
339 );
343 );
340 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
344 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b"foo"))));
341 assert_eq!(dirs.next(), None);
345 assert_eq!(dirs.next(), None);
342 assert_eq!(dirs.next(), None);
346 assert_eq!(dirs.next(), None);
343 }
347 }
344
348
345 #[test]
349 #[test]
346 fn test_find_dirs_with_base_empty() {
350 fn test_find_dirs_with_base_empty() {
347 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
351 let mut dirs = super::find_dirs_with_base(HgPath::new(b""));
348 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
352 assert_eq!(dirs.next(), Some((HgPath::new(b""), HgPath::new(b""))));
349 assert_eq!(dirs.next(), None);
353 assert_eq!(dirs.next(), None);
350 assert_eq!(dirs.next(), None);
354 assert_eq!(dirs.next(), None);
351 }
355 }
352
356
353 #[test]
357 #[test]
354 fn test_canonical_path() {
358 fn test_canonical_path() {
355 let root = Path::new("/repo");
359 let root = Path::new("/repo");
356 let cwd = Path::new("/dir");
360 let cwd = Path::new("/dir");
357 let name = Path::new("filename");
361 let name = Path::new("filename");
358 assert_eq!(
362 assert_eq!(
359 canonical_path(root, cwd, name),
363 canonical_path(root, cwd, name),
360 Err(HgPathError::NotUnderRoot {
364 Err(HgPathError::NotUnderRoot {
361 path: PathBuf::from("/dir/filename"),
365 path: PathBuf::from("/dir/filename"),
362 root: root.to_path_buf()
366 root: root.to_path_buf()
363 })
367 })
364 );
368 );
365
369
366 let root = Path::new("/repo");
370 let root = Path::new("/repo");
367 let cwd = Path::new("/");
371 let cwd = Path::new("/");
368 let name = Path::new("filename");
372 let name = Path::new("filename");
369 assert_eq!(
373 assert_eq!(
370 canonical_path(root, cwd, name),
374 canonical_path(root, cwd, name),
371 Err(HgPathError::NotUnderRoot {
375 Err(HgPathError::NotUnderRoot {
372 path: PathBuf::from("/filename"),
376 path: PathBuf::from("/filename"),
373 root: root.to_path_buf()
377 root: root.to_path_buf()
374 })
378 })
375 );
379 );
376
380
377 let root = Path::new("/repo");
381 let root = Path::new("/repo");
378 let cwd = Path::new("/");
382 let cwd = Path::new("/");
379 let name = Path::new("repo/filename");
383 let name = Path::new("repo/filename");
380 assert_eq!(
384 assert_eq!(
381 canonical_path(root, cwd, name),
385 canonical_path(root, cwd, name),
382 Ok(PathBuf::from("filename"))
386 Ok(PathBuf::from("filename"))
383 );
387 );
384
388
385 let root = Path::new("/repo");
389 let root = Path::new("/repo");
386 let cwd = Path::new("/repo");
390 let cwd = Path::new("/repo");
387 let name = Path::new("filename");
391 let name = Path::new("filename");
388 assert_eq!(
392 assert_eq!(
389 canonical_path(root, cwd, name),
393 canonical_path(root, cwd, name),
390 Ok(PathBuf::from("filename"))
394 Ok(PathBuf::from("filename"))
391 );
395 );
392
396
393 let root = Path::new("/repo");
397 let root = Path::new("/repo");
394 let cwd = Path::new("/repo/subdir");
398 let cwd = Path::new("/repo/subdir");
395 let name = Path::new("filename");
399 let name = Path::new("filename");
396 assert_eq!(
400 assert_eq!(
397 canonical_path(root, cwd, name),
401 canonical_path(root, cwd, name),
398 Ok(PathBuf::from("subdir/filename"))
402 Ok(PathBuf::from("subdir/filename"))
399 );
403 );
400 }
404 }
401
405
402 #[test]
406 #[test]
403 fn test_canonical_path_not_rooted() {
407 fn test_canonical_path_not_rooted() {
404 use std::fs::create_dir;
408 use std::fs::create_dir;
405 use tempfile::tempdir;
409 use tempfile::tempdir;
406
410
407 let base_dir = tempdir().unwrap();
411 let base_dir = tempdir().unwrap();
408 let base_dir_path = base_dir.path();
412 let base_dir_path = base_dir.path();
409 let beneath_repo = base_dir_path.join("a");
413 let beneath_repo = base_dir_path.join("a");
410 let root = base_dir_path.join("a/b");
414 let root = base_dir_path.join("a/b");
411 let out_of_repo = base_dir_path.join("c");
415 let out_of_repo = base_dir_path.join("c");
412 let under_repo_symlink = out_of_repo.join("d");
416 let under_repo_symlink = out_of_repo.join("d");
413
417
414 create_dir(&beneath_repo).unwrap();
418 create_dir(&beneath_repo).unwrap();
415 create_dir(&root).unwrap();
419 create_dir(&root).unwrap();
416
420
417 // TODO make portable
421 // TODO make portable
418 std::os::unix::fs::symlink(&root, &out_of_repo).unwrap();
422 std::os::unix::fs::symlink(&root, &out_of_repo).unwrap();
419
423
420 assert_eq!(
424 assert_eq!(
421 canonical_path(&root, Path::new(""), out_of_repo),
425 canonical_path(&root, Path::new(""), out_of_repo),
422 Ok(PathBuf::from(""))
426 Ok(PathBuf::from(""))
423 );
427 );
424 assert_eq!(
428 assert_eq!(
425 canonical_path(&root, Path::new(""), &beneath_repo),
429 canonical_path(&root, Path::new(""), &beneath_repo),
426 Err(HgPathError::NotUnderRoot {
430 Err(HgPathError::NotUnderRoot {
427 path: beneath_repo,
431 path: beneath_repo,
428 root: root.to_owned()
432 root: root.to_owned()
429 })
433 })
430 );
434 );
431 assert_eq!(
435 assert_eq!(
432 canonical_path(&root, Path::new(""), under_repo_symlink),
436 canonical_path(&root, Path::new(""), under_repo_symlink),
433 Ok(PathBuf::from("d"))
437 Ok(PathBuf::from("d"))
434 );
438 );
435 }
439 }
436 }
440 }
@@ -1,1053 +1,1053 b''
1 #testcases dirstate-v1 dirstate-v2
1 #testcases dirstate-v1 dirstate-v2
2
2
3 #if dirstate-v2
3 #if dirstate-v2
4 $ cat >> $HGRCPATH << EOF
4 $ cat >> $HGRCPATH << EOF
5 > [format]
5 > [format]
6 > use-dirstate-v2=1
6 > use-dirstate-v2=1
7 > [storage]
7 > [storage]
8 > dirstate-v2.slow-path=allow
8 > dirstate-v2.slow-path=allow
9 > EOF
9 > EOF
10 #endif
10 #endif
11
11
12 $ hg init repo1
12 $ hg init repo1
13 $ cd repo1
13 $ cd repo1
14 $ mkdir a b a/1 b/1 b/2
14 $ mkdir a b a/1 b/1 b/2
15 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
15 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
16
16
17 hg status in repo root:
17 hg status in repo root:
18
18
19 $ hg status
19 $ hg status
20 ? a/1/in_a_1
20 ? a/1/in_a_1
21 ? a/in_a
21 ? a/in_a
22 ? b/1/in_b_1
22 ? b/1/in_b_1
23 ? b/2/in_b_2
23 ? b/2/in_b_2
24 ? b/in_b
24 ? b/in_b
25 ? in_root
25 ? in_root
26
26
27 hg status . in repo root:
27 hg status . in repo root:
28
28
29 $ hg status .
29 $ hg status .
30 ? a/1/in_a_1
30 ? a/1/in_a_1
31 ? a/in_a
31 ? a/in_a
32 ? b/1/in_b_1
32 ? b/1/in_b_1
33 ? b/2/in_b_2
33 ? b/2/in_b_2
34 ? b/in_b
34 ? b/in_b
35 ? in_root
35 ? in_root
36
36
37 $ hg status --cwd a
37 $ hg status --cwd a
38 ? a/1/in_a_1
38 ? a/1/in_a_1
39 ? a/in_a
39 ? a/in_a
40 ? b/1/in_b_1
40 ? b/1/in_b_1
41 ? b/2/in_b_2
41 ? b/2/in_b_2
42 ? b/in_b
42 ? b/in_b
43 ? in_root
43 ? in_root
44 $ hg status --cwd a .
44 $ hg status --cwd a .
45 ? 1/in_a_1
45 ? 1/in_a_1
46 ? in_a
46 ? in_a
47 $ hg status --cwd a ..
47 $ hg status --cwd a ..
48 ? 1/in_a_1
48 ? 1/in_a_1
49 ? in_a
49 ? in_a
50 ? ../b/1/in_b_1
50 ? ../b/1/in_b_1
51 ? ../b/2/in_b_2
51 ? ../b/2/in_b_2
52 ? ../b/in_b
52 ? ../b/in_b
53 ? ../in_root
53 ? ../in_root
54
54
55 $ hg status --cwd b
55 $ hg status --cwd b
56 ? a/1/in_a_1
56 ? a/1/in_a_1
57 ? a/in_a
57 ? a/in_a
58 ? b/1/in_b_1
58 ? b/1/in_b_1
59 ? b/2/in_b_2
59 ? b/2/in_b_2
60 ? b/in_b
60 ? b/in_b
61 ? in_root
61 ? in_root
62 $ hg status --cwd b .
62 $ hg status --cwd b .
63 ? 1/in_b_1
63 ? 1/in_b_1
64 ? 2/in_b_2
64 ? 2/in_b_2
65 ? in_b
65 ? in_b
66 $ hg status --cwd b ..
66 $ hg status --cwd b ..
67 ? ../a/1/in_a_1
67 ? ../a/1/in_a_1
68 ? ../a/in_a
68 ? ../a/in_a
69 ? 1/in_b_1
69 ? 1/in_b_1
70 ? 2/in_b_2
70 ? 2/in_b_2
71 ? in_b
71 ? in_b
72 ? ../in_root
72 ? ../in_root
73
73
74 $ hg status --cwd a/1
74 $ hg status --cwd a/1
75 ? a/1/in_a_1
75 ? a/1/in_a_1
76 ? a/in_a
76 ? a/in_a
77 ? b/1/in_b_1
77 ? b/1/in_b_1
78 ? b/2/in_b_2
78 ? b/2/in_b_2
79 ? b/in_b
79 ? b/in_b
80 ? in_root
80 ? in_root
81 $ hg status --cwd a/1 .
81 $ hg status --cwd a/1 .
82 ? in_a_1
82 ? in_a_1
83 $ hg status --cwd a/1 ..
83 $ hg status --cwd a/1 ..
84 ? in_a_1
84 ? in_a_1
85 ? ../in_a
85 ? ../in_a
86
86
87 $ hg status --cwd b/1
87 $ hg status --cwd b/1
88 ? a/1/in_a_1
88 ? a/1/in_a_1
89 ? a/in_a
89 ? a/in_a
90 ? b/1/in_b_1
90 ? b/1/in_b_1
91 ? b/2/in_b_2
91 ? b/2/in_b_2
92 ? b/in_b
92 ? b/in_b
93 ? in_root
93 ? in_root
94 $ hg status --cwd b/1 .
94 $ hg status --cwd b/1 .
95 ? in_b_1
95 ? in_b_1
96 $ hg status --cwd b/1 ..
96 $ hg status --cwd b/1 ..
97 ? in_b_1
97 ? in_b_1
98 ? ../2/in_b_2
98 ? ../2/in_b_2
99 ? ../in_b
99 ? ../in_b
100
100
101 $ hg status --cwd b/2
101 $ hg status --cwd b/2
102 ? a/1/in_a_1
102 ? a/1/in_a_1
103 ? a/in_a
103 ? a/in_a
104 ? b/1/in_b_1
104 ? b/1/in_b_1
105 ? b/2/in_b_2
105 ? b/2/in_b_2
106 ? b/in_b
106 ? b/in_b
107 ? in_root
107 ? in_root
108 $ hg status --cwd b/2 .
108 $ hg status --cwd b/2 .
109 ? in_b_2
109 ? in_b_2
110 $ hg status --cwd b/2 ..
110 $ hg status --cwd b/2 ..
111 ? ../1/in_b_1
111 ? ../1/in_b_1
112 ? in_b_2
112 ? in_b_2
113 ? ../in_b
113 ? ../in_b
114
114
115 combining patterns with root and patterns without a root works
115 combining patterns with root and patterns without a root works
116
116
117 $ hg st a/in_a re:.*b$
117 $ hg st a/in_a re:.*b$
118 ? a/in_a
118 ? a/in_a
119 ? b/in_b
119 ? b/in_b
120
120
121 tweaking defaults works
121 tweaking defaults works
122 $ hg status --cwd a --config ui.tweakdefaults=yes
122 $ hg status --cwd a --config ui.tweakdefaults=yes
123 ? 1/in_a_1
123 ? 1/in_a_1
124 ? in_a
124 ? in_a
125 ? ../b/1/in_b_1
125 ? ../b/1/in_b_1
126 ? ../b/2/in_b_2
126 ? ../b/2/in_b_2
127 ? ../b/in_b
127 ? ../b/in_b
128 ? ../in_root
128 ? ../in_root
129 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
129 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
130 ? a/1/in_a_1 (glob)
130 ? a/1/in_a_1 (glob)
131 ? a/in_a (glob)
131 ? a/in_a (glob)
132 ? b/1/in_b_1 (glob)
132 ? b/1/in_b_1 (glob)
133 ? b/2/in_b_2 (glob)
133 ? b/2/in_b_2 (glob)
134 ? b/in_b (glob)
134 ? b/in_b (glob)
135 ? in_root
135 ? in_root
136 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
136 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
137 ? 1/in_a_1
137 ? 1/in_a_1
138 ? in_a
138 ? in_a
139 ? ../b/1/in_b_1
139 ? ../b/1/in_b_1
140 ? ../b/2/in_b_2
140 ? ../b/2/in_b_2
141 ? ../b/in_b
141 ? ../b/in_b
142 ? ../in_root (glob)
142 ? ../in_root (glob)
143
143
144 relative paths can be requested
144 relative paths can be requested
145
145
146 $ hg status --cwd a --config ui.relative-paths=yes
146 $ hg status --cwd a --config ui.relative-paths=yes
147 ? 1/in_a_1
147 ? 1/in_a_1
148 ? in_a
148 ? in_a
149 ? ../b/1/in_b_1
149 ? ../b/1/in_b_1
150 ? ../b/2/in_b_2
150 ? ../b/2/in_b_2
151 ? ../b/in_b
151 ? ../b/in_b
152 ? ../in_root
152 ? ../in_root
153
153
154 $ hg status --cwd a . --config ui.relative-paths=legacy
154 $ hg status --cwd a . --config ui.relative-paths=legacy
155 ? 1/in_a_1
155 ? 1/in_a_1
156 ? in_a
156 ? in_a
157 $ hg status --cwd a . --config ui.relative-paths=no
157 $ hg status --cwd a . --config ui.relative-paths=no
158 ? a/1/in_a_1
158 ? a/1/in_a_1
159 ? a/in_a
159 ? a/in_a
160
160
161 commands.status.relative overrides ui.relative-paths
161 commands.status.relative overrides ui.relative-paths
162
162
163 $ cat >> $HGRCPATH <<EOF
163 $ cat >> $HGRCPATH <<EOF
164 > [ui]
164 > [ui]
165 > relative-paths = False
165 > relative-paths = False
166 > [commands]
166 > [commands]
167 > status.relative = True
167 > status.relative = True
168 > EOF
168 > EOF
169 $ hg status --cwd a
169 $ hg status --cwd a
170 ? 1/in_a_1
170 ? 1/in_a_1
171 ? in_a
171 ? in_a
172 ? ../b/1/in_b_1
172 ? ../b/1/in_b_1
173 ? ../b/2/in_b_2
173 ? ../b/2/in_b_2
174 ? ../b/in_b
174 ? ../b/in_b
175 ? ../in_root
175 ? ../in_root
176 $ HGPLAIN=1 hg status --cwd a
176 $ HGPLAIN=1 hg status --cwd a
177 ? a/1/in_a_1 (glob)
177 ? a/1/in_a_1 (glob)
178 ? a/in_a (glob)
178 ? a/in_a (glob)
179 ? b/1/in_b_1 (glob)
179 ? b/1/in_b_1 (glob)
180 ? b/2/in_b_2 (glob)
180 ? b/2/in_b_2 (glob)
181 ? b/in_b (glob)
181 ? b/in_b (glob)
182 ? in_root
182 ? in_root
183
183
184 if relative paths are explicitly off, tweakdefaults doesn't change it
184 if relative paths are explicitly off, tweakdefaults doesn't change it
185 $ cat >> $HGRCPATH <<EOF
185 $ cat >> $HGRCPATH <<EOF
186 > [commands]
186 > [commands]
187 > status.relative = False
187 > status.relative = False
188 > EOF
188 > EOF
189 $ hg status --cwd a --config ui.tweakdefaults=yes
189 $ hg status --cwd a --config ui.tweakdefaults=yes
190 ? a/1/in_a_1
190 ? a/1/in_a_1
191 ? a/in_a
191 ? a/in_a
192 ? b/1/in_b_1
192 ? b/1/in_b_1
193 ? b/2/in_b_2
193 ? b/2/in_b_2
194 ? b/in_b
194 ? b/in_b
195 ? in_root
195 ? in_root
196
196
197 $ cd ..
197 $ cd ..
198
198
199 $ hg init repo2
199 $ hg init repo2
200 $ cd repo2
200 $ cd repo2
201 $ touch modified removed deleted ignored
201 $ touch modified removed deleted ignored
202 $ echo "^ignored$" > .hgignore
202 $ echo "^ignored$" > .hgignore
203 $ hg ci -A -m 'initial checkin'
203 $ hg ci -A -m 'initial checkin'
204 adding .hgignore
204 adding .hgignore
205 adding deleted
205 adding deleted
206 adding modified
206 adding modified
207 adding removed
207 adding removed
208 $ touch modified added unknown ignored
208 $ touch modified added unknown ignored
209 $ hg add added
209 $ hg add added
210 $ hg remove removed
210 $ hg remove removed
211 $ rm deleted
211 $ rm deleted
212
212
213 hg status:
213 hg status:
214
214
215 $ hg status
215 $ hg status
216 A added
216 A added
217 R removed
217 R removed
218 ! deleted
218 ! deleted
219 ? unknown
219 ? unknown
220
220
221 hg status -n:
221 hg status -n:
222 $ env RHG_ON_UNSUPPORTED=abort hg status -n
222 $ env RHG_ON_UNSUPPORTED=abort hg status -n
223 added
223 added
224 removed
224 removed
225 deleted
225 deleted
226 unknown
226 unknown
227
227
228 hg status modified added removed deleted unknown never-existed ignored:
228 hg status modified added removed deleted unknown never-existed ignored:
229
229
230 $ hg status modified added removed deleted unknown never-existed ignored
230 $ hg status modified added removed deleted unknown never-existed ignored
231 never-existed: * (glob)
231 never-existed: * (glob)
232 A added
232 A added
233 R removed
233 R removed
234 ! deleted
234 ! deleted
235 ? unknown
235 ? unknown
236
236
237 $ hg copy modified copied
237 $ hg copy modified copied
238
238
239 hg status -C:
239 hg status -C:
240
240
241 $ hg status -C
241 $ hg status -C
242 A added
242 A added
243 A copied
243 A copied
244 modified
244 modified
245 R removed
245 R removed
246 ! deleted
246 ! deleted
247 ? unknown
247 ? unknown
248
248
249 hg status -0:
249 hg status -0:
250
250
251 $ hg status -0 --config rhg.on-unsupported=abort
251 $ hg status -0 --config rhg.on-unsupported=abort
252 A added\x00A copied\x00R removed\x00! deleted\x00? unknown\x00 (no-eol) (esc)
252 A added\x00A copied\x00R removed\x00! deleted\x00? unknown\x00 (no-eol) (esc)
253
253
254 hg status -A:
254 hg status -A:
255
255
256 $ hg status -A
256 $ hg status -A
257 A added
257 A added
258 A copied
258 A copied
259 modified
259 modified
260 R removed
260 R removed
261 ! deleted
261 ! deleted
262 ? unknown
262 ? unknown
263 I ignored
263 I ignored
264 C .hgignore
264 C .hgignore
265 C modified
265 C modified
266
266
267 $ hg status -A -T '{status} {path} {node|shortest}\n'
267 $ hg status -A -T '{status} {path} {node|shortest}\n'
268 A added ffff
268 A added ffff
269 A copied ffff
269 A copied ffff
270 R removed ffff
270 R removed ffff
271 ! deleted ffff
271 ! deleted ffff
272 ? unknown ffff
272 ? unknown ffff
273 I ignored ffff
273 I ignored ffff
274 C .hgignore ffff
274 C .hgignore ffff
275 C modified ffff
275 C modified ffff
276
276
277 $ hg status -A -Tjson
277 $ hg status -A -Tjson
278 [
278 [
279 {
279 {
280 "itemtype": "file",
280 "itemtype": "file",
281 "path": "added",
281 "path": "added",
282 "status": "A"
282 "status": "A"
283 },
283 },
284 {
284 {
285 "itemtype": "file",
285 "itemtype": "file",
286 "path": "copied",
286 "path": "copied",
287 "source": "modified",
287 "source": "modified",
288 "status": "A"
288 "status": "A"
289 },
289 },
290 {
290 {
291 "itemtype": "file",
291 "itemtype": "file",
292 "path": "removed",
292 "path": "removed",
293 "status": "R"
293 "status": "R"
294 },
294 },
295 {
295 {
296 "itemtype": "file",
296 "itemtype": "file",
297 "path": "deleted",
297 "path": "deleted",
298 "status": "!"
298 "status": "!"
299 },
299 },
300 {
300 {
301 "itemtype": "file",
301 "itemtype": "file",
302 "path": "unknown",
302 "path": "unknown",
303 "status": "?"
303 "status": "?"
304 },
304 },
305 {
305 {
306 "itemtype": "file",
306 "itemtype": "file",
307 "path": "ignored",
307 "path": "ignored",
308 "status": "I"
308 "status": "I"
309 },
309 },
310 {
310 {
311 "itemtype": "file",
311 "itemtype": "file",
312 "path": ".hgignore",
312 "path": ".hgignore",
313 "status": "C"
313 "status": "C"
314 },
314 },
315 {
315 {
316 "itemtype": "file",
316 "itemtype": "file",
317 "path": "modified",
317 "path": "modified",
318 "status": "C"
318 "status": "C"
319 }
319 }
320 ]
320 ]
321
321
322 $ hg status -A -Tpickle > pickle
322 $ hg status -A -Tpickle > pickle
323 >>> import pickle
323 >>> import pickle
324 >>> from mercurial import util
324 >>> from mercurial import util
325 >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
325 >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
326 >>> for s, p in data: print("%s %s" % (s, p))
326 >>> for s, p in data: print("%s %s" % (s, p))
327 ! deleted
327 ! deleted
328 ? pickle
328 ? pickle
329 ? unknown
329 ? unknown
330 A added
330 A added
331 A copied
331 A copied
332 C .hgignore
332 C .hgignore
333 C modified
333 C modified
334 I ignored
334 I ignored
335 R removed
335 R removed
336 $ rm pickle
336 $ rm pickle
337
337
338 $ echo "^ignoreddir$" > .hgignore
338 $ echo "^ignoreddir$" > .hgignore
339 $ mkdir ignoreddir
339 $ mkdir ignoreddir
340 $ touch ignoreddir/file
340 $ touch ignoreddir/file
341
341
342 Test templater support:
342 Test templater support:
343
343
344 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
344 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
345 [M] .hgignore
345 [M] .hgignore
346 [A] added
346 [A] added
347 [A] modified -> copied
347 [A] modified -> copied
348 [R] removed
348 [R] removed
349 [!] deleted
349 [!] deleted
350 [?] ignored
350 [?] ignored
351 [?] unknown
351 [?] unknown
352 [I] ignoreddir/file
352 [I] ignoreddir/file
353 [C] modified
353 [C] modified
354 $ hg status -AT default
354 $ hg status -AT default
355 M .hgignore
355 M .hgignore
356 A added
356 A added
357 A copied
357 A copied
358 modified
358 modified
359 R removed
359 R removed
360 ! deleted
360 ! deleted
361 ? ignored
361 ? ignored
362 ? unknown
362 ? unknown
363 I ignoreddir/file
363 I ignoreddir/file
364 C modified
364 C modified
365 $ hg status -T compact
365 $ hg status -T compact
366 abort: "status" not in template map
366 abort: "status" not in template map
367 [255]
367 [255]
368
368
369 hg status ignoreddir/file:
369 hg status ignoreddir/file:
370
370
371 $ hg status ignoreddir/file
371 $ hg status ignoreddir/file
372
372
373 hg status -i ignoreddir/file:
373 hg status -i ignoreddir/file:
374
374
375 $ hg status -i ignoreddir/file
375 $ hg status -i ignoreddir/file
376 I ignoreddir/file
376 I ignoreddir/file
377 $ cd ..
377 $ cd ..
378
378
379 Check 'status -q' and some combinations
379 Check 'status -q' and some combinations
380
380
381 $ hg init repo3
381 $ hg init repo3
382 $ cd repo3
382 $ cd repo3
383 $ touch modified removed deleted ignored
383 $ touch modified removed deleted ignored
384 $ echo "^ignored$" > .hgignore
384 $ echo "^ignored$" > .hgignore
385 $ hg commit -A -m 'initial checkin'
385 $ hg commit -A -m 'initial checkin'
386 adding .hgignore
386 adding .hgignore
387 adding deleted
387 adding deleted
388 adding modified
388 adding modified
389 adding removed
389 adding removed
390 $ touch added unknown ignored
390 $ touch added unknown ignored
391 $ hg add added
391 $ hg add added
392 $ echo "test" >> modified
392 $ echo "test" >> modified
393 $ hg remove removed
393 $ hg remove removed
394 $ rm deleted
394 $ rm deleted
395 $ hg copy modified copied
395 $ hg copy modified copied
396
396
397 Specify working directory revision explicitly, that should be the same as
397 Specify working directory revision explicitly, that should be the same as
398 "hg status"
398 "hg status"
399
399
400 $ hg status --change "wdir()"
400 $ hg status --change "wdir()"
401 M modified
401 M modified
402 A added
402 A added
403 A copied
403 A copied
404 R removed
404 R removed
405 ! deleted
405 ! deleted
406 ? unknown
406 ? unknown
407
407
408 Run status with 2 different flags.
408 Run status with 2 different flags.
409 Check if result is the same or different.
409 Check if result is the same or different.
410 If result is not as expected, raise error
410 If result is not as expected, raise error
411
411
412 $ assert() {
412 $ assert() {
413 > hg status $1 > ../a
413 > hg status $1 > ../a
414 > hg status $2 > ../b
414 > hg status $2 > ../b
415 > if diff ../a ../b > /dev/null; then
415 > if diff ../a ../b > /dev/null; then
416 > out=0
416 > out=0
417 > else
417 > else
418 > out=1
418 > out=1
419 > fi
419 > fi
420 > if [ $3 -eq 0 ]; then
420 > if [ $3 -eq 0 ]; then
421 > df="same"
421 > df="same"
422 > else
422 > else
423 > df="different"
423 > df="different"
424 > fi
424 > fi
425 > if [ $out -ne $3 ]; then
425 > if [ $out -ne $3 ]; then
426 > echo "Error on $1 and $2, should be $df."
426 > echo "Error on $1 and $2, should be $df."
427 > fi
427 > fi
428 > }
428 > }
429
429
430 Assert flag1 flag2 [0-same | 1-different]
430 Assert flag1 flag2 [0-same | 1-different]
431
431
432 $ assert "-q" "-mard" 0
432 $ assert "-q" "-mard" 0
433 $ assert "-A" "-marduicC" 0
433 $ assert "-A" "-marduicC" 0
434 $ assert "-qA" "-mardcC" 0
434 $ assert "-qA" "-mardcC" 0
435 $ assert "-qAui" "-A" 0
435 $ assert "-qAui" "-A" 0
436 $ assert "-qAu" "-marducC" 0
436 $ assert "-qAu" "-marducC" 0
437 $ assert "-qAi" "-mardicC" 0
437 $ assert "-qAi" "-mardicC" 0
438 $ assert "-qu" "-u" 0
438 $ assert "-qu" "-u" 0
439 $ assert "-q" "-u" 1
439 $ assert "-q" "-u" 1
440 $ assert "-m" "-a" 1
440 $ assert "-m" "-a" 1
441 $ assert "-r" "-d" 1
441 $ assert "-r" "-d" 1
442 $ cd ..
442 $ cd ..
443
443
444 $ hg init repo4
444 $ hg init repo4
445 $ cd repo4
445 $ cd repo4
446 $ touch modified removed deleted
446 $ touch modified removed deleted
447 $ hg ci -q -A -m 'initial checkin'
447 $ hg ci -q -A -m 'initial checkin'
448 $ touch added unknown
448 $ touch added unknown
449 $ hg add added
449 $ hg add added
450 $ hg remove removed
450 $ hg remove removed
451 $ rm deleted
451 $ rm deleted
452 $ echo x > modified
452 $ echo x > modified
453 $ hg copy modified copied
453 $ hg copy modified copied
454 $ hg ci -m 'test checkin' -d "1000001 0"
454 $ hg ci -m 'test checkin' -d "1000001 0"
455 $ rm *
455 $ rm *
456 $ touch unrelated
456 $ touch unrelated
457 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
457 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
458
458
459 hg status --change 1:
459 hg status --change 1:
460
460
461 $ hg status --change 1
461 $ hg status --change 1
462 M modified
462 M modified
463 A added
463 A added
464 A copied
464 A copied
465 R removed
465 R removed
466
466
467 hg status --change 1 unrelated:
467 hg status --change 1 unrelated:
468
468
469 $ hg status --change 1 unrelated
469 $ hg status --change 1 unrelated
470
470
471 hg status -C --change 1 added modified copied removed deleted:
471 hg status -C --change 1 added modified copied removed deleted:
472
472
473 $ hg status -C --change 1 added modified copied removed deleted
473 $ hg status -C --change 1 added modified copied removed deleted
474 M modified
474 M modified
475 A added
475 A added
476 A copied
476 A copied
477 modified
477 modified
478 R removed
478 R removed
479
479
480 hg status -A --change 1 and revset:
480 hg status -A --change 1 and revset:
481
481
482 $ hg status -A --change '1|1'
482 $ hg status -A --change '1|1'
483 M modified
483 M modified
484 A added
484 A added
485 A copied
485 A copied
486 modified
486 modified
487 R removed
487 R removed
488 C deleted
488 C deleted
489
489
490 $ cd ..
490 $ cd ..
491
491
492 hg status with --rev and reverted changes:
492 hg status with --rev and reverted changes:
493
493
494 $ hg init reverted-changes-repo
494 $ hg init reverted-changes-repo
495 $ cd reverted-changes-repo
495 $ cd reverted-changes-repo
496 $ echo a > file
496 $ echo a > file
497 $ hg add file
497 $ hg add file
498 $ hg ci -m a
498 $ hg ci -m a
499 $ echo b > file
499 $ echo b > file
500 $ hg ci -m b
500 $ hg ci -m b
501
501
502 reverted file should appear clean
502 reverted file should appear clean
503
503
504 $ hg revert -r 0 .
504 $ hg revert -r 0 .
505 reverting file
505 reverting file
506 $ hg status -A --rev 0
506 $ hg status -A --rev 0
507 C file
507 C file
508
508
509 #if execbit
509 #if execbit
510 reverted file with changed flag should appear modified
510 reverted file with changed flag should appear modified
511
511
512 $ chmod +x file
512 $ chmod +x file
513 $ hg status -A --rev 0
513 $ hg status -A --rev 0
514 M file
514 M file
515
515
516 $ hg revert -r 0 .
516 $ hg revert -r 0 .
517 reverting file
517 reverting file
518
518
519 reverted and committed file with changed flag should appear modified
519 reverted and committed file with changed flag should appear modified
520
520
521 $ hg co -C .
521 $ hg co -C .
522 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
522 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
523 $ chmod +x file
523 $ chmod +x file
524 $ hg ci -m 'change flag'
524 $ hg ci -m 'change flag'
525 $ hg status -A --rev 1 --rev 2
525 $ hg status -A --rev 1 --rev 2
526 M file
526 M file
527 $ hg diff -r 1 -r 2
527 $ hg diff -r 1 -r 2
528
528
529 #endif
529 #endif
530
530
531 $ cd ..
531 $ cd ..
532
532
533 hg status of binary file starting with '\1\n', a separator for metadata:
533 hg status of binary file starting with '\1\n', a separator for metadata:
534
534
535 $ hg init repo5
535 $ hg init repo5
536 $ cd repo5
536 $ cd repo5
537 >>> open("010a", r"wb").write(b"\1\nfoo") and None
537 >>> open("010a", r"wb").write(b"\1\nfoo") and None
538 $ hg ci -q -A -m 'initial checkin'
538 $ hg ci -q -A -m 'initial checkin'
539 $ hg status -A
539 $ hg status -A
540 C 010a
540 C 010a
541
541
542 >>> open("010a", r"wb").write(b"\1\nbar") and None
542 >>> open("010a", r"wb").write(b"\1\nbar") and None
543 $ hg status -A
543 $ hg status -A
544 M 010a
544 M 010a
545 $ hg ci -q -m 'modify 010a'
545 $ hg ci -q -m 'modify 010a'
546 $ hg status -A --rev 0:1
546 $ hg status -A --rev 0:1
547 M 010a
547 M 010a
548
548
549 $ touch empty
549 $ touch empty
550 $ hg ci -q -A -m 'add another file'
550 $ hg ci -q -A -m 'add another file'
551 $ hg status -A --rev 1:2 010a
551 $ hg status -A --rev 1:2 010a
552 C 010a
552 C 010a
553
553
554 $ cd ..
554 $ cd ..
555
555
556 test "hg status" with "directory pattern" which matches against files
556 test "hg status" with "directory pattern" which matches against files
557 only known on target revision.
557 only known on target revision.
558
558
559 $ hg init repo6
559 $ hg init repo6
560 $ cd repo6
560 $ cd repo6
561
561
562 $ echo a > a.txt
562 $ echo a > a.txt
563 $ hg add a.txt
563 $ hg add a.txt
564 $ hg commit -m '#0'
564 $ hg commit -m '#0'
565 $ mkdir -p 1/2/3/4/5
565 $ mkdir -p 1/2/3/4/5
566 $ echo b > 1/2/3/4/5/b.txt
566 $ echo b > 1/2/3/4/5/b.txt
567 $ hg add 1/2/3/4/5/b.txt
567 $ hg add 1/2/3/4/5/b.txt
568 $ hg commit -m '#1'
568 $ hg commit -m '#1'
569
569
570 $ hg update -C 0 > /dev/null
570 $ hg update -C 0 > /dev/null
571 $ hg status -A
571 $ hg status -A
572 C a.txt
572 C a.txt
573
573
574 the directory matching against specified pattern should be removed,
574 the directory matching against specified pattern should be removed,
575 because directory existence prevents 'dirstate.walk()' from showing
575 because directory existence prevents 'dirstate.walk()' from showing
576 warning message about such pattern.
576 warning message about such pattern.
577
577
578 $ test ! -d 1
578 $ test ! -d 1
579 $ hg status -A --rev 1 1/2/3/4/5/b.txt
579 $ hg status -A --rev 1 1/2/3/4/5/b.txt
580 R 1/2/3/4/5/b.txt
580 R 1/2/3/4/5/b.txt
581 $ hg status -A --rev 1 1/2/3/4/5
581 $ hg status -A --rev 1 1/2/3/4/5
582 R 1/2/3/4/5/b.txt
582 R 1/2/3/4/5/b.txt
583 $ hg status -A --rev 1 1/2/3
583 $ hg status -A --rev 1 1/2/3
584 R 1/2/3/4/5/b.txt
584 R 1/2/3/4/5/b.txt
585 $ hg status -A --rev 1 1
585 $ hg status -A --rev 1 1
586 R 1/2/3/4/5/b.txt
586 R 1/2/3/4/5/b.txt
587
587
588 $ hg status --config ui.formatdebug=True --rev 1 1
588 $ hg status --config ui.formatdebug=True --rev 1 1
589 status = [
589 status = [
590 {
590 {
591 'itemtype': 'file',
591 'itemtype': 'file',
592 'path': '1/2/3/4/5/b.txt',
592 'path': '1/2/3/4/5/b.txt',
593 'status': 'R'
593 'status': 'R'
594 },
594 },
595 ]
595 ]
596
596
597 #if windows
597 #if windows
598 $ hg --config ui.slash=false status -A --rev 1 1
598 $ hg --config ui.slash=false status -A --rev 1 1
599 R 1\2\3\4\5\b.txt
599 R 1\2\3\4\5\b.txt
600 #endif
600 #endif
601
601
602 $ cd ..
602 $ cd ..
603
603
604 Status after move overwriting a file (issue4458)
604 Status after move overwriting a file (issue4458)
605 =================================================
605 =================================================
606
606
607
607
608 $ hg init issue4458
608 $ hg init issue4458
609 $ cd issue4458
609 $ cd issue4458
610 $ echo a > a
610 $ echo a > a
611 $ echo b > b
611 $ echo b > b
612 $ hg commit -Am base
612 $ hg commit -Am base
613 adding a
613 adding a
614 adding b
614 adding b
615
615
616
616
617 with --force
617 with --force
618
618
619 $ hg mv b --force a
619 $ hg mv b --force a
620 $ hg st --copies
620 $ hg st --copies
621 M a
621 M a
622 b
622 b
623 R b
623 R b
624 $ hg revert --all
624 $ hg revert --all
625 reverting a
625 reverting a
626 undeleting b
626 undeleting b
627 $ rm *.orig
627 $ rm *.orig
628
628
629 without force
629 without force
630
630
631 $ hg rm a
631 $ hg rm a
632 $ hg st --copies
632 $ hg st --copies
633 R a
633 R a
634 $ hg mv b a
634 $ hg mv b a
635 $ hg st --copies
635 $ hg st --copies
636 M a
636 M a
637 b
637 b
638 R b
638 R b
639
639
640 using ui.statuscopies setting
640 using ui.statuscopies setting
641 $ hg st --config ui.statuscopies=true
641 $ hg st --config ui.statuscopies=true
642 M a
642 M a
643 b
643 b
644 R b
644 R b
645 $ hg st --config ui.statuscopies=true --no-copies
645 $ hg st --config ui.statuscopies=true --no-copies
646 M a
646 M a
647 R b
647 R b
648 $ hg st --config ui.statuscopies=false
648 $ hg st --config ui.statuscopies=false
649 M a
649 M a
650 R b
650 R b
651 $ hg st --config ui.statuscopies=false --copies
651 $ hg st --config ui.statuscopies=false --copies
652 M a
652 M a
653 b
653 b
654 R b
654 R b
655 $ hg st --config ui.tweakdefaults=yes
655 $ hg st --config ui.tweakdefaults=yes
656 M a
656 M a
657 b
657 b
658 R b
658 R b
659
659
660 using log status template (issue5155)
660 using log status template (issue5155)
661 $ hg log -Tstatus -r 'wdir()' -C
661 $ hg log -Tstatus -r 'wdir()' -C
662 changeset: 2147483647:ffffffffffff
662 changeset: 2147483647:ffffffffffff
663 parent: 0:8c55c58b4c0e
663 parent: 0:8c55c58b4c0e
664 user: test
664 user: test
665 date: * (glob)
665 date: * (glob)
666 files:
666 files:
667 M a
667 M a
668 b
668 b
669 R b
669 R b
670
670
671 $ hg log -GTstatus -r 'wdir()' -C
671 $ hg log -GTstatus -r 'wdir()' -C
672 o changeset: 2147483647:ffffffffffff
672 o changeset: 2147483647:ffffffffffff
673 | parent: 0:8c55c58b4c0e
673 | parent: 0:8c55c58b4c0e
674 ~ user: test
674 ~ user: test
675 date: * (glob)
675 date: * (glob)
676 files:
676 files:
677 M a
677 M a
678 b
678 b
679 R b
679 R b
680
680
681
681
682 Other "bug" highlight, the revision status does not report the copy information.
682 Other "bug" highlight, the revision status does not report the copy information.
683 This is buggy behavior.
683 This is buggy behavior.
684
684
685 $ hg commit -m 'blah'
685 $ hg commit -m 'blah'
686 $ hg st --copies --change .
686 $ hg st --copies --change .
687 M a
687 M a
688 R b
688 R b
689
689
690 using log status template, the copy information is displayed correctly.
690 using log status template, the copy information is displayed correctly.
691 $ hg log -Tstatus -r. -C
691 $ hg log -Tstatus -r. -C
692 changeset: 1:6685fde43d21
692 changeset: 1:6685fde43d21
693 tag: tip
693 tag: tip
694 user: test
694 user: test
695 date: * (glob)
695 date: * (glob)
696 summary: blah
696 summary: blah
697 files:
697 files:
698 M a
698 M a
699 b
699 b
700 R b
700 R b
701
701
702
702
703 $ cd ..
703 $ cd ..
704
704
705 Make sure .hg doesn't show up even as a symlink
705 Make sure .hg doesn't show up even as a symlink
706
706
707 $ hg init repo0
707 $ hg init repo0
708 $ mkdir symlink-repo0
708 $ mkdir symlink-repo0
709 $ cd symlink-repo0
709 $ cd symlink-repo0
710 $ ln -s ../repo0/.hg
710 $ ln -s ../repo0/.hg
711 $ hg status
711 $ hg status
712
712
713 If the size hasnt changed but mtime has, status needs to read the contents
713 If the size hasnt changed but mtime has, status needs to read the contents
714 of the file to check whether it has changed
714 of the file to check whether it has changed
715
715
716 $ echo 1 > a
716 $ echo 1 > a
717 $ echo 1 > b
717 $ echo 1 > b
718 $ touch -t 200102030000 a b
718 $ touch -t 200102030000 a b
719 $ hg commit -Aqm '#0'
719 $ hg commit -Aqm '#0'
720 $ echo 2 > a
720 $ echo 2 > a
721 $ touch -t 200102040000 a b
721 $ touch -t 200102040000 a b
722 $ hg status
722 $ hg status
723 M a
723 M a
724
724
725 Asking specifically for the status of a deleted/removed file
725 Asking specifically for the status of a deleted/removed file
726
726
727 $ rm a
727 $ rm a
728 $ rm b
728 $ rm b
729 $ hg status a
729 $ hg status a
730 ! a
730 ! a
731 $ hg rm a
731 $ hg rm a
732 $ hg rm b
732 $ hg rm b
733 $ hg status a
733 $ hg status a
734 R a
734 R a
735 $ hg commit -qm '#1'
735 $ hg commit -qm '#1'
736 $ hg status a
736 $ hg status a
737 a: $ENOENT$
737 a: $ENOENT$
738
738
739 Check using include flag with pattern when status does not need to traverse
739 Check using include flag with pattern when status does not need to traverse
740 the working directory (issue6483)
740 the working directory (issue6483)
741
741
742 $ cd ..
742 $ cd ..
743 $ hg init issue6483
743 $ hg init issue6483
744 $ cd issue6483
744 $ cd issue6483
745 $ touch a.py b.rs
745 $ touch a.py b.rs
746 $ hg add a.py b.rs
746 $ hg add a.py b.rs
747 $ hg st -aI "*.py"
747 $ hg st -aI "*.py"
748 A a.py
748 A a.py
749
749
750 Also check exclude pattern
750 Also check exclude pattern
751
751
752 $ hg st -aX "*.rs"
752 $ hg st -aX "*.rs"
753 A a.py
753 A a.py
754
754
755 issue6335
755 issue6335
756 When a directory containing a tracked file gets symlinked, as of 5.8
756 When a directory containing a tracked file gets symlinked, as of 5.8
757 `hg st` only gives the correct answer about clean (or deleted) files
757 `hg st` only gives the correct answer about clean (or deleted) files
758 if also listing unknowns.
758 if also listing unknowns.
759 The tree-based dirstate and status algorithm fix this:
759 The tree-based dirstate and status algorithm fix this:
760
760
761 #if symlink no-dirstate-v1 rust
761 #if symlink no-dirstate-v1 rust
762
762
763 $ cd ..
763 $ cd ..
764 $ hg init issue6335
764 $ hg init issue6335
765 $ cd issue6335
765 $ cd issue6335
766 $ mkdir foo
766 $ mkdir foo
767 $ touch foo/a
767 $ touch foo/a
768 $ hg ci -Ama
768 $ hg ci -Ama
769 adding foo/a
769 adding foo/a
770 $ mv foo bar
770 $ mv foo bar
771 $ ln -s bar foo
771 $ ln -s bar foo
772 $ hg status
772 $ hg status
773 ! foo/a
773 ! foo/a
774 ? bar/a
774 ? bar/a
775 ? foo
775 ? foo
776
776
777 $ hg status -c # incorrect output without the Rust implementation
777 $ hg status -c # incorrect output without the Rust implementation
778 $ hg status -cu
778 $ hg status -cu
779 ? bar/a
779 ? bar/a
780 ? foo
780 ? foo
781 $ hg status -d # incorrect output without the Rust implementation
781 $ hg status -d # incorrect output without the Rust implementation
782 ! foo/a
782 ! foo/a
783 $ hg status -du
783 $ hg status -du
784 ! foo/a
784 ! foo/a
785 ? bar/a
785 ? bar/a
786 ? foo
786 ? foo
787
787
788 #endif
788 #endif
789
789
790
790
791 Create a repo with files in each possible status
791 Create a repo with files in each possible status
792
792
793 $ cd ..
793 $ cd ..
794 $ hg init repo7
794 $ hg init repo7
795 $ cd repo7
795 $ cd repo7
796 $ mkdir subdir
796 $ mkdir subdir
797 $ touch clean modified deleted removed
797 $ touch clean modified deleted removed
798 $ touch subdir/clean subdir/modified subdir/deleted subdir/removed
798 $ touch subdir/clean subdir/modified subdir/deleted subdir/removed
799 $ echo ignored > .hgignore
799 $ echo ignored > .hgignore
800 $ hg ci -Aqm '#0'
800 $ hg ci -Aqm '#0'
801 $ echo 1 > modified
801 $ echo 1 > modified
802 $ echo 1 > subdir/modified
802 $ echo 1 > subdir/modified
803 $ rm deleted
803 $ rm deleted
804 $ rm subdir/deleted
804 $ rm subdir/deleted
805 $ hg rm removed
805 $ hg rm removed
806 $ hg rm subdir/removed
806 $ hg rm subdir/removed
807 $ touch unknown ignored
807 $ touch unknown ignored
808 $ touch subdir/unknown subdir/ignored
808 $ touch subdir/unknown subdir/ignored
809
809
810 Check the output
810 Check the output
811
811
812 $ hg status
812 $ hg status
813 M modified
813 M modified
814 M subdir/modified
814 M subdir/modified
815 R removed
815 R removed
816 R subdir/removed
816 R subdir/removed
817 ! deleted
817 ! deleted
818 ! subdir/deleted
818 ! subdir/deleted
819 ? subdir/unknown
819 ? subdir/unknown
820 ? unknown
820 ? unknown
821
821
822 $ hg status -mard
822 $ hg status -mard
823 M modified
823 M modified
824 M subdir/modified
824 M subdir/modified
825 R removed
825 R removed
826 R subdir/removed
826 R subdir/removed
827 ! deleted
827 ! deleted
828 ! subdir/deleted
828 ! subdir/deleted
829
829
830 $ hg status -A
830 $ hg status -A
831 M modified
831 M modified
832 M subdir/modified
832 M subdir/modified
833 R removed
833 R removed
834 R subdir/removed
834 R subdir/removed
835 ! deleted
835 ! deleted
836 ! subdir/deleted
836 ! subdir/deleted
837 ? subdir/unknown
837 ? subdir/unknown
838 ? unknown
838 ? unknown
839 I ignored
839 I ignored
840 I subdir/ignored
840 I subdir/ignored
841 C .hgignore
841 C .hgignore
842 C clean
842 C clean
843 C subdir/clean
843 C subdir/clean
844
844
845 Test various matchers interatction with dirstate code:
846
845 $ hg status path:subdir
847 $ hg status path:subdir
846 M subdir/modified
848 M subdir/modified
847 R subdir/removed
849 R subdir/removed
848 ! subdir/deleted
850 ! subdir/deleted
849 ? subdir/unknown
851 ? subdir/unknown
850
852
851 FIXME: it's a bug in rhg that the status below is empty:
852
853 $ hg status 'glob:subdir/*'
853 $ hg status 'glob:subdir/*'
854 M subdir/modified (no-rhg !)
854 M subdir/modified
855 R subdir/removed (no-rhg !)
855 R subdir/removed
856 ! subdir/deleted (no-rhg !)
856 ! subdir/deleted
857 ? subdir/unknown (no-rhg !)
857 ? subdir/unknown
858
858
859 FIXME: it's a bug (both in rhg and in Python) that the status below is wrong,
859 FIXME: it's a bug (both in rhg and in Python) that the status below is wrong,
860 in rhg it's empty, in Python it's missing the unknown file:
860 in rhg it's empty, in Python it's missing the unknown file:
861
861
862 $ hg status rootfilesin:subdir
862 $ hg status rootfilesin:subdir
863 M subdir/modified (no-rhg !)
863 M subdir/modified (no-rhg !)
864 R subdir/removed (no-rhg !)
864 R subdir/removed (no-rhg !)
865 ! subdir/deleted (no-rhg !)
865 ! subdir/deleted (no-rhg !)
866
866
867 Note: `hg status some-name` creates a patternmatcher which is not supported
867 Note: `hg status some-name` creates a patternmatcher which is not supported
868 yet by the Rust implementation of status, but includematcher is supported.
868 yet by the Rust implementation of status, but includematcher is supported.
869 --include is used below for that reason
869 --include is used below for that reason
870
870
871 #if unix-permissions
871 #if unix-permissions
872
872
873 Not having permission to read a directory that contains tracked files makes
873 Not having permission to read a directory that contains tracked files makes
874 status emit a warning then behave as if the directory was empty or removed
874 status emit a warning then behave as if the directory was empty or removed
875 entirely:
875 entirely:
876
876
877 $ chmod 0 subdir
877 $ chmod 0 subdir
878 $ hg status --include subdir
878 $ hg status --include subdir
879 subdir: $EACCES$
879 subdir: $EACCES$
880 R subdir/removed
880 R subdir/removed
881 ! subdir/clean
881 ! subdir/clean
882 ! subdir/deleted
882 ! subdir/deleted
883 ! subdir/modified
883 ! subdir/modified
884 $ chmod 755 subdir
884 $ chmod 755 subdir
885
885
886 #endif
886 #endif
887
887
888 Remove a directory that contains tracked files
888 Remove a directory that contains tracked files
889
889
890 $ rm -r subdir
890 $ rm -r subdir
891 $ hg status --include subdir
891 $ hg status --include subdir
892 R subdir/removed
892 R subdir/removed
893 ! subdir/clean
893 ! subdir/clean
894 ! subdir/deleted
894 ! subdir/deleted
895 ! subdir/modified
895 ! subdir/modified
896
896
897 and replace it by a file
897 … and replace it by a file
898
898
899 $ touch subdir
899 $ touch subdir
900 $ hg status --include subdir
900 $ hg status --include subdir
901 R subdir/removed
901 R subdir/removed
902 ! subdir/clean
902 ! subdir/clean
903 ! subdir/deleted
903 ! subdir/deleted
904 ! subdir/modified
904 ! subdir/modified
905 ? subdir
905 ? subdir
906
906
907 Replaced a deleted or removed file with a directory
907 Replaced a deleted or removed file with a directory
908
908
909 $ mkdir deleted removed
909 $ mkdir deleted removed
910 $ touch deleted/1 removed/1
910 $ touch deleted/1 removed/1
911 $ hg status --include deleted --include removed
911 $ hg status --include deleted --include removed
912 R removed
912 R removed
913 ! deleted
913 ! deleted
914 ? deleted/1
914 ? deleted/1
915 ? removed/1
915 ? removed/1
916 $ hg add removed/1
916 $ hg add removed/1
917 $ hg status --include deleted --include removed
917 $ hg status --include deleted --include removed
918 A removed/1
918 A removed/1
919 R removed
919 R removed
920 ! deleted
920 ! deleted
921 ? deleted/1
921 ? deleted/1
922
922
923 Deeply nested files in an ignored directory are still listed on request
923 Deeply nested files in an ignored directory are still listed on request
924
924
925 $ echo ignored-dir >> .hgignore
925 $ echo ignored-dir >> .hgignore
926 $ mkdir ignored-dir
926 $ mkdir ignored-dir
927 $ mkdir ignored-dir/subdir
927 $ mkdir ignored-dir/subdir
928 $ touch ignored-dir/subdir/1
928 $ touch ignored-dir/subdir/1
929 $ hg status --ignored
929 $ hg status --ignored
930 I ignored
930 I ignored
931 I ignored-dir/subdir/1
931 I ignored-dir/subdir/1
932
932
933 Check using include flag while listing ignored composes correctly (issue6514)
933 Check using include flag while listing ignored composes correctly (issue6514)
934
934
935 $ cd ..
935 $ cd ..
936 $ hg init issue6514
936 $ hg init issue6514
937 $ cd issue6514
937 $ cd issue6514
938 $ mkdir ignored-folder
938 $ mkdir ignored-folder
939 $ touch A.hs B.hs C.hs ignored-folder/other.txt ignored-folder/ctest.hs
939 $ touch A.hs B.hs C.hs ignored-folder/other.txt ignored-folder/ctest.hs
940 $ cat >.hgignore <<EOF
940 $ cat >.hgignore <<EOF
941 > A.hs
941 > A.hs
942 > B.hs
942 > B.hs
943 > ignored-folder/
943 > ignored-folder/
944 > EOF
944 > EOF
945 $ hg st -i -I 're:.*\.hs$'
945 $ hg st -i -I 're:.*\.hs$'
946 I A.hs
946 I A.hs
947 I B.hs
947 I B.hs
948 I ignored-folder/ctest.hs
948 I ignored-folder/ctest.hs
949
949
950 #if rust dirstate-v2
950 #if rust dirstate-v2
951
951
952 Check read_dir caching
952 Check read_dir caching
953
953
954 $ cd ..
954 $ cd ..
955 $ hg init repo8
955 $ hg init repo8
956 $ cd repo8
956 $ cd repo8
957 $ mkdir subdir
957 $ mkdir subdir
958 $ touch subdir/a subdir/b
958 $ touch subdir/a subdir/b
959 $ hg ci -Aqm '#0'
959 $ hg ci -Aqm '#0'
960
960
961 The cached mtime is initially unset
961 The cached mtime is initially unset
962
962
963 $ hg debugdirstate --all --no-dates | grep '^ '
963 $ hg debugdirstate --all --no-dates | grep '^ '
964 0 -1 unset subdir
964 0 -1 unset subdir
965
965
966 It is still not set when there are unknown files
966 It is still not set when there are unknown files
967
967
968 $ touch subdir/unknown
968 $ touch subdir/unknown
969 $ hg status
969 $ hg status
970 ? subdir/unknown
970 ? subdir/unknown
971 $ hg debugdirstate --all --no-dates | grep '^ '
971 $ hg debugdirstate --all --no-dates | grep '^ '
972 0 -1 unset subdir
972 0 -1 unset subdir
973
973
974 Now the directory is eligible for caching, so its mtime is saved in the dirstate
974 Now the directory is eligible for caching, so its mtime is saved in the dirstate
975
975
976 $ rm subdir/unknown
976 $ rm subdir/unknown
977 $ sleep 0.1 # ensure the kernels internal clock for mtimes has ticked
977 $ sleep 0.1 # ensure the kernels internal clock for mtimes has ticked
978 $ hg status
978 $ hg status
979 $ hg debugdirstate --all --no-dates | grep '^ '
979 $ hg debugdirstate --all --no-dates | grep '^ '
980 0 -1 set subdir
980 0 -1 set subdir
981
981
982 This time the command should be ever so slightly faster since it does not need `read_dir("subdir")`
982 This time the command should be ever so slightly faster since it does not need `read_dir("subdir")`
983
983
984 $ hg status
984 $ hg status
985
985
986 Creating a new file changes the directorys mtime, invalidating the cache
986 Creating a new file changes the directorys mtime, invalidating the cache
987
987
988 $ touch subdir/unknown
988 $ touch subdir/unknown
989 $ hg status
989 $ hg status
990 ? subdir/unknown
990 ? subdir/unknown
991
991
992 $ rm subdir/unknown
992 $ rm subdir/unknown
993 $ hg status
993 $ hg status
994
994
995 Removing a node from the dirstate resets the cache for its parent directory
995 Removing a node from the dirstate resets the cache for its parent directory
996
996
997 $ hg forget subdir/a
997 $ hg forget subdir/a
998 $ hg debugdirstate --all --no-dates | grep '^ '
998 $ hg debugdirstate --all --no-dates | grep '^ '
999 0 -1 set subdir
999 0 -1 set subdir
1000 $ hg ci -qm '#1'
1000 $ hg ci -qm '#1'
1001 $ hg debugdirstate --all --no-dates | grep '^ '
1001 $ hg debugdirstate --all --no-dates | grep '^ '
1002 0 -1 unset subdir
1002 0 -1 unset subdir
1003 $ hg status
1003 $ hg status
1004 ? subdir/a
1004 ? subdir/a
1005
1005
1006 Changing the hgignore rules makes us recompute the status (and rewrite the dirstate).
1006 Changing the hgignore rules makes us recompute the status (and rewrite the dirstate).
1007
1007
1008 $ rm subdir/a
1008 $ rm subdir/a
1009 $ mkdir another-subdir
1009 $ mkdir another-subdir
1010 $ touch another-subdir/something-else
1010 $ touch another-subdir/something-else
1011
1011
1012 $ cat > "$TESTTMP"/extra-hgignore <<EOF
1012 $ cat > "$TESTTMP"/extra-hgignore <<EOF
1013 > something-else
1013 > something-else
1014 > EOF
1014 > EOF
1015
1015
1016 $ hg status --config ui.ignore.global="$TESTTMP"/extra-hgignore
1016 $ hg status --config ui.ignore.global="$TESTTMP"/extra-hgignore
1017 $ hg debugdirstate --all --no-dates | grep '^ '
1017 $ hg debugdirstate --all --no-dates | grep '^ '
1018 0 -1 set subdir
1018 0 -1 set subdir
1019
1019
1020 $ hg status
1020 $ hg status
1021 ? another-subdir/something-else
1021 ? another-subdir/something-else
1022
1022
1023 One invocation of status is enough to populate the cache even if it's invalidated
1023 One invocation of status is enough to populate the cache even if it's invalidated
1024 in the same run.
1024 in the same run.
1025
1025
1026 $ hg debugdirstate --all --no-dates | grep '^ '
1026 $ hg debugdirstate --all --no-dates | grep '^ '
1027 0 -1 set subdir
1027 0 -1 set subdir
1028
1028
1029 #endif
1029 #endif
1030
1030
1031
1031
1032 Test copy source formatting.
1032 Test copy source formatting.
1033 $ cd ..
1033 $ cd ..
1034 $ hg init copy-source-repo
1034 $ hg init copy-source-repo
1035 $ cd copy-source-repo
1035 $ cd copy-source-repo
1036 $ mkdir -p foo/bar
1036 $ mkdir -p foo/bar
1037 $ cd foo/bar
1037 $ cd foo/bar
1038 $ touch file
1038 $ touch file
1039 $ hg addremove
1039 $ hg addremove
1040 adding foo/bar/file
1040 adding foo/bar/file
1041 $ hg commit -m 'add file'
1041 $ hg commit -m 'add file'
1042 $ hg mv file copy
1042 $ hg mv file copy
1043
1043
1044 Copy source respects relative path setting.
1044 Copy source respects relative path setting.
1045 $ hg st --config ui.statuscopies=true --config commands.status.relative=true
1045 $ hg st --config ui.statuscopies=true --config commands.status.relative=true
1046 A copy
1046 A copy
1047 file
1047 file
1048 R file
1048 R file
1049
1049
1050 Copy source is not shown when --no-status is passed.
1050 Copy source is not shown when --no-status is passed.
1051 $ hg st --config ui.statuscopies=true --no-status
1051 $ hg st --config ui.statuscopies=true --no-status
1052 foo/bar/copy
1052 foo/bar/copy
1053 foo/bar/file
1053 foo/bar/file
General Comments 0
You need to be logged in to leave comments. Login now