# HG changeset patch # User Yuya Nishihara <y...@tcha.org> # Date 1542024651 -32400 # Mon Nov 12 21:10:51 2018 +0900 # Node ID c178d702bd35d0e4f3f3903c9a50f48221359d08 # Parent 2ca38e8c9fe7464596073de8c6c35a291c1e09c4 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/chgserver.py b/mercurial/chgserver.py --- a/mercurial/chgserver.py +++ b/mercurial/chgserver.py @@ -246,6 +246,10 @@ def _loadnewui(srcui, args): 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): 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,29 @@ 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. + +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 @@ 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 +338,17 @@ 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-*``. (DEPRECATED. ``uipopulate`` is + preferred on Mercurial 4.9 and later.) * 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: @@ -308,6 +309,7 @@ class hgwebdir(object): 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'): diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -454,6 +454,7 @@ def makelocalrepository(baseui, path, in 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)} diff --git a/tests/blackbox-readonly-dispatch.py b/tests/blackbox-readonly-dispatch.py --- a/tests/blackbox-readonly-dispatch.py +++ b/tests/blackbox-readonly-dispatch.py @@ -2,6 +2,7 @@ from __future__ import absolute_import import os from mercurial import ( dispatch, + extensions, ui as uimod, ) @@ -11,6 +12,7 @@ def testdispatch(cmd): 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) diff --git a/tests/test-clone.t b/tests/test-clone.t --- a/tests/test-clone.t +++ b/tests/test-clone.t @@ -574,6 +574,7 @@ iterable in addbranchrevs() > 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 diff --git a/tests/test-duplicateoptions.py b/tests/test-duplicateoptions.py --- a/tests/test-duplicateoptions.py +++ b/tests/test-duplicateoptions.py @@ -29,6 +29,7 @@ hgrc.close() u = uimod.ui.load() extensions.loadall(u) +extensions.populateui(u) globalshort = set() globallong = set() 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,26 @@ Test basic extension support $ 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 @@ 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) + 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 @@ Test basic extension support $ 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 @@ Test basic extension support $ 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 @@ module/__init__.py-style $ 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 @@ 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 +180,14 @@ 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 + 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 @@ 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 + 4) foo uipopulate + 4) bar uipopulate + 5) foo reposetup + 5) bar reposetup (check that revset predicate foo() and bar() are available) _______________________________________________ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel