changeset 32497:9eccd559c592

match: handle includes using new intersectionmatcher
author Martin von Zweigbergk <martinvonz@google.com>
date Fri, 12 May 2017 23:12:05 -0700
parents ca77a243ffa7
children bd56bea5ecf8
files mercurial/match.py tests/test-walk.t
diffstat 2 files changed, 79 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- 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 ('<differencematcher m1=%r, m2=%r>' % (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 ('<intersectionmatcher m1=%r, m2=%r>' % (self._m1, self._m2))
+
 class subdirmatcher(basematcher):
     """Adapt a matcher to work on a subdirectory only.
 
--- a/tests/test-walk.t	Thu May 25 14:32:56 2017 -0700
+++ b/tests/test-walk.t	Fri May 12 23:12:05 2017 -0700
@@ -96,7 +96,7 @@
   f  fenugreek      ../fenugreek
   f  mammals/skunk  skunk
   $ hg debugwalk -I 'relglob:*k' .
-  matcher: <matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes='(?:(?:|.*/)[^/]*k(?:/|$))'>
+  matcher: <intersectionmatcher m1=<matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:(?:|.*/)[^/]*k(?:/|$))'>>
   f  mammals/skunk  skunk
   $ hg debugwalk -I 're:.*k$'
   matcher: <matcher files=[], patterns=None, includes='(?:.*k$)'>
@@ -276,17 +276,17 @@
   f  fenugreek      fenugreek
   f  mammals/skunk  mammals/skunk
   $ hg debugwalk -Ibeans mammals
-  matcher: <matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes='(?:beans(?:/|$))'>
+  matcher: <intersectionmatcher m1=<matcher files=['mammals'], patterns='(?:mammals(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>>
   $ hg debugwalk -Inon-existent
   matcher: <matcher files=[], patterns=None, includes='(?:non\\-existent(?:/|$))'>
   $ hg debugwalk -Inon-existent -Ibeans/black
   matcher: <matcher files=[], patterns=None, includes='(?:non\\-existent(?:/|$)|beans\\/black(?:/|$))'>
   f  beans/black  beans/black
   $ hg debugwalk -Ibeans beans/black
-  matcher: <matcher files=['beans/black'], patterns='(?:beans\\/black(?:/|$))', includes='(?:beans(?:/|$))'>
+  matcher: <intersectionmatcher m1=<matcher files=['beans/black'], patterns='(?:beans\\/black(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans(?:/|$))'>>
   f  beans/black  beans/black  exact
   $ hg debugwalk -Ibeans/black beans
-  matcher: <matcher files=['beans'], patterns='(?:beans(?:/|$))', includes='(?:beans\\/black(?:/|$))'>
+  matcher: <intersectionmatcher m1=<matcher files=['beans'], patterns='(?:beans(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans\\/black(?:/|$))'>>
   f  beans/black  beans/black
   $ hg debugwalk -Xbeans/black beans
   matcher: <differencematcher m1=<matcher files=['beans'], patterns='(?:beans(?:/|$))', includes=None>, m2=<matcher files=[], patterns=None, includes='(?:beans\\/black(?:/|$))'>>