Mercurial > hg
changeset 38955:081cc9a95b65
match: add visitchildrenset complement to visitdir
`visitdir(d)` lets a caller query whether the directory is part of the matcher.
It can receive a response of 'all' (yes, and all children, you can stop calling
visitdir now), False (no, and no children either), or True (yes, either
something in this directory or a child is part of the matcher).
`visitchildrenset(d)` augments that by instead of returning True, it returns a
list of items to actually investigate. With this, code can be modified from:
for f in self.all_items:
if match.visitdir(self.dir + '/' + f):
<do stuff>
to be:
for f in self.all_items.intersect(match.visitchildrenset(self.dir)):
<do stuff>
use of this function can provide significant performance improvements,
especially when using narrow (so that the matcher is much smaller than the stuff
we see on disk) and/or treemanifests (so that we can avoid loading manifests for
trees that aren't part of the matcher).
Differential Revision: https://phab.mercurial-scm.org/D4130
author | spectral <spectral@google.com> |
---|---|
date | Mon, 06 Aug 2018 12:52:33 -0700 |
parents | 5a7df82de142 |
children | a3cabe9415e1 |
files | mercurial/match.py tests/test-match.py |
diffstat | 2 files changed, 551 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/match.py Mon Aug 06 12:52:22 2018 -0700 +++ b/mercurial/match.py Mon Aug 06 12:52:33 2018 -0700 @@ -8,6 +8,7 @@ from __future__ import absolute_import, print_function import copy +import itertools import os import re @@ -331,6 +332,38 @@ ''' return True + def visitchildrenset(self, dir): + '''Decides whether a directory should be visited based on whether it + has potential matches in it or one of its subdirectories, and + potentially lists which subdirectories of that directory should be + visited. This is based on the match's primary, included, and excluded + patterns. + + This function is very similar to 'visitdir', and the following mapping + can be applied: + + visitdir | visitchildrenlist + ----------+------------------- + False | set() + 'all' | 'all' + True | 'this' OR non-empty set of subdirs to visit + + Example: + Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return + the following values (assuming the implementation of visitchildrenset + is capable of recognizing this; some implementations are not). + + '.' -> {'foo', 'qux'} + 'baz' -> set() + 'foo' -> {'bar'} + # Ideally this would be 'all', but since the prefix nature of matchers + # is applied to the entire matcher, we have to downgrade to this + # 'this' due to the non-prefix 'rootfilesin'-kind matcher. + 'foo/bar' -> 'this' + 'qux' -> 'this' + ''' + return 'this' + def always(self): '''Matcher will match everything and .files() will be empty -- optimization might be possible.''' @@ -367,6 +400,9 @@ def visitdir(self, dir): return 'all' + def visitchildrenset(self, dir): + return 'all' + def __repr__(self): return r'<alwaysmatcher>' @@ -390,6 +426,9 @@ def visitdir(self, dir): return False + def visitchildrenset(self, dir): + return set() + def __repr__(self): return r'<nevermatcher>' @@ -430,6 +469,15 @@ any(parentdir in self._fileset for parentdir in util.finddirs(dir))) + def visitchildrenset(self, dir): + ret = self.visitdir(dir) + if ret is True: + return 'this' + elif not ret: + return set() + assert ret == 'all' + return 'all' + def prefix(self): return self._prefix @@ -464,6 +512,43 @@ any(parentdir in self._roots for parentdir in util.finddirs(dir))) + def visitchildrenset(self, dir): + if self._prefix and dir in self._roots: + return 'all' + # Note: this does *not* include the 'dir in self._parents' case from + # visitdir, that's handled below. + if ('.' in self._roots or + dir in self._roots or + dir in self._dirs or + any(parentdir in self._roots + for parentdir in util.finddirs(dir))): + return 'this' + + ret = set() + if dir in self._parents: + # We add a '/' on to `dir` so that we don't return items that are + # prefixed by `dir` but are actually siblings of `dir`. + suffixeddir = dir + '/' if dir != '.' else '' + # Look in all _roots, _dirs, and _parents for things that start with + # 'suffixeddir'. + for d in [q for q in + itertools.chain(self._roots, self._dirs, self._parents) if + q.startswith(suffixeddir)]: + # Don't emit '.' in the response for the root directory + if not suffixeddir and d == '.': + continue + + # We return the item name without the `suffixeddir` prefix or a + # slash suffix + d = d[len(suffixeddir):] + if '/' in d: + # This is a subdirectory-of-a-subdirectory, i.e. + # suffixeddir='foo/', d was 'foo/bar/baz' before removing + # 'foo/'. + d = d[:d.index('/')] + ret.add(d) + return ret + @encoding.strmethod def __repr__(self): return ('<includematcher includes=%r>' % pycompat.bytestr(self._pats)) @@ -490,6 +575,25 @@ def visitdir(self, dir): return dir in self._dirs + def visitchildrenset(self, dir): + if dir in self._dirs: + candidates = self._dirs - {'.'} + if dir != '.': + d = dir + '/' + candidates = set(c[len(d):] for c in candidates if + c.startswith(d)) + # self._dirs includes all of the directories, recursively, so if + # we're attempting to match foo/bar/baz.txt, it'll have '.', 'foo', + # 'foo/bar' in it. Thus we can safely ignore a candidate that has a + # '/' in it, indicating a it's for a subdir-of-a-subdir; the + # immediate subdir will be in there without a slash. + ret = set(c for c in candidates if '/' not in c) + # We need to emit 'this' for foo/bar, not set(), not {'baz.txt'}. + if not ret: + return 'this' + return ret + return set() + def isexact(self): return True @@ -531,6 +635,31 @@ return False return bool(self._m1.visitdir(dir)) + def visitchildrenset(self, dir): + m2_set = self._m2.visitchildrenset(dir) + if m2_set == 'all': + return set() + m1_set = self._m1.visitchildrenset(dir) + # Possible values for m1: 'all', 'this', set(...), set() + # Possible values for m2: 'this', set(...), set() + # If m2 has nothing under here that we care about, return m1, even if + # it's 'all'. This is a change in behavior from visitdir, which would + # return True, not 'all', for some reason. + if not m2_set: + return m1_set + if m1_set in ['all', 'this']: + # Never return 'all' here if m2_set is any kind of non-empty (either + # 'this' or set(foo)), since m2 might return set() for a + # subdirectory. + return 'this' + # Possible values for m1: set(...), set() + # Possible values for m2: 'this', set(...) + # We ignore m2's set results. They're possibly incorrect: + # m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset('.'): + # m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd + # return set(), which is *not* correct, we still need to visit 'dir'! + return m1_set + def isexact(self): return self._m1.isexact() @@ -595,6 +724,25 @@ # bool() because visit1=True + visit2='all' should not be 'all' return bool(visit1 and self._m2.visitdir(dir)) + def visitchildrenset(self, dir): + m1_set = self._m1.visitchildrenset(dir) + if not m1_set: + return set() + m2_set = self._m2.visitchildrenset(dir) + if not m2_set: + return set() + + if m1_set == 'all': + return m2_set + elif m2_set == 'all': + return m1_set + + if m1_set == 'this' or m2_set == 'this': + return 'this' + + assert isinstance(m1_set, set) and isinstance(m2_set, set) + return m1_set.intersection(m2_set) + def always(self): return self._m1.always() and self._m2.always() @@ -676,6 +824,13 @@ dir = self._path + "/" + dir return self._matcher.visitdir(dir) + def visitchildrenset(self, dir): + if dir == '.': + dir = self._path + else: + dir = self._path + "/" + dir + return self._matcher.visitchildrenset(dir) + def always(self): return self._always @@ -748,6 +903,14 @@ return self._matcher.visitdir(dir[len(self._pathprefix):]) return dir in self._pathdirs + def visitchildrenset(self, dir): + if dir == self._path: + return self._matcher.visitchildrenset('.') + if dir.startswith(self._pathprefix): + return self._matcher.visitchildrenset(dir[len(self._pathprefix):]) + if dir in self._pathdirs: + return 'this' + def isexact(self): return self._matcher.isexact() @@ -788,6 +951,25 @@ r |= v return r + def visitchildrenset(self, dir): + r = set() + this = False + for m in self._matchers: + v = m.visitchildrenset(dir) + if not v: + continue + if v == 'all': + return v + if this or v == 'this': + this = True + # don't break, we might have an 'all' in here. + continue + assert isinstance(v, set) + r = r.union(v) + if this: + return 'this' + return r + @encoding.strmethod def __repr__(self): return ('<unionmatcher matchers=%r>' % self._matchers)
--- a/tests/test-match.py Mon Aug 06 12:52:22 2018 -0700 +++ b/tests/test-match.py Mon Aug 06 12:52:33 2018 -0700 @@ -16,6 +16,11 @@ self.assertTrue(m.visitdir('.')) self.assertTrue(m.visitdir('dir')) + def testVisitchildrenset(self): + m = matchmod.basematcher('', '') + self.assertEqual(m.visitchildrenset('.'), 'this') + self.assertEqual(m.visitchildrenset('dir'), 'this') + class AlwaysMatcherTests(unittest.TestCase): def testVisitdir(self): @@ -23,6 +28,11 @@ self.assertEqual(m.visitdir('.'), 'all') self.assertEqual(m.visitdir('dir'), 'all') + def testVisitchildrenset(self): + m = matchmod.alwaysmatcher('', '') + self.assertEqual(m.visitchildrenset('.'), 'all') + self.assertEqual(m.visitchildrenset('dir'), 'all') + class NeverMatcherTests(unittest.TestCase): def testVisitdir(self): @@ -30,6 +40,11 @@ self.assertFalse(m.visitdir('.')) self.assertFalse(m.visitdir('dir')) + def testVisitchildrenset(self): + m = matchmod.nevermatcher('', '') + self.assertEqual(m.visitchildrenset('.'), set()) + self.assertEqual(m.visitchildrenset('dir'), set()) + class PredicateMatcherTests(unittest.TestCase): # predicatematcher does not currently define either of these methods, so # this is equivalent to BaseMatcherTests. @@ -39,6 +54,11 @@ self.assertTrue(m.visitdir('.')) self.assertTrue(m.visitdir('dir')) + def testVisitchildrenset(self): + m = matchmod.predicatematcher('', '', lambda *a: False) + self.assertEqual(m.visitchildrenset('.'), 'this') + self.assertEqual(m.visitchildrenset('dir'), 'this') + class PatternMatcherTests(unittest.TestCase): def testVisitdirPrefix(self): @@ -51,6 +71,16 @@ self.assertTrue(m.visitdir('dir/subdir/x')) self.assertFalse(m.visitdir('folder')) + def testVisitchildrensetPrefix(self): + m = matchmod.match('x', '', patterns=['path:dir/subdir']) + assert isinstance(m, matchmod.patternmatcher) + self.assertEqual(m.visitchildrenset('.'), 'this') + self.assertEqual(m.visitchildrenset('dir'), 'this') + self.assertEqual(m.visitchildrenset('dir/subdir'), 'all') + # OPT: This should probably be 'all' if its parent is? + self.assertEqual(m.visitchildrenset('dir/subdir/x'), 'this') + self.assertEqual(m.visitchildrenset('folder'), set()) + def testVisitdirRootfilesin(self): m = matchmod.match('x', '', patterns=['rootfilesin:dir/subdir']) assert isinstance(m, matchmod.patternmatcher) @@ -61,6 +91,15 @@ self.assertFalse(m.visitdir('dir')) self.assertFalse(m.visitdir('dir/subdir')) + def testVisitchildrensetRootfilesin(self): + m = matchmod.match('x', '', patterns=['rootfilesin:dir/subdir']) + assert isinstance(m, matchmod.patternmatcher) + self.assertEqual(m.visitchildrenset('.'), 'this') + self.assertEqual(m.visitchildrenset('dir/subdir/x'), set()) + self.assertEqual(m.visitchildrenset('folder'), set()) + self.assertEqual(m.visitchildrenset('dir'), set()) + self.assertEqual(m.visitchildrenset('dir/subdir'), set()) + def testVisitdirGlob(self): m = matchmod.match('x', '', patterns=['glob:dir/z*']) assert isinstance(m, matchmod.patternmatcher) @@ -71,6 +110,16 @@ self.assertTrue(m.visitdir('dir/subdir')) self.assertTrue(m.visitdir('dir/subdir/x')) + def testVisitchildrensetGlob(self): + m = matchmod.match('x', '', patterns=['glob:dir/z*']) + assert isinstance(m, matchmod.patternmatcher) + self.assertEqual(m.visitchildrenset('.'), 'this') + self.assertEqual(m.visitchildrenset('folder'), set()) + self.assertEqual(m.visitchildrenset('dir'), 'this') + # OPT: these should probably be set(). + self.assertEqual(m.visitchildrenset('dir/subdir'), 'this') + self.assertEqual(m.visitchildrenset('dir/subdir/x'), 'this') + class IncludeMatcherTests(unittest.TestCase): def testVisitdirPrefix(self): @@ -83,6 +132,16 @@ self.assertTrue(m.visitdir('dir/subdir/x')) self.assertFalse(m.visitdir('folder')) + def testVisitchildrensetPrefix(self): + m = matchmod.match('x', '', include=['path:dir/subdir']) + assert isinstance(m, matchmod.includematcher) + self.assertEqual(m.visitchildrenset('.'), {'dir'}) + self.assertEqual(m.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(m.visitchildrenset('dir/subdir'), 'all') + # OPT: This should probably be 'all' if its parent is? + self.assertEqual(m.visitchildrenset('dir/subdir/x'), 'this') + self.assertEqual(m.visitchildrenset('folder'), set()) + def testVisitdirRootfilesin(self): m = matchmod.match('x', '', include=['rootfilesin:dir/subdir']) assert isinstance(m, matchmod.includematcher) @@ -92,6 +151,15 @@ self.assertFalse(m.visitdir('dir/subdir/x')) self.assertFalse(m.visitdir('folder')) + def testVisitchildrensetRootfilesin(self): + m = matchmod.match('x', '', include=['rootfilesin:dir/subdir']) + assert isinstance(m, matchmod.includematcher) + self.assertEqual(m.visitchildrenset('.'), {'dir'}) + self.assertEqual(m.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(m.visitchildrenset('dir/subdir'), 'this') + self.assertEqual(m.visitchildrenset('dir/subdir/x'), set()) + self.assertEqual(m.visitchildrenset('folder'), set()) + def testVisitdirGlob(self): m = matchmod.match('x', '', include=['glob:dir/z*']) assert isinstance(m, matchmod.includematcher) @@ -102,6 +170,16 @@ self.assertTrue(m.visitdir('dir/subdir')) self.assertTrue(m.visitdir('dir/subdir/x')) + def testVisitchildrensetGlob(self): + m = matchmod.match('x', '', include=['glob:dir/z*']) + assert isinstance(m, matchmod.includematcher) + self.assertEqual(m.visitchildrenset('.'), {'dir'}) + self.assertEqual(m.visitchildrenset('folder'), set()) + self.assertEqual(m.visitchildrenset('dir'), 'this') + # OPT: these should probably be set(). + self.assertEqual(m.visitchildrenset('dir/subdir'), 'this') + self.assertEqual(m.visitchildrenset('dir/subdir/x'), 'this') + class ExactMatcherTests(unittest.TestCase): def testVisitdir(self): @@ -115,6 +193,16 @@ self.assertFalse(m.visitdir('dir/subdir/x')) self.assertFalse(m.visitdir('folder')) + def testVisitchildrenset(self): + m = matchmod.match('x', '', patterns=['dir/subdir/foo.txt'], exact=True) + assert isinstance(m, matchmod.exactmatcher) + self.assertEqual(m.visitchildrenset('.'), {'dir'}) + self.assertEqual(m.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(m.visitchildrenset('dir/subdir'), 'this') + self.assertEqual(m.visitchildrenset('dir/subdir/x'), set()) + self.assertEqual(m.visitchildrenset('dir/subdir/foo.txt'), set()) + self.assertEqual(m.visitchildrenset('folder'), set()) + class DifferenceMatcherTests(unittest.TestCase): def testVisitdirM2always(self): @@ -130,6 +218,19 @@ self.assertFalse(dm.visitdir('dir/subdir/x')) self.assertFalse(dm.visitdir('folder')) + def testVisitchildrensetM2always(self): + m1 = matchmod.alwaysmatcher('', '') + m2 = matchmod.alwaysmatcher('', '') + dm = matchmod.differencematcher(m1, m2) + # dm should be equivalent to a nevermatcher. + self.assertEqual(dm.visitchildrenset('.'), set()) + self.assertEqual(dm.visitchildrenset('dir'), set()) + self.assertEqual(dm.visitchildrenset('dir/subdir'), set()) + self.assertEqual(dm.visitchildrenset('dir/subdir/z'), set()) + self.assertEqual(dm.visitchildrenset('dir/foo'), set()) + self.assertEqual(dm.visitchildrenset('dir/subdir/x'), set()) + self.assertEqual(dm.visitchildrenset('folder'), set()) + def testVisitdirM2never(self): m1 = matchmod.alwaysmatcher('', '') m2 = matchmod.nevermatcher('', '') @@ -149,6 +250,19 @@ self.assertEqual(dm.visitdir('dir/subdir/x'), True) self.assertEqual(dm.visitdir('folder'), True) + def testVisitchildrensetM2never(self): + m1 = matchmod.alwaysmatcher('', '') + m2 = matchmod.nevermatcher('', '') + dm = matchmod.differencematcher(m1, m2) + # dm should be equivalent to a alwaysmatcher. + self.assertEqual(dm.visitchildrenset('.'), 'all') + self.assertEqual(dm.visitchildrenset('dir'), 'all') + self.assertEqual(dm.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(dm.visitchildrenset('dir/subdir/z'), 'all') + self.assertEqual(dm.visitchildrenset('dir/foo'), 'all') + self.assertEqual(dm.visitchildrenset('dir/subdir/x'), 'all') + self.assertEqual(dm.visitchildrenset('folder'), 'all') + def testVisitdirM2SubdirPrefix(self): m1 = matchmod.alwaysmatcher('', '') m2 = matchmod.match('', '', patterns=['path:dir/subdir']) @@ -165,6 +279,21 @@ self.assertEqual(dm.visitdir('dir/foo'), True) self.assertEqual(dm.visitdir('folder'), True) + def testVisitchildrensetM2SubdirPrefix(self): + m1 = matchmod.alwaysmatcher('', '') + m2 = matchmod.match('', '', patterns=['path:dir/subdir']) + dm = matchmod.differencematcher(m1, m2) + self.assertEqual(dm.visitchildrenset('.'), 'this') + self.assertEqual(dm.visitchildrenset('dir'), 'this') + self.assertEqual(dm.visitchildrenset('dir/subdir'), set()) + self.assertEqual(dm.visitchildrenset('dir/foo'), 'all') + self.assertEqual(dm.visitchildrenset('folder'), 'all') + # OPT: We should probably return set() for these; we don't because + # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of + # an 'all' pattern, just 'this'. + self.assertEqual(dm.visitchildrenset('dir/subdir/z'), 'this') + self.assertEqual(dm.visitchildrenset('dir/subdir/x'), 'this') + # We're using includematcher instead of patterns because it behaves slightly # better (giving narrower results) than patternmatcher. def testVisitdirIncludeIncludfe(self): @@ -182,6 +311,21 @@ self.assertEqual(dm.visitdir('dir/subdir/z'), True) self.assertEqual(dm.visitdir('dir/subdir/x'), True) + def testVisitchildrensetIncludeInclude(self): + m1 = matchmod.match('', '', include=['path:dir/subdir']) + m2 = matchmod.match('', '', include=['rootfilesin:dir']) + dm = matchmod.differencematcher(m1, m2) + self.assertEqual(dm.visitchildrenset('.'), {'dir'}) + self.assertEqual(dm.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(dm.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(dm.visitchildrenset('dir/foo'), set()) + self.assertEqual(dm.visitchildrenset('folder'), set()) + # OPT: We should probably return set() for these; we don't because + # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of + # an 'all' pattern, just 'this'. + self.assertEqual(dm.visitchildrenset('dir/subdir/z'), 'this') + self.assertEqual(dm.visitchildrenset('dir/subdir/x'), 'this') + class IntersectionMatcherTests(unittest.TestCase): def testVisitdirM2always(self): @@ -197,6 +341,19 @@ self.assertEqual(im.visitdir('dir/subdir/x'), 'all') self.assertEqual(im.visitdir('folder'), 'all') + def testVisitchildrensetM2always(self): + m1 = matchmod.alwaysmatcher('', '') + m2 = matchmod.alwaysmatcher('', '') + im = matchmod.intersectmatchers(m1, m2) + # im should be equivalent to a alwaysmatcher. + self.assertEqual(im.visitchildrenset('.'), 'all') + self.assertEqual(im.visitchildrenset('dir'), 'all') + self.assertEqual(im.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(im.visitchildrenset('dir/subdir/z'), 'all') + self.assertEqual(im.visitchildrenset('dir/foo'), 'all') + self.assertEqual(im.visitchildrenset('dir/subdir/x'), 'all') + self.assertEqual(im.visitchildrenset('folder'), 'all') + def testVisitdirM2never(self): m1 = matchmod.alwaysmatcher('', '') m2 = matchmod.nevermatcher('', '') @@ -210,6 +367,19 @@ self.assertFalse(im.visitdir('dir/subdir/x')) self.assertFalse(im.visitdir('folder')) + def testVisitchildrensetM2never(self): + m1 = matchmod.alwaysmatcher('', '') + m2 = matchmod.nevermatcher('', '') + im = matchmod.intersectmatchers(m1, m2) + # im should be equivalent to a nevermqtcher. + self.assertEqual(im.visitchildrenset('.'), set()) + self.assertEqual(im.visitchildrenset('dir'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir/z'), set()) + self.assertEqual(im.visitchildrenset('dir/foo'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir/x'), set()) + self.assertEqual(im.visitchildrenset('folder'), set()) + def testVisitdirM2SubdirPrefix(self): m1 = matchmod.alwaysmatcher('', '') m2 = matchmod.match('', '', patterns=['path:dir/subdir']) @@ -225,6 +395,19 @@ self.assertEqual(im.visitdir('dir/subdir/z'), True) self.assertEqual(im.visitdir('dir/subdir/x'), True) + def testVisitchildrensetM2SubdirPrefix(self): + m1 = matchmod.alwaysmatcher('', '') + m2 = matchmod.match('', '', include=['path:dir/subdir']) + im = matchmod.intersectmatchers(m1, m2) + self.assertEqual(im.visitchildrenset('.'), {'dir'}) + self.assertEqual(im.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(im.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(im.visitchildrenset('dir/foo'), set()) + self.assertEqual(im.visitchildrenset('folder'), set()) + # OPT: We should probably return 'all' for these + self.assertEqual(im.visitchildrenset('dir/subdir/z'), 'this') + self.assertEqual(im.visitchildrenset('dir/subdir/x'), 'this') + # We're using includematcher instead of patterns because it behaves slightly # better (giving narrower results) than patternmatcher. def testVisitdirIncludeIncludfe(self): @@ -239,6 +422,18 @@ self.assertFalse(im.visitdir('dir/subdir/z')) self.assertFalse(im.visitdir('dir/subdir/x')) + def testVisitchildrensetIncludeInclude(self): + m1 = matchmod.match('', '', include=['path:dir/subdir']) + m2 = matchmod.match('', '', include=['rootfilesin:dir']) + im = matchmod.intersectmatchers(m1, m2) + self.assertEqual(im.visitchildrenset('.'), {'dir'}) + self.assertEqual(im.visitchildrenset('dir'), 'this') + self.assertEqual(im.visitchildrenset('dir/subdir'), set()) + self.assertEqual(im.visitchildrenset('dir/foo'), set()) + self.assertEqual(im.visitchildrenset('folder'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir/z'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir/x'), set()) + # We're using includematcher instead of patterns because it behaves slightly # better (giving narrower results) than patternmatcher. def testVisitdirIncludeInclude2(self): @@ -254,6 +449,19 @@ self.assertFalse(im.visitdir('dir/subdir/z')) self.assertFalse(im.visitdir('dir/subdir/x')) + def testVisitchildrensetIncludeInclude2(self): + m1 = matchmod.match('', '', include=['path:dir/subdir']) + m2 = matchmod.match('', '', include=['path:folder']) + im = matchmod.intersectmatchers(m1, m2) + # FIXME: is set() correct here? + self.assertEqual(im.visitchildrenset('.'), set()) + self.assertEqual(im.visitchildrenset('dir'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir'), set()) + self.assertEqual(im.visitchildrenset('dir/foo'), set()) + self.assertEqual(im.visitchildrenset('folder'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir/z'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir/x'), set()) + # We're using includematcher instead of patterns because it behaves slightly # better (giving narrower results) than patternmatcher. def testVisitdirIncludeInclude3(self): @@ -269,6 +477,19 @@ # OPT: this should probably be 'all' not True. self.assertEqual(im.visitdir('dir/subdir/x'), True) + def testVisitchildrensetIncludeInclude3(self): + m1 = matchmod.match('', '', include=['path:dir/subdir/x']) + m2 = matchmod.match('', '', include=['path:dir/subdir']) + im = matchmod.intersectmatchers(m1, m2) + self.assertEqual(im.visitchildrenset('.'), {'dir'}) + self.assertEqual(im.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(im.visitchildrenset('dir/subdir'), {'x'}) + self.assertEqual(im.visitchildrenset('dir/foo'), set()) + self.assertEqual(im.visitchildrenset('folder'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir/z'), set()) + # OPT: this should probably be 'all' not 'this'. + self.assertEqual(im.visitchildrenset('dir/subdir/x'), 'this') + # We're using includematcher instead of patterns because it behaves slightly # better (giving narrower results) than patternmatcher. def testVisitdirIncludeInclude4(self): @@ -284,6 +505,19 @@ self.assertFalse(im.visitdir('dir/subdir/z')) self.assertFalse(im.visitdir('dir/subdir/x')) + def testVisitchildrensetIncludeInclude4(self): + m1 = matchmod.match('', '', include=['path:dir/subdir/x']) + m2 = matchmod.match('', '', include=['path:dir/subdir/z']) + im = matchmod.intersectmatchers(m1, m2) + # OPT: these next two could probably be set() as well. + self.assertEqual(im.visitchildrenset('.'), {'dir'}) + self.assertEqual(im.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(im.visitchildrenset('dir/subdir'), set()) + self.assertEqual(im.visitchildrenset('dir/foo'), set()) + self.assertEqual(im.visitchildrenset('folder'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir/z'), set()) + self.assertEqual(im.visitchildrenset('dir/subdir/x'), set()) + class UnionMatcherTests(unittest.TestCase): def testVisitdirM2always(self): @@ -299,6 +533,19 @@ self.assertEqual(um.visitdir('dir/subdir/x'), 'all') self.assertEqual(um.visitdir('folder'), 'all') + def testVisitchildrensetM2always(self): + m1 = matchmod.alwaysmatcher('', '') + m2 = matchmod.alwaysmatcher('', '') + um = matchmod.unionmatcher([m1, m2]) + # um should be equivalent to a alwaysmatcher. + self.assertEqual(um.visitchildrenset('.'), 'all') + self.assertEqual(um.visitchildrenset('dir'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir/z'), 'all') + self.assertEqual(um.visitchildrenset('dir/foo'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir/x'), 'all') + self.assertEqual(um.visitchildrenset('folder'), 'all') + def testVisitdirM1never(self): m1 = matchmod.nevermatcher('', '') m2 = matchmod.alwaysmatcher('', '') @@ -312,6 +559,19 @@ self.assertEqual(um.visitdir('dir/subdir/x'), 'all') self.assertEqual(um.visitdir('folder'), 'all') + def testVisitchildrensetM1never(self): + m1 = matchmod.nevermatcher('', '') + m2 = matchmod.alwaysmatcher('', '') + um = matchmod.unionmatcher([m1, m2]) + # um should be equivalent to a alwaysmatcher. + self.assertEqual(um.visitchildrenset('.'), 'all') + self.assertEqual(um.visitchildrenset('dir'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir/z'), 'all') + self.assertEqual(um.visitchildrenset('dir/foo'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir/x'), 'all') + self.assertEqual(um.visitchildrenset('folder'), 'all') + def testVisitdirM2never(self): m1 = matchmod.alwaysmatcher('', '') m2 = matchmod.nevermatcher('', '') @@ -325,6 +585,19 @@ self.assertEqual(um.visitdir('dir/subdir/x'), 'all') self.assertEqual(um.visitdir('folder'), 'all') + def testVisitchildrensetM2never(self): + m1 = matchmod.alwaysmatcher('', '') + m2 = matchmod.nevermatcher('', '') + um = matchmod.unionmatcher([m1, m2]) + # um should be equivalent to a alwaysmatcher. + self.assertEqual(um.visitchildrenset('.'), 'all') + self.assertEqual(um.visitchildrenset('dir'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir/z'), 'all') + self.assertEqual(um.visitchildrenset('dir/foo'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir/x'), 'all') + self.assertEqual(um.visitchildrenset('folder'), 'all') + def testVisitdirM2SubdirPrefix(self): m1 = matchmod.alwaysmatcher('', '') m2 = matchmod.match('', '', patterns=['path:dir/subdir']) @@ -337,6 +610,18 @@ self.assertEqual(um.visitdir('dir/subdir/z'), 'all') self.assertEqual(um.visitdir('dir/subdir/x'), 'all') + def testVisitchildrensetM2SubdirPrefix(self): + m1 = matchmod.alwaysmatcher('', '') + m2 = matchmod.match('', '', include=['path:dir/subdir']) + um = matchmod.unionmatcher([m1, m2]) + self.assertEqual(um.visitchildrenset('.'), 'all') + self.assertEqual(um.visitchildrenset('dir'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(um.visitchildrenset('dir/foo'), 'all') + self.assertEqual(um.visitchildrenset('folder'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir/z'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir/x'), 'all') + # We're using includematcher instead of patterns because it behaves slightly # better (giving narrower results) than patternmatcher. def testVisitdirIncludeIncludfe(self): @@ -352,6 +637,19 @@ self.assertEqual(um.visitdir('dir/subdir/z'), True) self.assertEqual(um.visitdir('dir/subdir/x'), True) + def testVisitchildrensetIncludeInclude(self): + m1 = matchmod.match('', '', include=['path:dir/subdir']) + m2 = matchmod.match('', '', include=['rootfilesin:dir']) + um = matchmod.unionmatcher([m1, m2]) + self.assertEqual(um.visitchildrenset('.'), {'dir'}) + self.assertEqual(um.visitchildrenset('dir'), 'this') + self.assertEqual(um.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(um.visitchildrenset('dir/foo'), set()) + self.assertEqual(um.visitchildrenset('folder'), set()) + # OPT: These next two could be 'all' instead of 'this'. + self.assertEqual(um.visitchildrenset('dir/subdir/z'), 'this') + self.assertEqual(um.visitchildrenset('dir/subdir/x'), 'this') + # We're using includematcher instead of patterns because it behaves slightly # better (giving narrower results) than patternmatcher. def testVisitdirIncludeInclude2(self): @@ -367,6 +665,19 @@ self.assertEqual(um.visitdir('dir/subdir/z'), True) self.assertEqual(um.visitdir('dir/subdir/x'), True) + def testVisitchildrensetIncludeInclude2(self): + m1 = matchmod.match('', '', include=['path:dir/subdir']) + m2 = matchmod.match('', '', include=['path:folder']) + um = matchmod.unionmatcher([m1, m2]) + self.assertEqual(um.visitchildrenset('.'), {'folder', 'dir'}) + self.assertEqual(um.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(um.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(um.visitchildrenset('dir/foo'), set()) + self.assertEqual(um.visitchildrenset('folder'), 'all') + # OPT: These next two could be 'all' instead of 'this'. + self.assertEqual(um.visitchildrenset('dir/subdir/z'), 'this') + self.assertEqual(um.visitchildrenset('dir/subdir/x'), 'this') + # We're using includematcher instead of patterns because it behaves slightly # better (giving narrower results) than patternmatcher. def testVisitdirIncludeInclude3(self): @@ -382,6 +693,19 @@ # OPT: this should probably be 'all' not True. self.assertEqual(um.visitdir('dir/subdir/z'), True) + def testVisitchildrensetIncludeInclude3(self): + m1 = matchmod.match('', '', include=['path:dir/subdir/x']) + m2 = matchmod.match('', '', include=['path:dir/subdir']) + um = matchmod.unionmatcher([m1, m2]) + self.assertEqual(um.visitchildrenset('.'), {'dir'}) + self.assertEqual(um.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(um.visitchildrenset('dir/subdir'), 'all') + self.assertEqual(um.visitchildrenset('dir/foo'), set()) + self.assertEqual(um.visitchildrenset('folder'), set()) + self.assertEqual(um.visitchildrenset('dir/subdir/x'), 'all') + # OPT: this should probably be 'all' not 'this'. + self.assertEqual(um.visitchildrenset('dir/subdir/z'), 'this') + # We're using includematcher instead of patterns because it behaves slightly # better (giving narrower results) than patternmatcher. def testVisitdirIncludeInclude4(self): @@ -397,6 +721,18 @@ self.assertEqual(um.visitdir('dir/subdir/z'), 'all') self.assertEqual(um.visitdir('dir/subdir/x'), 'all') + def testVisitchildrensetIncludeInclude4(self): + m1 = matchmod.match('', '', include=['path:dir/subdir/x']) + m2 = matchmod.match('', '', include=['path:dir/subdir/z']) + um = matchmod.unionmatcher([m1, m2]) + self.assertEqual(um.visitchildrenset('.'), {'dir'}) + self.assertEqual(um.visitchildrenset('dir'), {'subdir'}) + self.assertEqual(um.visitchildrenset('dir/subdir'), {'x', 'z'}) + self.assertEqual(um.visitchildrenset('dir/foo'), set()) + self.assertEqual(um.visitchildrenset('folder'), set()) + self.assertEqual(um.visitchildrenset('dir/subdir/z'), 'all') + self.assertEqual(um.visitchildrenset('dir/subdir/x'), 'all') + class SubdirMatcherTests(unittest.TestCase): def testVisitdir(self): @@ -410,6 +746,17 @@ self.assertEqual(sm.visitdir('subdir/z'), True) self.assertFalse(sm.visitdir('foo')) + def testVisitchildrenset(self): + m = matchmod.match('', '', include=['path:dir/subdir']) + sm = matchmod.subdirmatcher('dir', m) + + self.assertEqual(sm.visitchildrenset('.'), {'subdir'}) + self.assertEqual(sm.visitchildrenset('subdir'), 'all') + # OPT: These next two should probably be 'all' not 'this'. + self.assertEqual(sm.visitchildrenset('subdir/x'), 'this') + self.assertEqual(sm.visitchildrenset('subdir/z'), 'this') + self.assertEqual(sm.visitchildrenset('foo'), set()) + class PrefixdirMatcherTests(unittest.TestCase): def testVisitdir(self): @@ -444,5 +791,27 @@ self.assertEqual(pm.visitdir('d/e/f'), True) self.assertEqual(pm.visitdir('d/e/f/g'), False) + def testVisitchildrenset(self): + m = matchmod.match(util.localpath('root/d'), 'e/f', + ['../a.txt', 'b.txt']) + pm = matchmod.prefixdirmatcher('root', 'd/e/f', 'd', m) + + # OPT: visitchildrenset could possibly return {'e'} and {'f'} for these + # next two, respectively; patternmatcher does not have this + # optimization. + self.assertEqual(m.visitchildrenset('.'), 'this') + self.assertEqual(m.visitchildrenset('e'), 'this') + self.assertEqual(m.visitchildrenset('e/f'), 'this') + self.assertEqual(m.visitchildrenset('e/f/g'), set()) + + # OPT: visitchildrenset could possibly return {'d'}, {'e'}, and {'f'} + # for these next three, respectively; patternmatcher does not have this + # optimization. + self.assertEqual(pm.visitchildrenset('.'), 'this') + self.assertEqual(pm.visitchildrenset('d'), 'this') + self.assertEqual(pm.visitchildrenset('d/e'), 'this') + self.assertEqual(pm.visitchildrenset('d/e/f'), 'this') + self.assertEqual(pm.visitchildrenset('d/e/f/g'), set()) + if __name__ == '__main__': silenttestrunner.main(__name__)