[PATCH 6 of 6 RFC] hg: hook immutable repos up to registrar and dispatch

Gregory Szorc gregory.szorc at gmail.com
Fri Jun 9 02:36:10 EDT 2017

# HG changeset patch
# User Gregory Szorc <gregory.szorc at gmail.com>
# Date 1496980564 25200
#      Thu Jun 08 20:56:04 2017 -0700
# Node ID 872abe4302f5fab719ac0774d5725aff9afa5c20
# Parent  1075b79fc1809c69e1819c654ea1f635c70f1d70
hg: hook immutable repos up to registrar and dispatch

An end goal of the mutable/immutable repo split is for commands
to be passed immutable repos by default so all operations are fast
and safe. Getting there will be a long endeavor and will be a
significant BC breakage.

In this commit, we introduce a new argument to @command that specifies
an immutable repo is wanted. The value is plugged into the dispatch
and repo instantiation mechanism so commands can opt into immutable

There are no backwards incompatibilities with the implementation.

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -489,8 +489,12 @@ class cmdalias(object):
         return aliasargs(self.fn, args)
     def __getattr__(self, name):
-        adefaults = {r'norepo': True,
-                     r'optionalrepo': False, r'inferrepo': False}
+        adefaults = {
+            r'norepo': True,
+            r'optionalrepo': False,
+            r'inferrepo': False,
+            r'immutablerepo': False,
+        }
         if name not in adefaults:
             raise AttributeError(name)
         if self.badalias or util.safehasattr(self, 'shell'):
@@ -883,7 +887,8 @@ def _dispatch(req):
                     repo = hg.repository(ui, path=path,
-                                         presetupfuncs=req.prereposetups)
+                                         presetupfuncs=req.prereposetups,
+                                         immutable=func.immutablerepo)
                     if not repo.local():
                         raise error.Abort(_("repository '%s' is not local")
                                           % path)
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -10,6 +10,7 @@ from __future__ import absolute_import
 import errno
 import hashlib
+import inspect
 import os
 import shutil
@@ -148,9 +149,18 @@ def openpath(ui, path):
 # a list of (ui, repo) functions called for wire peer initialization
 wirepeersetupfuncs = []
-def _peerorrepo(ui, path, create=False, presetupfuncs=None):
+def _peerorrepo(ui, path, create=False, presetupfuncs=None, immutable=False):
     """return a repository object for the specified path"""
-    obj = _peerlookup(path).instance(ui, path, create)
+    factory = _peerlookup(path)
+    try:
+        obj = factory.instance(ui, path, create, immutable=immutable)
+    except TypeError:
+        # Not all types support immutable argument.
+        keywords = inspect.getargspec(factory.instance).keywords or {}
+        if 'immutable' in keywords:
+            raise
+        obj = factory.instance(ui, path, create)
     ui = getattr(obj, "ui", ui)
     for f in presetupfuncs or []:
         f(ui, obj)
@@ -163,9 +173,10 @@ def _peerorrepo(ui, path, create=False, 
             f(ui, obj)
     return obj
-def repository(ui, path='', create=False, presetupfuncs=None):
+def repository(ui, path='', create=False, presetupfuncs=None, immutable=False):
     """return a repository object for the specified path"""
-    peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs)
+    peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs,
+                       immutable=immutable)
     repo = peer.local()
     if not repo:
         raise error.Abort(_("repository '%s' is not local") %
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -2060,8 +2060,9 @@ def undoname(fn):
     assert name.startswith('journal')
     return os.path.join(base, name.replace('journal', 'undo', 1))
-def instance(ui, path, create):
-    return localrepository(ui, util.urllocalpath(path), create)
+def instance(ui, path, create, immutable=False):
+    cls = immutablelocalrepository if immutable else localrepository
+    return cls(ui, util.urllocalpath(path), create)
 def islocal(path):
     return True
diff --git a/mercurial/registrar.py b/mercurial/registrar.py
--- a/mercurial/registrar.py
+++ b/mercurial/registrar.py
@@ -124,13 +124,21 @@ class command(_funcregistrarbase):
     command line arguments. If True, arguments will be examined for potential
     repository locations. See ``findrepo()``. If a repository is found, it
     will be used.
+    The _immutablerepo argument defines whether the command is read-only. If
+    set, an immutable repository instance will be passed to the command instead
+    of a mutable one. Once the mutable/immutable split is complete, this API
+    will be refactored. So it should be considered temporary and not used by
+    extensions.
     def _doregister(self, func, name, options=(), synopsis=None,
-                    norepo=False, optionalrepo=False, inferrepo=False):
+                    norepo=False, optionalrepo=False, inferrepo=False,
+                    _immutablerepo=False):
         func.norepo = norepo
         func.optionalrepo = optionalrepo
         func.inferrepo = inferrepo
+        func.immutablerepo = _immutablerepo
         if synopsis:
             self._table[name] = func, list(options), synopsis

More information about the Mercurial-devel mailing list