Show More
@@ -18,7 +18,8 b' To use a plain path name without any pat' | |||||
18 | current repository root, and when the path points to a directory, it is matched |
|
18 | current repository root, and when the path points to a directory, it is matched | |
19 | recursively. To match all files in a directory non-recursively (not including |
|
19 | recursively. To match all files in a directory non-recursively (not including | |
20 | any files in subdirectories), ``rootfilesin:`` can be used, specifying an |
|
20 | any files in subdirectories), ``rootfilesin:`` can be used, specifying an | |
21 | absolute path (relative to the repository root). |
|
21 | absolute path (relative to the repository root). To match a single file exactly, | |
|
22 | relative to the repository root, you can use ``filepath:``. | |||
22 |
|
23 | |||
23 | To use an extended glob, start a name with ``glob:``. Globs are rooted |
|
24 | To use an extended glob, start a name with ``glob:``. Globs are rooted | |
24 | at the current directory; a glob such as ``*.c`` will only match files |
|
25 | at the current directory; a glob such as ``*.c`` will only match files | |
@@ -50,11 +51,15 b' For ``-I`` and ``-X`` options, ``glob:``' | |||||
50 |
|
51 | |||
51 | Plain examples:: |
|
52 | Plain examples:: | |
52 |
|
53 | |||
53 | path:foo/bar a name bar in a directory named foo in the root |
|
54 | path:foo/bar a name bar in a directory named foo in the root | |
54 | of the repository |
|
55 | of the repository | |
55 |
path: |
|
56 | path:some/path a file or directory named "some/path" | |
56 | rootfilesin:foo/bar the files in a directory called foo/bar, but not any files |
|
57 | filepath:some/path/to/a/file exactly a single file named | |
57 | in its subdirectories and not a file bar in directory foo |
|
58 | "some/path/to/a/file", relative to the root | |
|
59 | of the repository | |||
|
60 | rootfilesin:foo/bar the files in a directory called foo/bar, but | |||
|
61 | not any files in its subdirectories and not | |||
|
62 | a file bar in directory foo | |||
58 |
|
63 | |||
59 | Glob examples:: |
|
64 | Glob examples:: | |
60 |
|
65 |
@@ -30,6 +30,7 b' allpatternkinds = (' | |||||
30 | b're', |
|
30 | b're', | |
31 | b'glob', |
|
31 | b'glob', | |
32 | b'path', |
|
32 | b'path', | |
|
33 | b'filepath', | |||
33 | b'relglob', |
|
34 | b'relglob', | |
34 | b'relpath', |
|
35 | b'relpath', | |
35 | b'relre', |
|
36 | b'relre', | |
@@ -181,6 +182,8 b' def match(' | |||||
181 | 're:<regexp>' - a regular expression |
|
182 | 're:<regexp>' - a regular expression | |
182 | 'path:<path>' - a path relative to repository root, which is matched |
|
183 | 'path:<path>' - a path relative to repository root, which is matched | |
183 | recursively |
|
184 | recursively | |
|
185 | 'filepath:<path>' - an exact path to a single file, relative to the | |||
|
186 | repository root | |||
184 | 'rootfilesin:<path>' - a path relative to repository root, which is |
|
187 | 'rootfilesin:<path>' - a path relative to repository root, which is | |
185 | matched non-recursively (will not match subdirectories) |
|
188 | matched non-recursively (will not match subdirectories) | |
186 | 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs) |
|
189 | 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs) | |
@@ -334,10 +337,18 b' def _donormalize(patterns, default, root' | |||||
334 | """Convert 'kind:pat' from the patterns list to tuples with kind and |
|
337 | """Convert 'kind:pat' from the patterns list to tuples with kind and | |
335 | normalized and rooted patterns and with listfiles expanded.""" |
|
338 | normalized and rooted patterns and with listfiles expanded.""" | |
336 | kindpats = [] |
|
339 | kindpats = [] | |
|
340 | kinds_to_normalize = ( | |||
|
341 | b'relglob', | |||
|
342 | b'path', | |||
|
343 | b'filepath', | |||
|
344 | b'rootfilesin', | |||
|
345 | b'rootglob', | |||
|
346 | ) | |||
|
347 | ||||
337 | for kind, pat in [_patsplit(p, default) for p in patterns]: |
|
348 | for kind, pat in [_patsplit(p, default) for p in patterns]: | |
338 | if kind in cwdrelativepatternkinds: |
|
349 | if kind in cwdrelativepatternkinds: | |
339 | pat = pathutil.canonpath(root, cwd, pat, auditor=auditor) |
|
350 | pat = pathutil.canonpath(root, cwd, pat, auditor=auditor) | |
340 | elif kind in (b'relglob', b'path', b'rootfilesin', b'rootglob'): |
|
351 | elif kind in kinds_to_normalize: | |
341 | pat = util.normpath(pat) |
|
352 | pat = util.normpath(pat) | |
342 | elif kind in (b'listfile', b'listfile0'): |
|
353 | elif kind in (b'listfile', b'listfile0'): | |
343 | try: |
|
354 | try: | |
@@ -1340,6 +1351,10 b' def _regex(kind, pat, globsuffix):' | |||||
1340 | return b'' |
|
1351 | return b'' | |
1341 | if kind == b're': |
|
1352 | if kind == b're': | |
1342 | return pat |
|
1353 | return pat | |
|
1354 | if kind == b'filepath': | |||
|
1355 | raise error.ProgrammingError( | |||
|
1356 | "'filepath:' patterns should not be converted to a regex" | |||
|
1357 | ) | |||
1343 | if kind in (b'path', b'relpath'): |
|
1358 | if kind in (b'path', b'relpath'): | |
1344 | if pat == b'.': |
|
1359 | if pat == b'.': | |
1345 | return b'' |
|
1360 | return b'' | |
@@ -1444,7 +1459,14 b' def _buildregexmatch(kindpats, globsuffi' | |||||
1444 | """ |
|
1459 | """ | |
1445 | try: |
|
1460 | try: | |
1446 | allgroups = [] |
|
1461 | allgroups = [] | |
1447 | regexps = [_regex(k, p, globsuffix) for (k, p, s) in kindpats] |
|
1462 | regexps = [] | |
|
1463 | exact = set() | |||
|
1464 | for (kind, pattern, _source) in kindpats: | |||
|
1465 | if kind == b'filepath': | |||
|
1466 | exact.add(pattern) | |||
|
1467 | continue | |||
|
1468 | regexps.append(_regex(kind, pattern, globsuffix)) | |||
|
1469 | ||||
1448 | fullregexp = _joinregexes(regexps) |
|
1470 | fullregexp = _joinregexes(regexps) | |
1449 |
|
1471 | |||
1450 | startidx = 0 |
|
1472 | startidx = 0 | |
@@ -1469,9 +1491,20 b' def _buildregexmatch(kindpats, globsuffi' | |||||
1469 | allgroups.append(_joinregexes(group)) |
|
1491 | allgroups.append(_joinregexes(group)) | |
1470 | allmatchers = [_rematcher(g) for g in allgroups] |
|
1492 | allmatchers = [_rematcher(g) for g in allgroups] | |
1471 | func = lambda s: any(m(s) for m in allmatchers) |
|
1493 | func = lambda s: any(m(s) for m in allmatchers) | |
1472 | return fullregexp, func |
|
1494 | ||
|
1495 | actualfunc = func | |||
|
1496 | if exact: | |||
|
1497 | # An empty regex will always match, so only call the regex if | |||
|
1498 | # there were any actual patterns to match. | |||
|
1499 | if not regexps: | |||
|
1500 | actualfunc = lambda s: s in exact | |||
|
1501 | else: | |||
|
1502 | actualfunc = lambda s: s in exact or func(s) | |||
|
1503 | return fullregexp, actualfunc | |||
1473 | except re.error: |
|
1504 | except re.error: | |
1474 | for k, p, s in kindpats: |
|
1505 | for k, p, s in kindpats: | |
|
1506 | if k == b'filepath': | |||
|
1507 | continue | |||
1475 | try: |
|
1508 | try: | |
1476 | _rematcher(_regex(k, p, globsuffix)) |
|
1509 | _rematcher(_regex(k, p, globsuffix)) | |
1477 | except re.error: |
|
1510 | except re.error: | |
@@ -1502,7 +1535,7 b' def _patternrootsanddirs(kindpats):' | |||||
1502 | break |
|
1535 | break | |
1503 | root.append(p) |
|
1536 | root.append(p) | |
1504 | r.append(b'/'.join(root)) |
|
1537 | r.append(b'/'.join(root)) | |
1505 | elif kind in (b'relpath', b'path'): |
|
1538 | elif kind in (b'relpath', b'path', b'filepath'): | |
1506 | if pat == b'.': |
|
1539 | if pat == b'.': | |
1507 | pat = b'' |
|
1540 | pat = b'' | |
1508 | r.append(pat) |
|
1541 | r.append(pat) |
@@ -50,6 +50,8 b' pub enum PatternSyntax {' | |||||
50 | Glob, |
|
50 | Glob, | |
51 | /// a path relative to repository root, which is matched recursively |
|
51 | /// a path relative to repository root, which is matched recursively | |
52 | Path, |
|
52 | Path, | |
|
53 | /// a single exact path relative to repository root | |||
|
54 | FilePath, | |||
53 | /// A path relative to cwd |
|
55 | /// A path relative to cwd | |
54 | RelPath, |
|
56 | RelPath, | |
55 | /// an unrooted glob (*.rs matches Rust files in all dirs) |
|
57 | /// an unrooted glob (*.rs matches Rust files in all dirs) | |
@@ -157,6 +159,7 b' pub fn parse_pattern_syntax(' | |||||
157 | match kind { |
|
159 | match kind { | |
158 | b"re:" => Ok(PatternSyntax::Regexp), |
|
160 | b"re:" => Ok(PatternSyntax::Regexp), | |
159 | b"path:" => Ok(PatternSyntax::Path), |
|
161 | b"path:" => Ok(PatternSyntax::Path), | |
|
162 | b"filepath:" => Ok(PatternSyntax::FilePath), | |||
160 | b"relpath:" => Ok(PatternSyntax::RelPath), |
|
163 | b"relpath:" => Ok(PatternSyntax::RelPath), | |
161 | b"rootfilesin:" => Ok(PatternSyntax::RootFiles), |
|
164 | b"rootfilesin:" => Ok(PatternSyntax::RootFiles), | |
162 | b"relglob:" => Ok(PatternSyntax::RelGlob), |
|
165 | b"relglob:" => Ok(PatternSyntax::RelGlob), | |
@@ -252,7 +255,8 b' fn _build_single_regex(entry: &IgnorePat' | |||||
252 | } |
|
255 | } | |
253 | PatternSyntax::Include |
|
256 | PatternSyntax::Include | |
254 | | PatternSyntax::SubInclude |
|
257 | | PatternSyntax::SubInclude | |
255 |
| PatternSyntax::ExpandedSubInclude(_) |
|
258 | | PatternSyntax::ExpandedSubInclude(_) | |
|
259 | | PatternSyntax::FilePath => unreachable!(), | |||
256 | } |
|
260 | } | |
257 | } |
|
261 | } | |
258 |
|
262 | |||
@@ -319,9 +323,9 b' pub fn build_single_regex(' | |||||
319 | } |
|
323 | } | |
320 | _ => pattern.to_owned(), |
|
324 | _ => pattern.to_owned(), | |
321 | }; |
|
325 | }; | |
322 |
|
|
326 | let is_simple_rootglob = *syntax == PatternSyntax::RootGlob | |
323 | && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b)) |
|
327 | && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b)); | |
324 | { |
|
328 | if is_simple_rootglob || syntax == &PatternSyntax::FilePath { | |
325 | Ok(None) |
|
329 | Ok(None) | |
326 | } else { |
|
330 | } else { | |
327 | let mut entry = entry.clone(); |
|
331 | let mut entry = entry.clone(); |
@@ -708,7 +708,9 b' fn roots_and_dirs(' | |||||
708 | } |
|
708 | } | |
709 | roots.push(root); |
|
709 | roots.push(root); | |
710 | } |
|
710 | } | |
711 |
PatternSyntax::Path |
|
711 | PatternSyntax::Path | |
|
712 | | PatternSyntax::RelPath | |||
|
713 | | PatternSyntax::FilePath => { | |||
712 | let pat = HgPath::new(if pattern == b"." { |
|
714 | let pat = HgPath::new(if pattern == b"." { | |
713 | &[] as &[u8] |
|
715 | &[] as &[u8] | |
714 | } else { |
|
716 | } else { | |
@@ -1223,6 +1225,40 b' mod tests {' | |||||
1223 | VisitChildrenSet::This |
|
1225 | VisitChildrenSet::This | |
1224 | ); |
|
1226 | ); | |
1225 |
|
1227 | |||
|
1228 | // VisitchildrensetFilePath | |||
|
1229 | let matcher = IncludeMatcher::new(vec![IgnorePattern::new( | |||
|
1230 | PatternSyntax::FilePath, | |||
|
1231 | b"dir/z", | |||
|
1232 | Path::new(""), | |||
|
1233 | )]) | |||
|
1234 | .unwrap(); | |||
|
1235 | ||||
|
1236 | let mut set = HashSet::new(); | |||
|
1237 | set.insert(HgPathBuf::from_bytes(b"dir")); | |||
|
1238 | assert_eq!( | |||
|
1239 | matcher.visit_children_set(HgPath::new(b"")), | |||
|
1240 | VisitChildrenSet::Set(set) | |||
|
1241 | ); | |||
|
1242 | assert_eq!( | |||
|
1243 | matcher.visit_children_set(HgPath::new(b"folder")), | |||
|
1244 | VisitChildrenSet::Empty | |||
|
1245 | ); | |||
|
1246 | let mut set = HashSet::new(); | |||
|
1247 | set.insert(HgPathBuf::from_bytes(b"z")); | |||
|
1248 | assert_eq!( | |||
|
1249 | matcher.visit_children_set(HgPath::new(b"dir")), | |||
|
1250 | VisitChildrenSet::Set(set) | |||
|
1251 | ); | |||
|
1252 | // OPT: these should probably be set(). | |||
|
1253 | assert_eq!( | |||
|
1254 | matcher.visit_children_set(HgPath::new(b"dir/subdir")), | |||
|
1255 | VisitChildrenSet::Empty | |||
|
1256 | ); | |||
|
1257 | assert_eq!( | |||
|
1258 | matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), | |||
|
1259 | VisitChildrenSet::Empty | |||
|
1260 | ); | |||
|
1261 | ||||
1226 | // Test multiple patterns |
|
1262 | // Test multiple patterns | |
1227 | let matcher = IncludeMatcher::new(vec![ |
|
1263 | let matcher = IncludeMatcher::new(vec![ | |
1228 | IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")), |
|
1264 | IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")), |
@@ -140,6 +140,28 b' class PatternMatcherTests(unittest.TestC' | |||||
140 | self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this') |
|
140 | self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this') | |
141 | self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this') |
|
141 | self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this') | |
142 |
|
142 | |||
|
143 | def testVisitdirFilepath(self): | |||
|
144 | m = matchmod.match( | |||
|
145 | util.localpath(b'/repo'), b'', patterns=[b'filepath:dir/z'] | |||
|
146 | ) | |||
|
147 | assert isinstance(m, matchmod.patternmatcher) | |||
|
148 | self.assertTrue(m.visitdir(b'')) | |||
|
149 | self.assertTrue(m.visitdir(b'dir')) | |||
|
150 | self.assertFalse(m.visitdir(b'folder')) | |||
|
151 | self.assertFalse(m.visitdir(b'dir/subdir')) | |||
|
152 | self.assertFalse(m.visitdir(b'dir/subdir/x')) | |||
|
153 | ||||
|
154 | def testVisitchildrensetFilepath(self): | |||
|
155 | m = matchmod.match( | |||
|
156 | util.localpath(b'/repo'), b'', patterns=[b'filepath:dir/z'] | |||
|
157 | ) | |||
|
158 | assert isinstance(m, matchmod.patternmatcher) | |||
|
159 | self.assertEqual(m.visitchildrenset(b''), b'this') | |||
|
160 | self.assertEqual(m.visitchildrenset(b'folder'), set()) | |||
|
161 | self.assertEqual(m.visitchildrenset(b'dir'), b'this') | |||
|
162 | self.assertEqual(m.visitchildrenset(b'dir/subdir'), set()) | |||
|
163 | self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set()) | |||
|
164 | ||||
143 |
|
165 | |||
144 | class IncludeMatcherTests(unittest.TestCase): |
|
166 | class IncludeMatcherTests(unittest.TestCase): | |
145 | def testVisitdirPrefix(self): |
|
167 | def testVisitdirPrefix(self): | |
@@ -212,6 +234,28 b' class IncludeMatcherTests(unittest.TestC' | |||||
212 | self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this') |
|
234 | self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this') | |
213 | self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this') |
|
235 | self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this') | |
214 |
|
236 | |||
|
237 | def testVisitdirFilepath(self): | |||
|
238 | m = matchmod.match( | |||
|
239 | util.localpath(b'/repo'), b'', include=[b'filepath:dir/z'] | |||
|
240 | ) | |||
|
241 | assert isinstance(m, matchmod.includematcher) | |||
|
242 | self.assertTrue(m.visitdir(b'')) | |||
|
243 | self.assertTrue(m.visitdir(b'dir')) | |||
|
244 | self.assertFalse(m.visitdir(b'folder')) | |||
|
245 | self.assertFalse(m.visitdir(b'dir/subdir')) | |||
|
246 | self.assertFalse(m.visitdir(b'dir/subdir/x')) | |||
|
247 | ||||
|
248 | def testVisitchildrensetFilepath(self): | |||
|
249 | m = matchmod.match( | |||
|
250 | util.localpath(b'/repo'), b'', include=[b'filepath:dir/z'] | |||
|
251 | ) | |||
|
252 | assert isinstance(m, matchmod.includematcher) | |||
|
253 | self.assertEqual(m.visitchildrenset(b''), {b'dir'}) | |||
|
254 | self.assertEqual(m.visitchildrenset(b'folder'), set()) | |||
|
255 | self.assertEqual(m.visitchildrenset(b'dir'), {b'z'}) | |||
|
256 | self.assertEqual(m.visitchildrenset(b'dir/subdir'), set()) | |||
|
257 | self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set()) | |||
|
258 | ||||
215 |
|
259 | |||
216 | class ExactMatcherTests(unittest.TestCase): |
|
260 | class ExactMatcherTests(unittest.TestCase): | |
217 | def testVisitdir(self): |
|
261 | def testVisitdir(self): |
@@ -61,6 +61,37 b'' | |||||
61 | f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon |
|
61 | f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon | |
62 | f mammals/skunk mammals/skunk |
|
62 | f mammals/skunk mammals/skunk | |
63 |
|
63 | |||
|
64 | Test 'filepath:' pattern | |||
|
65 | ||||
|
66 | $ hg debugwalk -v -I 'filepath:mammals/Procyonidae/cacomistle' | |||
|
67 | * matcher: | |||
|
68 | <includematcher includes=''> | |||
|
69 | f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle | |||
|
70 | ||||
|
71 | $ hg debugwalk -v -I 'filepath:mammals/Procyonidae' | |||
|
72 | * matcher: | |||
|
73 | <includematcher includes=''> | |||
|
74 | ||||
|
75 | $ hg debugwalk -v -X 'filepath:beans/borlotti' | |||
|
76 | * matcher: | |||
|
77 | <differencematcher | |||
|
78 | m1=<alwaysmatcher>, | |||
|
79 | m2=<includematcher includes=''>> | |||
|
80 | f beans/black beans/black | |||
|
81 | f beans/kidney beans/kidney | |||
|
82 | f beans/navy beans/navy | |||
|
83 | f beans/pinto beans/pinto | |||
|
84 | f beans/turtle beans/turtle | |||
|
85 | f fennel fennel | |||
|
86 | f fenugreek fenugreek | |||
|
87 | f fiddlehead fiddlehead | |||
|
88 | f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle | |||
|
89 | f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi | |||
|
90 | f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon | |||
|
91 | f mammals/skunk mammals/skunk | |||
|
92 | ||||
|
93 | Test relative paths | |||
|
94 | ||||
64 | $ cd mammals |
|
95 | $ cd mammals | |
65 | $ hg debugwalk -v |
|
96 | $ hg debugwalk -v | |
66 | * matcher: |
|
97 | * matcher: |
General Comments 0
You need to be logged in to leave comments.
Login now