##// END OF EJS Templates
match: add `filepath:` pattern to match an exact filepath relative to the root...
Raphaël Gomès -
r51588:1c31b343 default
parent child Browse files
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:path:name a file or directory named "path:name"
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(_) => unreachable!(),
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 if *syntax == PatternSyntax::RootGlob
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 | PatternSyntax::RelPath => {
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