changeset 39566:65b5900f30be

hg: recognize include and exclude patterns when cloning This commit teaches clone() to accept arguments defining file patterns to clone. This is the first step in teaching core code about the existence of a narrow clone. Right now, we only perform validation of the arguments and pass additional options into createopts to influence repository creation. Nothing of consequence happens with that creation option yet, however. For now, arbitrary restrictions exist, such as not allowing patterns for shared repos and disabling local copies when patterns are defined. We can potentially lift these restrictions in the future once partial clone/storage support is more flushed out. I figure it is best to reduce the surface area for bugs for the time being. It may seem weird to prefix these arguments with "store." However, clone is effectively pull + update and file patterns could apply to both the store and the working directory. The prefix is there to disambiguate in the future when this function may want to use different sets of patterns for the store and working directory. Differential Revision: https://phab.mercurial-scm.org/D4536
author Gregory Szorc <gregory.szorc@gmail.com>
date Tue, 11 Sep 2018 17:15:35 -0700
parents 089fc0db0954
children 261f1e8dc300
files mercurial/hg.py
diffstat 1 files changed, 46 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/hg.py	Tue Sep 11 17:11:32 2018 -0700
+++ b/mercurial/hg.py	Tue Sep 11 17:15:35 2018 -0700
@@ -35,6 +35,7 @@
     logcmdutil,
     logexchange,
     merge as mergemod,
+    narrowspec,
     node,
     phases,
     scmutil,
@@ -500,7 +501,8 @@
         util.copyfile(srcbranchcache, dstbranchcache)
 
 def clone(ui, peeropts, source, dest=None, pull=False, revs=None,
-          update=True, stream=False, branch=None, shareopts=None):
+          update=True, stream=False, branch=None, shareopts=None,
+          storeincludepats=None, storeexcludepats=None):
     """Make a copy of an existing repository.
 
     Create a copy of an existing repository in a new directory.  The
@@ -542,6 +544,13 @@
     repository. "identity" means the name is derived from the node of the first
     changeset in the repository. "remote" means the name is derived from the
     remote's path/URL. Defaults to "identity."
+
+    storeincludepats and storeexcludepats: sets of file patterns to include and
+    exclude in the repository copy, respectively. If not defined, all files
+    will be included (a "full" clone). Otherwise a "narrow" clone containing
+    only the requested files will be performed. If ``storeincludepats`` is not
+    defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
+    ``path:.``. If both are empty sets, no files will be cloned.
     """
 
     if isinstance(source, bytes):
@@ -574,6 +583,24 @@
         elif destvfs.listdir():
             raise error.Abort(_("destination '%s' is not empty") % dest)
 
+    createopts = {}
+    narrow = False
+
+    if storeincludepats is not None:
+        narrowspec.validatepatterns(storeincludepats)
+        narrow = True
+
+    if storeexcludepats is not None:
+        narrowspec.validatepatterns(storeexcludepats)
+        narrow = True
+
+    if narrow:
+        # Include everything by default if only exclusion patterns defined.
+        if storeexcludepats and not storeincludepats:
+            storeincludepats = {'path:.'}
+
+        createopts['narrowfiles'] = True
+
     shareopts = shareopts or {}
     sharepool = shareopts.get('pool')
     sharenamemode = shareopts.get('mode')
@@ -605,6 +632,11 @@
             raise error.Abort(_('unknown share naming mode: %s') %
                               sharenamemode)
 
+        # TODO this is a somewhat arbitrary restriction.
+        if narrow:
+            ui.status(_('(pooled storage not supported for narrow clones)\n'))
+            sharepath = None
+
         if sharepath:
             return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
                                   dest, pull=pull, rev=revs, update=update,
@@ -625,6 +657,10 @@
             and not phases.hassecret(srcrepo)):
             copy = not pull and not revs
 
+        # TODO this is a somewhat arbitrary restriction.
+        if narrow:
+            copy = False
+
         if copy:
             try:
                 # we use a lock here because if we race with commit, we
@@ -671,8 +707,9 @@
                           node=node.hex(node.nullid))
         else:
             try:
-                destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
-                                # only pass ui when no srcrepo
+                # only pass ui when no srcrepo
+                destpeer = peer(srcrepo or ui, peeropts, dest, create=True,
+                                createopts=createopts)
             except OSError as inst:
                 if inst.errno == errno.EEXIST:
                     cleandir = None
@@ -714,6 +751,12 @@
                     exchange.pull(local, srcpeer, revs,
                                   streamclonerequested=stream)
             elif srcrepo:
+                # TODO lift restriction once exchange.push() accepts narrow
+                # push.
+                if narrow:
+                    raise error.Abort(_('narrow clone not available for '
+                                        'remote destinations'))
+
                 exchange.push(srcrepo, destpeer, revs=revs,
                               bookmarks=srcrepo._bookmarks.keys())
             else: