diff -r ca77a243ffa7 -r 9eccd559c592 mercurial/match.py --- a/mercurial/match.py Thu May 25 14:32:56 2017 -0700 +++ b/mercurial/match.py Fri May 12 23:12:05 2017 -0700 @@ -142,9 +142,14 @@ kindpats.append((kind, pats, source)) return kindpats - m = matcher(root, cwd, normalize, patterns, include=include, + m = matcher(root, cwd, normalize, patterns, include=None, default=default, exact=exact, auditor=auditor, ctx=ctx, listsubrepos=listsubrepos, warn=warn, badfn=badfn) + if include: + im = matcher(root, cwd, normalize, [], include=include, default=default, + exact=False, auditor=auditor, ctx=ctx, + listsubrepos=listsubrepos, warn=warn, badfn=None) + m = intersectmatchers(m, im) if exclude: em = matcher(root, cwd, normalize, [], include=exclude, default=default, exact=False, auditor=auditor, ctx=ctx, @@ -457,6 +462,75 @@ def __repr__(self): return ('' % (self._m1, self._m2)) +def intersectmatchers(m1, m2): + '''Composes two matchers by matching if both of them match. + + The second matcher's non-matching-attributes (root, cwd, bad, explicitdir, + traversedir) are ignored. + ''' + if m1 is None or m2 is None: + return m1 or m2 + if m1.always(): + m = copy.copy(m2) + # TODO: Consider encapsulating these things in a class so there's only + # one thing to copy from m1. + m.bad = m1.bad + m.explicitdir = m1.explicitdir + m.traversedir = m1.traversedir + m.abs = m1.abs + m.rel = m1.rel + m._relativeuipath |= m1._relativeuipath + return m + if m2.always(): + m = copy.copy(m1) + m._relativeuipath |= m2._relativeuipath + return m + return intersectionmatcher(m1, m2) + +class intersectionmatcher(basematcher): + def __init__(self, m1, m2): + super(intersectionmatcher, self).__init__(m1._root, m1._cwd) + self._m1 = m1 + self._m2 = m2 + self.bad = m1.bad + self.explicitdir = m1.explicitdir + self.traversedir = m1.traversedir + + @propertycache + def _files(self): + if self.isexact(): + m1, m2 = self._m1, self._m2 + if not m1.isexact(): + m1, m2 = m2, m1 + return [f for f in m1.files() if m2(f)] + # It neither m1 nor m2 is an exact matcher, we can't easily intersect + # the set of files, because their files() are not always files. For + # example, if intersecting a matcher "-I glob:foo.txt" with matcher of + # "path:dir2", we don't want to remove "dir2" from the set. + return self._m1.files() + self._m2.files() + + def matchfn(self, f): + return self._m1(f) and self._m2(f) + + def visitdir(self, dir): + visit1 = self._m1.visitdir(dir) + if visit1 == 'all': + return self._m2.visitdir(dir) + # bool() because visit1=True + visit2='all' should not be 'all' + return bool(visit1 and self._m2.visitdir(dir)) + + def always(self): + return self._m1.always() and self._m2.always() + + def isexact(self): + return self._m1.isexact() or self._m2.isexact() + + def anypats(self): + return self._m1.anypats() or self._m2.anypats() + + def __repr__(self): + return ('' % (self._m1, self._m2)) + class subdirmatcher(basematcher): """Adapt a matcher to work on a subdirectory only.