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.
--- a/mercurial/chgserver.py Sat Nov 17 19:11:45 2018 +0900
+++ b/mercurial/chgserver.py Mon Nov 12 21:10:51 2018 +0900
@@ -246,6 +246,10 @@
rpath = options['repository']
path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
+ extensions.populateui(newui)
+ if newui is not newlui:
+ extensions.populateui(newlui)
+
return (newui, newlui)
class channeledsystem(object):
--- a/mercurial/dispatch.py Sat Nov 17 19:11:45 2018 +0900
+++ b/mercurial/dispatch.py Mon Nov 12 21:10:51 2018 +0900
@@ -866,6 +866,9 @@
# 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 @@
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']:
--- a/mercurial/extensions.py Sat Nov 17 19:11:45 2018 +0900
+++ b/mercurial/extensions.py Mon Nov 12 21:10:51 2018 +0900
@@ -405,6 +405,25 @@
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
--- a/mercurial/help/internals/extensions.txt Sat Nov 17 19:11:45 2018 +0900
+++ b/mercurial/help/internals/extensions.txt Mon Nov 12 21:10:51 2018 +0900
@@ -183,6 +183,29 @@
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.
+
+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 +327,8 @@
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 +338,17 @@
* 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-*``. (DEPRECATED. ``uipopulate`` is
+ preferred on Mercurial 4.9 and later.)
* Modify configuration variables
* Changes to ``repo.__class__``, ``repo.dirstate.__class__``
--- a/mercurial/hgweb/hgweb_mod.py Sat Nov 17 19:11:45 2018 +0900
+++ b/mercurial/hgweb/hgweb_mod.py Mon Nov 12 21:10:51 2018 +0900
@@ -214,6 +214,7 @@
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
--- a/mercurial/hgweb/hgwebdir_mod.py Sat Nov 17 19:11:45 2018 +0900
+++ b/mercurial/hgweb/hgwebdir_mod.py Mon Nov 12 21:10:51 2018 +0900
@@ -272,6 +272,7 @@
if not baseui:
# set up environment for new ui
extensions.loadall(self.ui)
+ extensions.populateui(self.ui)
def refresh(self):
if self.ui:
@@ -308,6 +309,7 @@
paths = self.conf
elif isinstance(self.conf, dict):
paths = self.conf.items()
+ extensions.populateui(u)
repos = findrepos(paths)
for prefix, root in u.configitems('collections'):
--- a/mercurial/localrepo.py Sat Nov 17 19:11:45 2018 +0900
+++ b/mercurial/localrepo.py Mon Nov 12 21:10:51 2018 +0900
@@ -454,6 +454,7 @@
if loadhgrc(ui, wdirvfs, hgvfs, requirements):
afterhgrcload(ui, wdirvfs, hgvfs, requirements)
extensions.loadall(ui)
+ extensions.populateui(ui)
# Set of module names of extensions loaded for this repository.
extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
--- a/tests/blackbox-readonly-dispatch.py Sat Nov 17 19:11:45 2018 +0900
+++ b/tests/blackbox-readonly-dispatch.py Mon Nov 12 21:10:51 2018 +0900
@@ -2,6 +2,7 @@
import os
from mercurial import (
dispatch,
+ extensions,
ui as uimod,
)
@@ -11,6 +12,7 @@
Prints command and result value, but does not handle quoting.
"""
ui = uimod.ui.load()
+ extensions.populateui(ui)
ui.status(b"running: %s\n" % cmd)
req = dispatch.request(cmd.split(), ui)
result = dispatch.dispatch(req)
--- a/tests/test-clone.t Sat Nov 17 19:11:45 2018 +0900
+++ b/tests/test-clone.t Mon Nov 12 21:10:51 2018 +0900
@@ -574,6 +574,7 @@
> from mercurial import extensions, hg, ui as uimod
> myui = uimod.ui.load()
> extensions.loadall(myui)
+ > extensions.populateui(myui)
> repo = hg.repository(myui, b'a')
> hg.clone(myui, {}, repo, dest=b"ua", branch=[b"stable",])
> EOF
--- a/tests/test-duplicateoptions.py Sat Nov 17 19:11:45 2018 +0900
+++ b/tests/test-duplicateoptions.py Mon Nov 12 21:10:51 2018 +0900
@@ -29,6 +29,7 @@
u = uimod.ui.load()
extensions.loadall(u)
+extensions.populateui(u)
globalshort = set()
globallong = set()
--- a/tests/test-extension.t Sat Nov 17 19:11:45 2018 +0900
+++ b/tests/test-extension.t Mon Nov 12 21:10:51 2018 +0900
@@ -24,6 +24,9 @@
> 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,26 @@
$ hg foo
uisetup called
uisetup called [status]
+ uipopulate called (1 times)
+ 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 !)
+ uipopulate called (1 times) (chg !)
+ 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)
+ uipopulate called (1 times) (chg !)
+ uipopulate called (1 times) (chg !)
+ uipopulate called (1 times) (chg !)
reposetup called for a (chg !)
ui == repo.ui
Foo
@@ -68,6 +84,11 @@
uisetup called [debug] (no-chg !)
uisetup called (no-chg !)
uisetup called [status] (no-chg !)
+ uipopulate called (1 times)
+ uipopulate called (1 times)
+ uipopulate called (1 times) (chg !)
+ uipopulate called (1 times) (chg !)
+ uipopulate called (1 times) (chg !)
reposetup called for a (chg !)
ui == repo.ui
Foo
@@ -76,8 +97,12 @@
$ hg clone a b
uisetup called (no-chg !)
uisetup called [status] (no-chg !)
+ uipopulate called (1 times)
+ uipopulate called (1 times) (chg !)
+ uipopulate called (1 times) (chg !)
reposetup called for a
ui == repo.ui
+ uipopulate called (1 times)
reposetup called for b
ui == repo.ui
updating to branch default
@@ -86,6 +111,8 @@
$ hg bar
uisetup called (no-chg !)
uisetup called [status] (no-chg !)
+ uipopulate called (1 times)
+ uipopulate called (1 times) (chg !)
Bar
$ echo 'foobar = !' >> $HGRCPATH
@@ -96,8 +123,16 @@
$ hg foo
uisetup called
uisetup called [status]
+ uipopulate called (1 times)
+ 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 !)
+ uipopulate called (1 times) (chg !)
+ uipopulate called (1 times) (chg !)
+ uipopulate called (1 times) (chg !)
reposetup called for a (chg !)
ui == repo.ui (chg !)
Foo
@@ -114,8 +149,10 @@
> 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 +180,14 @@
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
+ 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 +210,12 @@
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
(check that revset predicate foo() and bar() are available)