diff mercurial/sparse.py @ 33320:153456f02426

sparse: move function for resolving sparse matcher into core As part of the move, the function arguments changed so revs are passed as a list instead of *args. This allows us to use keyword arguments properly. Since the plan is to integrate sparse into core and have it enabled by default, we need to prepare for a sparse matcher to always be obtained and operated on. As part of the move, we inserted code that returns an always matcher if sparse isn't enabled. Some callers in the sparse extension take this into account and conditionally perform matching depending on whether the special always matcher is seen. I /think/ this may have sped up some operations where the extension is installed but no sparse config is activated. One thing I'm ensure of in this code is whether os.path.dirname() is semantically correct. os.posixpath.dirname() (which is exported as pathutil.dirname) might be a better choise because all patterns should be using posix directory separators (/) instead of Windows (\). There's an inline comment that implies Windows was tested. So hopefully it won't be a problem. We can improve this in a follow-up. I've added a TODO to track it.
author Gregory Szorc <gregory.szorc@gmail.com>
date Thu, 06 Jul 2017 17:41:45 -0700
parents 526255fe7899
children d09e948dc303
line wrap: on
line diff
--- a/mercurial/sparse.py	Thu Jul 06 17:39:24 2017 -0700
+++ b/mercurial/sparse.py	Thu Jul 06 17:41:45 2017 -0700
@@ -8,11 +8,14 @@
 from __future__ import absolute_import
 
 import hashlib
+import os
 
 from .i18n import _
 from .node import nullid
 from . import (
     error,
+    match as matchmod,
+    pycompat,
 )
 
 # Whether sparse features are enabled. This variable is intended to be
@@ -193,3 +196,70 @@
     for i in additional:
         includes.add(i)
     writetemporaryincludes(repo, includes)
+
+def matcher(repo, revs=None, includetemp=True):
+    """Obtain a matcher for sparse working directories for the given revs.
+
+    If multiple revisions are specified, the matcher is the union of all
+    revs.
+
+    ``includetemp`` indicates whether to use the temporary sparse profile.
+    """
+    # If sparse isn't enabled, sparse matcher matches everything.
+    if not enabled:
+        return matchmod.always(repo.root, '')
+
+    if not revs or revs == [None]:
+        revs = [repo.changelog.rev(node)
+                for node in repo.dirstate.parents() if node != nullid]
+
+    signature = configsignature(repo, includetemp=includetemp)
+
+    key = '%s %s' % (signature, ' '.join(map(pycompat.bytestr, revs)))
+
+    result = repo._sparsematchercache.get(key)
+    if result:
+        return result
+
+    matchers = []
+    for rev in revs:
+        try:
+            includes, excludes, profiles = patternsforrev(repo, rev)
+
+            if includes or excludes:
+                # Explicitly include subdirectories of includes so
+                # status will walk them down to the actual include.
+                subdirs = set()
+                for include in includes:
+                    # TODO consider using posix path functions here so Windows
+                    # \ directory separators don't come into play.
+                    dirname = os.path.dirname(include)
+                    # basename is used to avoid issues with absolute
+                    # paths (which on Windows can include the drive).
+                    while os.path.basename(dirname):
+                        subdirs.add(dirname)
+                        dirname = os.path.dirname(dirname)
+
+                matcher = matchmod.match(repo.root, '', [],
+                                         include=includes, exclude=excludes,
+                                         default='relpath')
+                if subdirs:
+                    matcher = matchmod.forceincludematcher(matcher, subdirs)
+                matchers.append(matcher)
+        except IOError:
+            pass
+
+    if not matchers:
+        result = matchmod.always(repo.root, '')
+    elif len(matchers) == 1:
+        result = matchers[0]
+    else:
+        result = matchmod.unionmatcher(matchers)
+
+    if includetemp:
+        tempincludes = readtemporaryincludes(repo)
+        result = matchmod.forceincludematcher(result, tempincludes)
+
+    repo._sparsematchercache[key] = result
+
+    return result