[PATCH] extensions: formalize concept of experimental extensions

Gregory Szorc gregory.szorc at gmail.com
Sun Mar 12 20:41:49 EDT 2017

# HG changeset patch
# User Gregory Szorc <gregory.szorc at gmail.com>
# Date 1489364702 25200
#      Sun Mar 12 17:25:02 2017 -0700
# Node ID 6a85d5031daf1ab3a5cb3c6705a46367d5e0de29
# Parent  1c3352d7eaf24533ad52d4b8a024211e9189fb0b
extensions: formalize concept of experimental extensions

Per discussions at the Sprint, we would like to be more
welcoming to shipping experimental extensions with the official
Mercurial distribution so they are more easily available to

A concern with "experimental" extensions is where to put them and
how to differentiate them as "experimental" to end-users.

One idea is to put them in a special location or name them in
such a way that "experimental" (or some other label) is in the
name end-users use to load them. A problem with this is that if
the extension "graduates" to fully-supported status, users have to
update their configs to load the extension at the new name (or
Mercurial maintains a mapping table, which excludes 3rd party
extensions from having this benefit).

This patch formalizes the concept of experimental extensions and
does so in a way that is user-friendly and allows experimental
extensions to gracefully graduate to non-experimental status without
end-user intervention. It does so through 2 mechanisms:

1. Extensions now set the "experimental" attribute to mark themselves
   as experimental
2. The extension loader only loads experimental extensions if the
   config option value of the extension is "!beta"

If a user attempts to load an experimental mechanism using the
normal "extensions.<name>=" syntax, a warning message is printed
saying the extension is "in trial mode" and tells them how to
activate it. If the value is "!beta" (e.g. extensions.foo=!beta),
the extension is loaded. This requires explicit affirmation from
the end-user that an extension is "beta." This should mitigate
any surprises from a user using an extension without realizing it
is experimental.

Because the old extension loading code interpreted a leading "!"
as "do not load," the "!beta" syntax results in old clients not
loading experimental extensions. This is a graceful failure.

A drawback of using "!beta" is that an explicit path to the
extension cannot be specified at this time. This means the
extension's name must correspond to a module in the "hgext" or
"hgext3rd" packages. I think this is acceptable.

I purposefully chose to use "beta" for the end-user facing value.
>From my experience, users are scared of the "experimental" label.
In most cases, "experimental" features in Mercurial are more stable
than the other end of the spectrum. So I wanted to use a label
that is more reflective of reality, isn't scary, and doesn't
require strong English knowledge. I consulted a thesaurus for
suitable synonyms of "experimental" and couldn't find anything
"just right." So, I used "beta." This is a common technical term
that most people relate to (thanks, Gmail!) and it accurately
reflects the state of most extensions that Mercurial will
distribute in "experimental" form. For users who don't know
what it means, the translated message printed without "!beta"
can provide more context.

diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -119,9 +119,9 @@ def _reportimporterror(ui, err, failed, 
              % (failed, _forbytes(err), next))
     if ui.debugflag:
-def load(ui, name, path):
+def load(ui, name, path, allowexperimental=False):
     if name.startswith('hgext.') or name.startswith('hgext/'):
         shortname = name[6:]
         shortname = name
@@ -141,8 +141,15 @@ def load(ui, name, path):
         ui.warn(_('(third party extension %s requires version %s or newer '
                   'of Mercurial; disabling)\n') % (shortname, minver))
+    experimental = getattr(mod, 'experimental', None)
+    if experimental and not allowexperimental:
+        ui.warn(_('(extension %s is in trial mode and requires explicit '
+                  'opt-in by setting extensions.%s=!beta; disabling)\n') %
+                (name, name))
+        return
     _extensions[shortname] = mod
     for fn in _aftercallbacks.get(shortname, []):
@@ -166,14 +173,18 @@ def _runextsetup(name, ui):
 def loadall(ui):
     result = ui.configitems("extensions")
     newindex = len(_order)
     for (name, path) in result:
+        allowexperimental = False
         if path:
-            if path[0:1] == '!':
+            if path.startswith('!beta'):
+                allowexperimental = True
+                path = ''
+            elif path[0:1] == '!':
                 _disabledextensions[name] = path[1:]
-            load(ui, name, path)
+            load(ui, name, path, allowexperimental=allowexperimental)
         except KeyboardInterrupt:
         except Exception as inst:
             inst = _forbytes(inst)
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -1556,4 +1556,17 @@ Test synopsis and docstring extending
   $ hg help bookmarks | grep GREPME
   hg bookmarks [OPTIONS]... [NAME]... GREPME [--foo] [-x]
       GREPME make sure that this is in the help!
+  $ cd ..
+Experimental extensions require explicit opt-in via "!beta" value
+  $ cat > experimental.py << EOF
+  > experimental = True
+  > EOF
+  $ hg init experimental
+  $ hg -R experimental --config extensions.path=./path.py --config extensions.experimental= log
+  (extension experimental is in trial mode and requires explicit opt-in by setting extensions.experimental=!beta; disabling)
+  $ hg -R experimental --config extensions.path=./path.py --config extensions.experimental=!beta log

More information about the Mercurial-devel mailing list