[PATCH] extensions: formalize concept of experimental extensions

Gregory Szorc gregory.szorc at gmail.com
Sun Mar 12 22:48:35 EDT 2017



> On Mar 12, 2017, at 19:28, Jun Wu <quark at fb.com> wrote:
> 
> If I read it correctly, this means an "experimental" extension will end up
> with:
> 
>             | ext=        | ext=!beta
>  ----------------------------------------
>  old client | load        | do not load
>  new client | do not load | load
> 
> I think that's confusing.
> 
> I think we can have an "[extensions:experimental]" section instead, which
> will only be processed by new mercurial.

For extensions distributed with Mercurial, old clients won't have the extension. So the old client plus ext= scenario results in no load.

Of course, you probably didn't have ext= in the first place because it would emit an annoying warning on every command. So I doubt ext= for experimental extensions will occur much in the wild.

> 
> Excerpts from Gregory Szorc's message of 2017-03-12 17:41:49 -0700:
>> # 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
>> end-users.
>> 
>> 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:
>>         ui.traceback()
>> 
>> -def load(ui, name, path):
>> +def load(ui, name, path, allowexperimental=False):
>>     if name.startswith('hgext.') or name.startswith('hgext/'):
>>         shortname = name[6:]
>>     else:
>>         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))
>>         return
>> 
>> +    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
>>     _order.append(shortname)
>>     for fn in _aftercallbacks.get(shortname, []):
>>         fn(loaded=True)
>> @@ -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:]
>>                 continue
>>         try:
>> -            load(ui, name, path)
>> +            load(ui, name, path, allowexperimental=allowexperimental)
>>         except KeyboardInterrupt:
>>             raise
>>         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