Patchwork [4,of,6] match: add prefixdirmatcher to adapt subrepo matcher back

login
register
mail settings
Submitter Yuya Nishihara
Date July 7, 2018, 8:38 a.m.
Message ID <609c30cc3b87da952398.1530952728@mimosa>
Download mbox | patch
Permalink /patch/32679/
State New
Headers show

Comments

Yuya Nishihara - July 7, 2018, 8:38 a.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1528549447 -32400
#      Sat Jun 09 22:04:07 2018 +0900
# Node ID 609c30cc3b87da9523988402ad0a1ba8899411cc
# Parent  f037e8dfa6c2047d0ef86efc2e83fece0fa08d80
match: add prefixdirmatcher to adapt subrepo matcher back

This serves as an inverse function to the subdirmatcher, and will be used
to wrap a fileset matcher of subrepositories. One of the root/prefix paths
could be deduced from the matcher attributes to be wrapped, but we don't
since the callers of this class know the root/prefix paths and can simply
pass them in.

Patch

diff --git a/mercurial/match.py b/mercurial/match.py
--- a/mercurial/match.py
+++ b/mercurial/match.py
@@ -684,6 +684,78 @@  class subdirmatcher(basematcher):
         return ('<subdirmatcher path=%r, matcher=%r>' %
                 (self._path, self._matcher))
 
+class prefixdirmatcher(basematcher):
+    """Adapt a matcher to work on a parent directory.
+
+    The matcher's non-matching-attributes (root, cwd, bad, explicitdir,
+    traversedir) are ignored.
+
+    The prefix path should usually be the relative path from the root of
+    this matcher to the root of the wrapped matcher.
+
+    >>> m1 = match(b'root/d/e', b'f', [b'../a.txt', b'b.txt'])
+    >>> m2 = prefixdirmatcher(b'root', b'd/e/f', b'd/e', m1)
+    >>> bool(m2(b'a.txt'),)
+    False
+    >>> bool(m2(b'd/e/a.txt'))
+    True
+    >>> bool(m2(b'd/e/b.txt'))
+    False
+    >>> m2.files()
+    ['d/e/a.txt', 'd/e/f/b.txt']
+    >>> m2.exact(b'd/e/a.txt')
+    True
+    >>> m2.visitdir(b'd')
+    True
+    >>> m2.visitdir(b'd/e')
+    True
+    >>> m2.visitdir(b'd/e/f')
+    True
+    >>> m2.visitdir(b'd/e/g')
+    False
+    >>> m2.visitdir(b'd/ef')
+    False
+    """
+
+    def __init__(self, root, cwd, path, matcher, badfn=None):
+        super(prefixdirmatcher, self).__init__(root, cwd, badfn)
+        if not path:
+            raise error.ProgrammingError('prefix path must not be empty')
+        self._path = path
+        self._pathprefix = path + '/'
+        self._matcher = matcher
+
+    @propertycache
+    def _files(self):
+        return [self._pathprefix + f for f in self._matcher._files]
+
+    def matchfn(self, f):
+        if not f.startswith(self._pathprefix):
+            return False
+        return self._matcher.matchfn(f[len(self._pathprefix):])
+
+    @propertycache
+    def _pathdirs(self):
+        return set(util.finddirs(self._path)) | {'.'}
+
+    def visitdir(self, dir):
+        if dir == self._path:
+            return self._matcher.visitdir('.')
+        if dir.startswith(self._pathprefix):
+            return self._matcher.visitdir(dir[len(self._pathprefix):])
+        return dir in self._pathdirs
+
+    def isexact(self):
+        return self._matcher.isexact()
+
+    def prefix(self):
+        return self._matcher.prefix()
+
+    @encoding.strmethod
+    def __repr__(self):
+        return ('<prefixdirmatcher path=%r, matcher=%r>'
+                % (pycompat.bytestr(self._path), self._matcher))
+
 class unionmatcher(basematcher):
     """A matcher that is the union of several matchers.