[PATCH] dispatch: modifying an option list without copying it first makes a mess

Dan Villiom Podlaski Christiansen danchr at gmail.com
Fri Jul 10 06:59:57 CDT 2009


# HG changeset patch
# User Dan Villiom Podlaski Christiansen <danchr at gmail.com>
# Date 1247225972 -7200
# Node ID db5aba03c61248ce61998b17ef39877164c53633
# Parent  561ff8d9e4f073ced576eff21b535d81d1314b1e
dispatch: modifying an option list without copying it first makes a mess.

When one or more option lists refer to the same list instance,
aliasing each other, updates to one list them will affects the others
as well. This can lead to changes to options for one command
inadvertently affecting other commands.

We try to ensure this distinctness of option lists in the main two
functions that modify command tables: extensions.wrapcommand() and
dispatch._dispatch().

To be on the safe side, wrapcommand() always uses a copy of the option
list.  For _dispatch(), we can maintain a set of instance IDs, and
only clone duplicates.

diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -322,6 +322,16 @@ def runcommand(lui, repo, cmd, fullargs,
 
 _loaded = set()
 def _dispatch(ui, args):
+    optionids = set()
+    def distinctifyoptions(cmdtable):
+        '''ensures that every option list is a distinct instance.'''
+        for cmd in cmdtable.iterkeys():
+            entry = cmdtable[cmd]
+            if id(entry[1]) in optionids:
+                entry = (entry[0], list(entry[1])) + entry[2:]
+                cmdtable[cmd] = entry
+            optionids.add(id(entry[1]))
+
     # read --config before doing anything else
     # (e.g. to change trust settings for reading .hg/hgrc)
     _parseconfig(ui, _earlygetopt(['--config'], args))
@@ -350,6 +360,8 @@ def _dispatch(ui, args):
         lui.readconfig(os.path.join(path, ".hg", "hgrc"))
 
     extensions.loadall(lui)
+    distinctifyoptions(commands.table)
+
     for name, module in extensions.extensions():
         if name in _loaded:
             continue
@@ -367,6 +379,8 @@ def _dispatch(ui, args):
         if overrides:
             ui.warn(_("extension '%s' overrides commands: %s\n")
                     % (name, " ".join(overrides)))
+        distinctifyoptions(cmdtable)
+
         commands.table.update(cmdtable)
         _loaded.add(name)
 
diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -102,12 +102,14 @@ def wrapcommand(table, command, wrapper)
         return util.checksignature(wrapper)(
             util.checksignature(origfn), *args, **kwargs)
 
+    # extensions often modify the option list, so we clone it to
+    # make sure updates don't make a mess elsewhere...
+    options = list(entry[1])
+
     wrap.__doc__ = getattr(origfn, '__doc__')
     wrap.__module__ = getattr(origfn, '__module__')
 
-    newentry = list(entry)
-    newentry[0] = wrap
-    table[key] = tuple(newentry)
+    table[key] = entry = (wrap, options) + entry[2:]
     return entry
 
 def wrapfunction(container, funcname, wrapper):


More information about the Mercurial-devel mailing list