diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -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) diff --git a/mercurial/repository.py b/mercurial/repository.py --- a/mercurial/repository.py +++ b/mercurial/repository.py @@ -1222,8 +1222,21 @@ Raises ``error.LookupError`` if the node is not known. """ -class completelocalrepository(interfaceutil.Interface): - """Monolithic interface for local repositories. +class ilocalrepositoryfilestorage(interfaceutil.Interface): + """Local repository sub-interface providing access to tracked file storage. + + This interface defines how a repository accesses storage for a single + tracked file path. + """ + + def file(f): + """Obtain a filelog for a tracked path. + + The returned type conforms to the ``ifilestorage`` interface. + """ + +class ilocalrepositorymain(interfaceutil.Interface): + """Main interface for local repositories. This currently captures the reality of things - not how things should be. """ @@ -1439,12 +1452,6 @@ def wjoin(f, *insidef): """Calls self.vfs.reljoin(self.root, f, *insidef)""" - def file(f): - """Obtain a filelog for a tracked path. - - The returned type conforms to the ``ifilestorage`` interface. - """ - def setparents(p1, p2): """Set the parent nodes of the working directory.""" @@ -1572,3 +1579,7 @@ def savecommitmessage(text): pass + +class completelocalrepository(ilocalrepositorymain, + ilocalrepositoryfilestorage): + """Complete interface for a local repository.""" diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py --- a/mercurial/statichttprepo.py +++ b/mercurial/statichttprepo.py @@ -134,7 +134,8 @@ def canpush(self): return False -class statichttprepository(localrepo.localrepository): +class statichttprepository(localrepo.localrepository, + localrepo.revlogfilestorage): supported = localrepo.localrepository._basesupported def __init__(self, ui, path): diff --git a/tests/test-check-interfaces.py b/tests/test-check-interfaces.py --- a/tests/test-check-interfaces.py +++ b/tests/test-check-interfaces.py @@ -142,8 +142,10 @@ ziverify.verifyClass(repository.ipeerbase, unionrepo.unionpeer) checkzobject(unionrepo.unionpeer(dummyrepo())) - ziverify.verifyClass(repository.completelocalrepository, + ziverify.verifyClass(repository.ilocalrepositorymain, localrepo.localrepository) + ziverify.verifyClass(repository.ilocalrepositoryfilestorage, + localrepo.revlogfilestorage) repo = localrepo.makelocalrepository(ui, rootdir) checkzobject(repo)