localrepo: define "features" on repository instances (API)
authorGregory Szorc <gregory.szorc@gmail.com>
Wed, 19 Sep 2018 14:36:57 -0700
changeset 39850 d89d5bc06eaa
parent 39849 d3d4b4b5f725
child 39851 1f7b3b980af8
localrepo: define "features" on repository instances (API) There are a handful of attributes/methods on repository instances that describe the behavior of the repository. Furthermore, there is an unbound set of repository descriptors that we may wish to expose. For example, an extension may wish to add a descriptor and have monkeypatched functions look for the presence of an attribute before taking actions. This commit introduces a "features" mechanism to allow repositories to self-advertise an arbitrary set of strings that describe repository behavior or capabilities. We implement basic support for advertising a few features to give an idea of what I want to use this for. Differential Revision: https://phab.mercurial-scm.org/D4709
mercurial/bundle2.py
mercurial/localrepo.py
mercurial/repository.py
mercurial/streamclone.py
--- a/mercurial/bundle2.py	Wed Sep 19 17:27:37 2018 -0700
+++ b/mercurial/bundle2.py	Wed Sep 19 14:36:57 2018 -0700
@@ -1798,7 +1798,7 @@
                 "non-empty and does not use tree manifests"))
         op.repo.requirements.add('treemanifest')
         op.repo.svfs.options = localrepo.resolvestorevfsoptions(
-            op.repo.ui, op.repo.requirements)
+            op.repo.ui, op.repo.requirements, op.repo.features)
         op.repo._writerequirements()
     extrakwargs = {}
     targetphase = inpart.params.get('targetphase')
--- a/mercurial/localrepo.py	Wed Sep 19 17:27:37 2018 -0700
+++ b/mercurial/localrepo.py	Wed Sep 19 14:36:57 2018 -0700
@@ -478,6 +478,8 @@
     # At this point, we know we should be capable of opening the repository.
     # Now get on with doing that.
 
+    features = set()
+
     # The "store" part of the repository holds versioned data. How it is
     # accessed is determined by various requirements. The ``shared`` or
     # ``relshared`` requirements indicate the store lives in the path contained
@@ -494,6 +496,8 @@
             raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
                                     b'directory %s') % sharedvfs.base)
 
+        features.add(repository.REPO_FEATURE_SHARED_STORAGE)
+
         storebasepath = sharedvfs.base
         cachepath = sharedvfs.join(b'cache')
     else:
@@ -508,7 +512,7 @@
     hgvfs.createmode = store.createmode
 
     storevfs = store.vfs
-    storevfs.options = resolvestorevfsoptions(ui, requirements)
+    storevfs.options = resolvestorevfsoptions(ui, requirements, features)
 
     # The cache vfs is used to manage cache files.
     cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
@@ -528,6 +532,7 @@
         typ = fn(ui=ui,
                  intents=intents,
                  requirements=requirements,
+                 features=features,
                  wdirvfs=wdirvfs,
                  hgvfs=hgvfs,
                  store=store,
@@ -564,6 +569,7 @@
         sharedpath=storebasepath,
         store=store,
         cachevfs=cachevfs,
+        features=features,
         intents=intents)
 
 def gathersupportedrequirements(ui):
@@ -643,7 +649,7 @@
 
     return storemod.basicstore(path, vfstype)
 
-def resolvestorevfsoptions(ui, requirements):
+def resolvestorevfsoptions(ui, requirements, features):
     """Resolve the options to pass to the store vfs opener.
 
     The returned dict is used to influence behavior of the storage layer.
@@ -664,11 +670,11 @@
     # opener options for it because those options wouldn't do anything
     # meaningful on such old repos.
     if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
-        options.update(resolverevlogstorevfsoptions(ui, requirements))
+        options.update(resolverevlogstorevfsoptions(ui, requirements, features))
 
     return options
 
-def resolverevlogstorevfsoptions(ui, requirements):
+def resolverevlogstorevfsoptions(ui, requirements, features):
     """Resolve opener options specific to revlogs."""
 
     options = {}
@@ -756,8 +762,10 @@
 
         return filelog.narrowfilelog(self.svfs, path, self.narrowmatch())
 
-def makefilestorage(requirements, **kwargs):
+def makefilestorage(requirements, features, **kwargs):
     """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
+    features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
+
     if repository.NARROW_REQUIREMENT in requirements:
         return revlognarrowfilestorage
     else:
@@ -831,7 +839,7 @@
 
     def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
                  supportedrequirements, sharedpath, store, cachevfs,
-                 intents=None):
+                 features, intents=None):
         """Create a new local repository instance.
 
         Most callers should use ``hg.repository()``, ``localrepo.instance()``,
@@ -873,6 +881,10 @@
         cachevfs
            ``vfs.vfs`` used for cache files.
 
+        features
+           ``set`` of bytestrings defining features/capabilities of this
+           instance.
+
         intents
            ``set`` of system strings indicating what this repo will be used
            for.
@@ -891,6 +903,7 @@
         self.sharedpath = sharedpath
         self.store = store
         self.cachevfs = cachevfs
+        self.features = features
 
         self.filtername = None
 
--- a/mercurial/repository.py	Wed Sep 19 17:27:37 2018 -0700
+++ b/mercurial/repository.py	Wed Sep 19 14:36:57 2018 -0700
@@ -19,6 +19,13 @@
 # we should move this to just "narrow" or similar.
 NARROW_REQUIREMENT = 'narrowhg-experimental'
 
+# Local repository feature string.
+
+# Revlogs are being used for file storage.
+REPO_FEATURE_REVLOG_FILE_STORAGE = b'revlogfilestorage'
+# The storage part of the repository is shared from an external source.
+REPO_FEATURE_SHARED_STORAGE = b'sharedstore'
+
 class ipeerconnection(interfaceutil.Interface):
     """Represents a "connection" to a repository.
 
@@ -1275,6 +1282,24 @@
     requirements = interfaceutil.Attribute(
         """Set of requirements this repo uses.""")
 
+    features = interfaceutil.Attribute(
+        """Set of "features" this repository supports.
+
+        A "feature" is a loosely-defined term. It can refer to a feature
+        in the classical sense or can describe an implementation detail
+        of the repository. For example, a ``readonly`` feature may denote
+        the repository as read-only. Or a ``revlogfilestore`` feature may
+        denote that the repository is using revlogs for file storage.
+
+        The intent of features is to provide a machine-queryable mechanism
+        for repo consumers to test for various repository characteristics.
+
+        Features are similar to ``requirements``. The main difference is that
+        requirements are stored on-disk and represent requirements to open the
+        repository. Features are more run-time capabilities of the repository
+        and more granular capabilities (which may be derived from requirements).
+        """)
+
     filtername = interfaceutil.Attribute(
         """Name of the repoview that is active on this repo.""")
 
--- a/mercurial/streamclone.py	Wed Sep 19 17:27:37 2018 -0700
+++ b/mercurial/streamclone.py	Wed Sep 19 14:36:57 2018 -0700
@@ -168,7 +168,7 @@
         repo.requirements = requirements | (
                 repo.requirements - repo.supportedformats)
         repo.svfs.options = localrepo.resolvestorevfsoptions(
-            repo.ui, repo.requirements)
+            repo.ui, repo.requirements, repo.features)
         repo._writerequirements()
 
         if rbranchmap:
@@ -643,5 +643,5 @@
     repo.requirements = set(requirements) | (
             repo.requirements - repo.supportedformats)
     repo.svfs.options = localrepo.resolvestorevfsoptions(
-        repo.ui, repo.requirements)
+        repo.ui, repo.requirements, repo.features)
     repo._writerequirements()