mercurial/localrepo.py
changeset 39764 e4e881572382
parent 39763 5ccd791344f3
child 39765 3e801ffd7269
--- a/mercurial/localrepo.py	Tue Sep 18 15:15:24 2018 -0700
+++ b/mercurial/localrepo.py	Tue Sep 18 15:29:42 2018 -0700
@@ -380,11 +380,32 @@
     """Create a local repository object.
 
     Given arguments needed to construct a local repository, this function
-    derives a type suitable for representing that repository and returns an
-    instance of it.
+    performs various early repository loading functionality (such as
+    reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
+    the repository can be opened, derives a type suitable for representing
+    that repository, and returns an instance of it.
 
     The returned object conforms to the ``repository.completelocalrepository``
     interface.
+
+    The repository type is derived by calling a series of factory functions
+    for each aspect/interface of the final repository. These are defined by
+    ``REPO_INTERFACES``.
+
+    Each factory function is called to produce a type implementing a specific
+    interface. The cumulative list of returned types will be combined into a
+    new type and that type will be instantiated to represent the local
+    repository.
+
+    The factory functions each receive various state that may be consulted
+    as part of deriving a type.
+
+    Extensions should wrap these factory functions to customize repository type
+    creation. Note that an extension's wrapped function may be called even if
+    that extension is not loaded for the repo being constructed. Extensions
+    should check if their ``__name__`` appears in the
+    ``extensionmodulenames`` set passed to the factory function and no-op if
+    not.
     """
     ui = baseui.copy()
     # Prevent copying repo configuration.
@@ -430,6 +451,9 @@
     else:
         extensions.loadall(ui)
 
+    # Set of module names of extensions loaded for this repository.
+    extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
+
     supportedrequirements = gathersupportedrequirements(ui)
 
     # We first validate the requirements are known.
@@ -490,7 +514,46 @@
     cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
     cachevfs.createmode = store.createmode
 
-    return localrepository(
+    # Now resolve the type for the repository object. We do this by repeatedly
+    # calling a factory function to produces types for specific aspects of the
+    # repo's operation. The aggregate returned types are used as base classes
+    # for a dynamically-derived type, which will represent our new repository.
+
+    bases = []
+    extrastate = {}
+
+    for iface, fn in REPO_INTERFACES:
+        # We pass all potentially useful state to give extensions tons of
+        # flexibility.
+        typ = fn(ui=ui,
+                 intents=intents,
+                 requirements=requirements,
+                 wdirvfs=wdirvfs,
+                 hgvfs=hgvfs,
+                 store=store,
+                 storevfs=storevfs,
+                 storeoptions=storevfs.options,
+                 cachevfs=cachevfs,
+                 extensionmodulenames=extensionmodulenames,
+                 extrastate=extrastate,
+                 baseclasses=bases)
+
+        if not isinstance(typ, type):
+            raise error.ProgrammingError('unable to construct type for %s' %
+                                         iface)
+
+        bases.append(typ)
+
+    # type() allows you to use characters in type names that wouldn't be
+    # recognized as Python symbols in source code. We abuse that to add
+    # rich information about our constructed repo.
+    name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
+        wdirvfs.base,
+        b','.join(sorted(requirements))))
+
+    cls = type(name, tuple(bases), {})
+
+    return cls(
         baseui=baseui,
         ui=ui,
         origroot=path,
@@ -666,8 +729,47 @@
 
     return options
 
-@interfaceutil.implementer(repository.completelocalrepository)
+def makemain(**kwargs):
+    """Produce a type conforming to ``ilocalrepositorymain``."""
+    return localrepository
+
+@interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
+class revlogfilestorage(object):
+    """File storage when using revlogs."""
+
+    def file(self, path):
+        if path[0] == b'/':
+            path = path[1:]
+
+        return filelog.filelog(self.svfs, path)
+
+def makefilestorage(requirements, **kwargs):
+    """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
+    return revlogfilestorage
+
+# List of repository interfaces and factory functions for them. Each
+# will be called in order during ``makelocalrepository()`` to iteratively
+# derive the final type for a local repository instance.
+REPO_INTERFACES = [
+    (repository.ilocalrepositorymain, makemain),
+    (repository.ilocalrepositoryfilestorage, makefilestorage),
+]
+
+@interfaceutil.implementer(repository.ilocalrepositorymain)
 class localrepository(object):
+    """Main class for representing local repositories.
+
+    All local repositories are instances of this class.
+
+    Constructed on its own, instances of this class are not usable as
+    repository objects. To obtain a usable repository object, call
+    ``hg.repository()``, ``localrepo.instance()``, or
+    ``localrepo.makelocalrepository()``. The latter is the lowest-level.
+    ``instance()`` adds support for creating new repositories.
+    ``hg.repository()`` adds more extension integration, including calling
+    ``reposetup()``. Generally speaking, ``hg.repository()`` should be
+    used.
+    """
 
     # obsolete experimental requirements:
     #  - manifestv2: An experimental new manifest format that allowed
@@ -1325,11 +1427,6 @@
     def wjoin(self, f, *insidef):
         return self.vfs.reljoin(self.root, f, *insidef)
 
-    def file(self, f):
-        if f[0] == '/':
-            f = f[1:]
-        return filelog.filelog(self.svfs, f)
-
     def setparents(self, p1, p2=nullid):
         with self.dirstate.parentchange():
             copies = self.dirstate.setparents(p1, p2)