[PATCH 4 of 5] extensions: add "uipopulate" hook, called per instance, not per process
Yuya Nishihara
yuya at tcha.org
Sun Nov 18 09:19:20 EST 2018
# HG changeset patch
# User Yuya Nishihara <yuya at tcha.org>
# Date 1542024651 -32400
# Mon Nov 12 21:10:51 2018 +0900
# Node ID 1e1427e07940f57d4146c38a0757e5104a88985c
# Parent 7b0e4efc0a7140a3d6f0814214e0c2bfa6c60acf
extensions: add "uipopulate" hook, called per instance, not per process
In short, this is the "reposetup" function for ui. It allows us to modify
ui attributes without extending ui.__class__. Before, the only way to do
that was to abuse the config dictionary, which is copied across ui instances.
See the next patch for usage example.
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -866,6 +866,9 @@ def _dispatch(req):
# Check abbreviation/ambiguity of shell alias.
shellaliasfn = _checkshellalias(lui, ui, args)
if shellaliasfn:
+ # no additional configs will be set, set up the ui instances
+ for ui_ in uis:
+ extensions.populateui(ui_)
return shellaliasfn()
# check for fallback encoding
@@ -948,6 +951,10 @@ def _dispatch(req):
for ui_ in uis:
ui_.disablepager()
+ # configs are fully loaded, set up the ui instances
+ for ui_ in uis:
+ extensions.populateui(ui_)
+
if options['version']:
return commands.version_(ui)
if options['help']:
diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -405,6 +405,25 @@ def afterloaded(extension, callback):
else:
_aftercallbacks.setdefault(extension, []).append(callback)
+def populateui(ui):
+ """Run extension hooks on the given ui to populate additional members,
+ extend the class dynamically, etc.
+
+ This will be called after the configuration is loaded, and/or extensions
+ are loaded. In general, it's once per ui instance, but in command-server
+ and hgweb, this may be called more than once with the same ui.
+ """
+ for name, mod in extensions(ui):
+ hook = getattr(mod, 'uipopulate', None)
+ if not hook:
+ continue
+ try:
+ hook(ui)
+ except Exception as inst:
+ ui.traceback(force=True)
+ ui.warn(_('*** failed to populate ui by extension %s: %s\n')
+ % (name, stringutil.forcebytestr(inst)))
+
def bind(func, *args):
'''Partial function application
diff --git a/mercurial/help/internals/extensions.txt b/mercurial/help/internals/extensions.txt
--- a/mercurial/help/internals/extensions.txt
+++ b/mercurial/help/internals/extensions.txt
@@ -183,6 +183,34 @@ Command table setup
After ``extsetup``, the ``cmdtable`` is copied into the global command table
in Mercurial.
+Ui instance setup
+-----------------
+
+The optional ``uipopulate`` is called for each ``ui`` instance after
+configuration is loaded, where extensions can set up additional ui members,
+update configuration by ``ui.setconfig()``, and extend the class dynamically.
+
+Typically there are three ``ui`` instances involved in command execution:
+
+``req.ui`` (or ``repo.baseui``)
+ Only system and user configurations are loaded into it.
+``lui``
+ Local repository configuration is loaded as well. This will be used at
+ early dispatching stage where a repository isn't available.
+``repo.ui``
+ The fully-loaded ``ui`` used after a repository is instantiated. This
+ will be created from the ``req.ui`` per repository.
+
+Be aware that the ``req.ui`` wouldn't see local repository configuration,
+and the ``repo.ui`` will be copied from the base ``req.ui`` *after*
+``uipopulate`` is called. Use ``reposetup`` to set up ``repo.ui`` instance
+with repository-local configuration.
+
+In command server and hgweb, this may be called more than once for the same
+``ui`` instance.
+
+(New in Mercurial 4.9)
+
Repository setup
----------------
@@ -304,7 +332,8 @@ uisetup
a change made here will be visible by other extensions during ``extsetup``.
* Monkeypatches or function wraps (``extensions.wrapfunction``) of ``dispatch``
module members
-* Setup of ``pre-*`` and ``post-*`` hooks
+* Set up ``pre-*`` and ``post-*`` hooks. (DEPRECATED. ``uipopulate`` is
+ preferred on Mercurial 4.9 and later.)
* ``pushkey`` setup
extsetup
@@ -314,9 +343,16 @@ extsetup
* Add a global option to all commands
* Extend revsets
+uipopulate
+----------
+
+* Modify ``ui`` instance attributes and configuration variables.
+* Changes to ``ui.__class__`` per instance.
+* Set up all hooks per scoped configuration.
+
reposetup
---------
-* All hooks but ``pre-*`` and ``post-*``
+* Set up all hooks but ``pre-*`` and ``post-*`` for repository
* Modify configuration variables
* Changes to ``repo.__class__``, ``repo.dirstate.__class__``
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -214,6 +214,7 @@ class hgweb(object):
else:
u = uimod.ui.load()
extensions.loadall(u)
+ extensions.populateui(u)
r = hg.repository(u, repo)
else:
# we trust caller to give us a private copy
diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -272,6 +272,7 @@ class hgwebdir(object):
if not baseui:
# set up environment for new ui
extensions.loadall(self.ui)
+ extensions.populateui(self.ui)
def refresh(self):
if self.ui:
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -29,6 +29,7 @@ from . import (
configitems,
encoding,
error,
+ extensions,
formatter,
progress,
pycompat,
@@ -303,6 +304,9 @@ class ui(object):
else:
raise error.ProgrammingError('unknown rctype: %s' % t)
u._maybetweakdefaults()
+ # at the very first ui.load() of the process, this would be noop since
+ # no extension is loaded yet. _dispatch() needs to handle that case.
+ extensions.populateui(u)
return u
def _maybetweakdefaults(self):
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -24,6 +24,9 @@ Test basic extension support
> ui.write(b"uisetup called\\n")
> ui.status(b"uisetup called [status]\\n")
> ui.flush()
+ > def uipopulate(ui):
+ > ui._populatecnt = getattr(ui, "_populatecnt", 0) + 1
+ > ui.write(b"uipopulate called (%d times)\n" % ui._populatecnt)
> def reposetup(ui, repo):
> ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
> ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
@@ -54,13 +57,19 @@ Test basic extension support
$ hg foo
uisetup called
uisetup called [status]
+ uipopulate called (1 times)
+ uipopulate called (1 times)
reposetup called for a
ui == repo.ui
+ uipopulate called (1 times) (chg !)
+ uipopulate called (1 times) (chg !)
reposetup called for a (chg !)
ui == repo.ui (chg !)
Foo
$ hg foo --quiet
uisetup called (no-chg !)
+ uipopulate called (1 times)
+ uipopulate called (1 times)
reposetup called for a (chg !)
ui == repo.ui
Foo
@@ -68,6 +77,8 @@ Test basic extension support
uisetup called [debug] (no-chg !)
uisetup called (no-chg !)
uisetup called [status] (no-chg !)
+ uipopulate called (1 times)
+ uipopulate called (1 times)
reposetup called for a (chg !)
ui == repo.ui
Foo
@@ -76,6 +87,7 @@ Test basic extension support
$ hg clone a b
uisetup called (no-chg !)
uisetup called [status] (no-chg !)
+ uipopulate called (1 times)
reposetup called for a
ui == repo.ui
reposetup called for b
@@ -86,6 +98,7 @@ Test basic extension support
$ hg bar
uisetup called (no-chg !)
uisetup called [status] (no-chg !)
+ uipopulate called (1 times)
Bar
$ echo 'foobar = !' >> $HGRCPATH
@@ -96,8 +109,12 @@ module/__init__.py-style
$ hg foo
uisetup called
uisetup called [status]
+ uipopulate called (1 times)
+ uipopulate called (1 times)
reposetup called for a
ui == repo.ui
+ uipopulate called (1 times) (chg !)
+ uipopulate called (1 times) (chg !)
reposetup called for a (chg !)
ui == repo.ui (chg !)
Foo
@@ -114,8 +131,10 @@ Check that extensions are loaded in phas
> print("2) %s uisetup" % name, flush=True)
> def extsetup():
> print("3) %s extsetup" % name, flush=True)
+ > def uipopulate(ui):
+ > print("4) %s uipopulate" % name, flush=True)
> def reposetup(ui, repo):
- > print("4) %s reposetup" % name, flush=True)
+ > print("5) %s reposetup" % name, flush=True)
>
> bytesname = name.encode('utf-8')
> # custom predicate to check registration of functions at loading
@@ -143,8 +162,12 @@ Check normal command's load order of ext
2) bar uisetup
3) foo extsetup
3) bar extsetup
- 4) foo reposetup
- 4) bar reposetup
+ 4) foo uipopulate
+ 4) bar uipopulate
+ 4) foo uipopulate
+ 4) bar uipopulate
+ 5) foo reposetup
+ 5) bar reposetup
0:c24b9ac61126
Check hgweb's load order of extensions and registration of functions
@@ -167,8 +190,10 @@ Check hgweb's load order of extensions a
2) bar uisetup
3) foo extsetup
3) bar extsetup
- 4) foo reposetup
- 4) bar reposetup
+ 4) foo uipopulate
+ 4) bar uipopulate
+ 5) foo reposetup
+ 5) bar reposetup
(check that revset predicate foo() and bar() are available)
More information about the Mercurial-devel
mailing list