[PATCH 1 of 2] extensions: permit extension interdependency through load-time hooking

Brodie Rao dackze at gmail.com
Mon Jun 1 12:42:52 CDT 2009


# HG changeset patch
# User Brodie Rao <me+hg at dackz.net>
# Date 1243877702 14400
# Node ID f9a18bcef2996cf9775b3a27b65e4d674d728fd2
# Parent  68e0a55eee6e97df2bbb4548c6b6dbd2d2b7bb97
extensions: permit extension interdependency through load-time hooking

To let an extension safely use or modify another extension, a new API is
added: extensions.hook(shortname, func). If the extension identified by
shortname is already loaded, func is executed immediately; otherwise, func
is executed immediately after the extension is loaded, before its
uisetup() is called.

For example, the color extension wraps various mq commands. In the past it
has used two different methods of finding mq to wrap it:

1. from hgext import mq

This bypasses the extension machinery, and can potentially load a different
mq than what the user specified in hgrc.

2. extensions.find('mq')

This fails if mq is not yet loaded. Whether it's loaded yet depends on the
order of [extensions] in hgrc.

Using extensions.hook(), the hook function is only executed if the target
extension is loaded, and it doesn't cause the wrong mq to be loaded.

diff --git a/hgext/color.py b/hgext/color.py
--- a/hgext/color.py
+++ b/hgext/color.py
@@ -232,13 +232,11 @@ def uisetup(ui):
     _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
     _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
     _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
-    try:
-        mq = extensions.find('mq')
+
+    def setupmq(mq):
         _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
         _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
-    except KeyError:
-        # The mq extension is not enabled
-        pass
+    extensions.hook('mq', setupmq)
 
 def _setupcmd(ui, cmd, table, func, effectsmap):
     '''patch in command to command table and load effect map'''
diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -10,6 +10,7 @@ import util, cmdutil
 from i18n import _
 
 _extensions = {}
+_hooks = {}
 _order = []
 
 def extensions():
@@ -66,6 +67,18 @@ def load(ui, name, path):
     _extensions[shortname] = mod
     _order.append(shortname)
 
+    for func in _hooks.pop(shortname, []):
+        try:
+            func(mod)
+        except KeyboardInterrupt:
+            raise
+        except Exception, inst:
+            errname = func.__module__.rsplit('.', 1)[1]
+            ui.warn(_('*** extension %s failed to hook extension %s: %s\n')
+                    % (errname, name, inst))
+            if ui.traceback():
+                raise
+
     uisetup = getattr(mod, 'uisetup', None)
     if uisetup:
         uisetup(ui)
@@ -90,6 +103,14 @@ def loadall(ui):
             if ui.traceback():
                 return 1
 
+def hook(shortname, func):
+    if shortname in _extensions:
+        func(_extensions[shortname])
+    elif shortname in _hooks:
+        _hooks[shortname].append(func)
+    else:
+        _hooks[shortname] = [func]
+
 def wrapcommand(table, command, wrapper):
     aliases, entry = cmdutil.findcmd(command, table)
     for alias, e in table.iteritems():
diff --git a/tests/test-extension b/tests/test-extension
--- a/tests/test-extension
+++ b/tests/test-extension
@@ -102,3 +102,30 @@ echo "hgext/mq=" >> $HGRCPATH
 
 echo % show extensions
 hg debugextensions
+echo 'mq = !' >> $HGRCPATH
+echo 'hgext.mq = !' >> $HGRCPATH
+echo 'hgext/mq = !' >> $HGRCPATH
+
+echo % hook extensions
+cat > debughookextension.py <<EOF
+'''hook another extension
+'''
+from mercurial import extensions
+
+def wrap(ext):
+    def wrapper(orig, ui, *args, **kw):
+        ui.write('wrapped\n')
+    extensions.wrapcommand(ext.cmdtable, 'debugextensions', wrapper)
+
+def uisetup(ui):
+    extensions.hook('debugissue811', wrap)
+EOF
+debughookpath=`pwd`/debughookextension.py
+echo "debugissue811 = $debugpath" >> $HGRCPATH
+echo "debughookextension = $debughookpath" >> $HGRCPATH
+hg debugextensions
+echo 'debugissue811 = !' >> $HGRCPATH
+echo 'debughookextension = !' >> $HGRCPATH
+echo "debughookextension = $debughookpath" >> $HGRCPATH
+echo "debugissue811 = $debugpath" >> $HGRCPATH
+hg debugextensions
diff --git a/tests/test-extension.out b/tests/test-extension.out
--- a/tests/test-extension.out
+++ b/tests/test-extension.out
@@ -54,3 +54,6 @@ global options:
 % show extensions
 debugissue811
 mq
+% hook extensions
+wrapped
+wrapped


More information about the Mercurial-devel mailing list