Patchwork commands: add 'setup' command to allow automatic hgrc configuration

login
register
mail settings
Submitter Mathias De Maré
Date March 12, 2015, 5:07 p.m.
Message ID <0de0fc66a20a5f0658a6.1426180045@waste.org>
Download mbox | patch
Permalink /patch/8026/
State Superseded
Headers show

Comments

Mathias De Maré - March 12, 2015, 5:07 p.m.
# HG changeset patch
# User Mathias De Maré <mathias.demare@gmail.com>
# Date 1426162870 -3600
#      Thu Mar 12 13:21:10 2015 +0100
# Node ID 0de0fc66a20a5f0658a6c6f1f5de20d4705f3de1
# Parent  7cf9a9e0cf893e7ae82dc576a03c843fd6640438
commands: add 'setup' command to allow automatic hgrc configuration

This is a proposal meant to easily set up new users with
a good hgrc file. It's very basic (a combination of
'release early' and 'I don't have a lot of time').
Comments are very much welcome.

I wasn't too sure about using the first path
listed in the userhgrc, not sure if that's the best approach.

Some other ideas to throw out there:
- Running 'setup' by default if a user does not have a .hgrc
      and is using the terminal (possibly falls under moot?)
- Perhaps having a global 'do you want to have a lot of sensible defaults set'
  question could be useful as well.
- Popping up a merge window in case a user already has a .hgrc.
  This could allow the user to do the merge,
  and avoids us having to mess with editing configs
  (this was a very good idea by smf).
Mathias De Maré - March 12, 2015, 5:15 p.m.
On Thu, Mar 12, 2015 at 6:07 PM, Mathias De Maré <mathias.demare@gmail.com>
wrote:

> # HG changeset patch
> # User Mathias De Maré <mathias.demare@gmail.com>
> # Date 1426162870 -3600
> #      Thu Mar 12 13:21:10 2015 +0100
> # Node ID 0de0fc66a20a5f0658a6c6f1f5de20d4705f3de1
> # Parent  7cf9a9e0cf893e7ae82dc576a03c843fd6640438
> commands: add 'setup' command to allow automatic hgrc configuration
>

Please ignore this patch, I pushed it too early, I'll be sending a new
version (with RFC flag and some changes).

>
> This is a proposal meant to easily set up new users with
> a good hgrc file. It's very basic (a combination of
> 'release early' and 'I don't have a lot of time').
> Comments are very much welcome.
>
> I wasn't too sure about using the first path
> listed in the userhgrc, not sure if that's the best approach.
>
> Some other ideas to throw out there:
> - Running 'setup' by default if a user does not have a .hgrc
>       and is using the terminal (possibly falls under moot?)
> - Perhaps having a global 'do you want to have a lot of sensible defaults
> set'
>   question could be useful as well.
> - Popping up a merge window in case a user already has a .hgrc.
>   This could allow the user to do the merge,
>   and avoids us having to mess with editing configs
>   (this was a very good idea by smf).
>
> diff --git a/mercurial/commands.py b/mercurial/commands.py
> --- a/mercurial/commands.py
> +++ b/mercurial/commands.py
> @@ -19,10 +19,12 @@
>  import merge as mergemod
>  import minirst, revset, fileset
>  import dagparser, context, simplemerge, graphmod, copies
> +import query
>  import random
>  import setdiscovery, treediscovery, dagutil, pvec, localrepo
>  import phases, obsolete, exchange, bundle2
>  import ui as uimod
> +import config as configmod
>
>  table = {}
>
> @@ -5615,6 +5617,50 @@
>          self.httpd.serve_forever()
>
>
> +@command('setup', [])
> +def setup(ui, repo):
> +    """run a setup to generate a Mercurial configuration
> +
> +    Answer a few questions to generate a basic Mercurial configuration.
> +
> +    Returns 0 on success.
> +    """
> +
> +    paths = scmutil.userrcpath()
> +    if os.path.isfile(paths[0]):
> +        if not query.binaryquery(repo,
> +                _('WARNING: you already have a ' \
> +                'Mercurial configuration file!\n' \
> +                'Are you sure you want to overwrite it ' \
> +                'with a new configuration?'),
> +            False):
> +            return
> +
> +    userconfig = configmod.config()
> +
> +    if query.binaryquery(repo,
> +            _('Would you like to configure your username? '), True):
> +        name = query.textquery(repo, _('Please enter your name: '))
> +        email = query.textquery(repo, _('Please enter your email adress:
> '))
> +        userconfig.set('ui', 'username', '%s <%s>' % (name, email))
> +    if query.binaryquery(repo, _('Would you like to enable ' \
> +            'some useful extensions by default, ' \
> +            'enhancing your experience?\n' \
> +            'The extensions to be enabled are: ' \
> +            'color, pager, progress'), True):
> +        userconfig.set('extensions', 'color', '')
> +        userconfig.set('extensions', 'pager', '')
> +        userconfig.set('pager', 'pager', 'less -FRX')
> +        userconfig.set('extensions', 'progress', '')
> +    if query.binaryquery(repo,
> +            'Would you like to use an extended diff format? ' \
> +            'If you are viewing changes, ' \
> +            'this allows you to easily see added/removed files.',
> +            True):
> +        userconfig.set('diff', 'git', True)
> +    userconfig.write(paths[0])
> +
> +
>  @command('^status|st',
>      [('A', 'all', None, _('show status of all files')),
>      ('m', 'modified', None, _('show only modified files')),
> diff --git a/mercurial/config.py b/mercurial/config.py
> --- a/mercurial/config.py
> +++ b/mercurial/config.py
> @@ -157,3 +157,13 @@
>          if not fp:
>              fp = util.posixfile(path)
>          self.parse(path, fp.read(), sections, remap, self.read)
> +
> +    def write(self, path, fp=None):
> +        if not fp:
> +            fp = util.posixfile(path, 'w')
> +        for section in self:
> +            d = self[section]
> +            fp.write('[%s]\n' % section)
> +            for key in d:
> +                fp.write('%s = %s\n' % (key, d[key]))
> +            fp.write('\n')
> diff --git a/mercurial/query.py b/mercurial/query.py
> new file mode 100644
> --- /dev/null
> +++ b/mercurial/query.py
> @@ -0,0 +1,42 @@
> +# query.py - user querying for mercurial
> +#
> +# Copyright 2015 Mathias De Maré <mathias.demare@gmail.com>
> +#
> +# This software may be used and distributed according to the terms of the
> +# GNU General Public License version 2 or any later version.
> +
> +def _generateresponsestr(responses, default):
> +    defaultidx = responses.index(default)
> +    if defaultidx >= 0 and len(responses[defaultidx]) > 0:
> +        responses[defaultidx] = responses[defaultidx].title()
> +    return '(%s)' % '/'.join(responses)
> +
> +def textquery(repo, question, emptyallowed=False):
> +    """Query the user using an open question"""
> +    repo.ui.write('%s\n' % question)
> +    while True:
> +        res = raw_input()
> +        if emptyallowed or len(res):
> +            return res
> +
> +def optionquery(repo, question, responses, default):
> +    """Query the user using question and a given set of parameters.
> +    Once a valid response is given, the query returns
> +    the match from the list of responses."""
> +    repo.ui.write('%s %s\n' % (question, _generateresponsestr(responses,
> default)))
> +    while True:
> +        res = raw_input()
> +        if not len(res) and default:
> +            return default
> +        for response in responses:
> +            if response.startswith(res):
> +                return response
> +
> +def binaryquery(repo, question, default=True):
> +    """Query the user using question and 'yes/no'"""
> +    if default:
> +        default = 'yes'
> +    else:
> +        default = 'no'
> +    res = optionquery(repo, question, ['yes', 'no'], default)
> +    return res == 'yes'
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
>
Gregory Szorc - March 15, 2015, 3:54 a.m.
On Thu, Mar 12, 2015 at 10:07 AM, Mathias De Maré <mathias.demare@gmail.com>
wrote:

> # HG changeset patch
> # User Mathias De Maré <mathias.demare@gmail.com>
> # Date 1426162870 -3600
> #      Thu Mar 12 13:21:10 2015 +0100
> # Node ID 0de0fc66a20a5f0658a6c6f1f5de20d4705f3de1
> # Parent  7cf9a9e0cf893e7ae82dc576a03c843fd6640438
> commands: add 'setup' command to allow automatic hgrc configuration
>
> This is a proposal meant to easily set up new users with
> a good hgrc file. It's very basic (a combination of
> 'release early' and 'I don't have a lot of time').
> Comments are very much welcome.
>
> I wasn't too sure about using the first path
> listed in the userhgrc, not sure if that's the best approach.
>
> Some other ideas to throw out there:
> - Running 'setup' by default if a user does not have a .hgrc
>       and is using the terminal (possibly falls under moot?)
> - Perhaps having a global 'do you want to have a lot of sensible defaults
> set'
>   question could be useful as well.
> - Popping up a merge window in case a user already has a .hgrc.
>   This could allow the user to do the merge,
>   and avoids us having to mess with editing configs
>   (this was a very good idea by smf).
>

At Mozilla, I wrote a "mercurial-setup" command that essentially runs an
interactive wizard where people can select what options to enable and it
writes an updated hgrc at the end (`mach mercurial-setup` for people with a
mozilla-central checkout around). It also does stuff like ensure Mercurial
extensions are up-to-date from their upstream repos and discourages people
from using MQ. Unlike some corporate Mercurial users, we don't have the
luxury of centralized machine configuration and we can't just "push out" an
optimized/ideal config/environment to our developers automatically.

I've received considerable feedback that this tool is useful. As easy as
hgrc files are to many on this list, many people don't want to be bothered
by them. They want things to "just work."

I also believe many people try Mercurial, see the defaults are sub-par on
numerous fronts, and then give up. I think "hg setup" could be part of the
all-important first use user experience.

If this lands, I'd love to see a hook point so extensions could add their
own prompts. This would play into my idea for servers to advertise
recommended settings and "best practices" for interacting with it. I have a
very old proof-of-concept at
https://bugzilla.mozilla.org/show_bug.cgi?id=941856. I'd love to find time
to polish that off and somehow integrate it into Mercurial core...
Matt Mackall - March 15, 2015, 9:19 p.m.
> If this lands, I'd love to see a hook point so extensions could add their
> own prompts.

Can't work: classic chicken-and-egg problem.
Gregory Szorc - March 16, 2015, 5:21 p.m.
On Sun, Mar 15, 2015 at 2:19 PM, Matt Mackall <mpm@selenic.com> wrote:

>
> > If this lands, I'd love to see a hook point so extensions could add their
> > own prompts.
>
> Can't work: classic chicken-and-egg problem.
>

I assumed "setup" was something people could run periodically to ensure
their config is up-to-date. I think I'm thinking ahead :)

Patch

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -19,10 +19,12 @@ 
 import merge as mergemod
 import minirst, revset, fileset
 import dagparser, context, simplemerge, graphmod, copies
+import query
 import random
 import setdiscovery, treediscovery, dagutil, pvec, localrepo
 import phases, obsolete, exchange, bundle2
 import ui as uimod
+import config as configmod
 
 table = {}
 
@@ -5615,6 +5617,50 @@ 
         self.httpd.serve_forever()
 
 
+@command('setup', [])
+def setup(ui, repo):
+    """run a setup to generate a Mercurial configuration
+
+    Answer a few questions to generate a basic Mercurial configuration.
+
+    Returns 0 on success.
+    """
+
+    paths = scmutil.userrcpath()
+    if os.path.isfile(paths[0]):
+        if not query.binaryquery(repo,
+                _('WARNING: you already have a ' \
+                'Mercurial configuration file!\n' \
+                'Are you sure you want to overwrite it ' \
+                'with a new configuration?'),
+            False):
+            return
+
+    userconfig = configmod.config()
+
+    if query.binaryquery(repo,
+            _('Would you like to configure your username? '), True):
+        name = query.textquery(repo, _('Please enter your name: '))
+        email = query.textquery(repo, _('Please enter your email adress: '))
+        userconfig.set('ui', 'username', '%s <%s>' % (name, email))
+    if query.binaryquery(repo, _('Would you like to enable ' \
+            'some useful extensions by default, ' \
+            'enhancing your experience?\n' \
+            'The extensions to be enabled are: ' \
+            'color, pager, progress'), True):
+        userconfig.set('extensions', 'color', '')
+        userconfig.set('extensions', 'pager', '')
+        userconfig.set('pager', 'pager', 'less -FRX')
+        userconfig.set('extensions', 'progress', '')
+    if query.binaryquery(repo,
+            'Would you like to use an extended diff format? ' \
+            'If you are viewing changes, ' \
+            'this allows you to easily see added/removed files.',
+            True):
+        userconfig.set('diff', 'git', True)
+    userconfig.write(paths[0])
+
+
 @command('^status|st',
     [('A', 'all', None, _('show status of all files')),
     ('m', 'modified', None, _('show only modified files')),
diff --git a/mercurial/config.py b/mercurial/config.py
--- a/mercurial/config.py
+++ b/mercurial/config.py
@@ -157,3 +157,13 @@ 
         if not fp:
             fp = util.posixfile(path)
         self.parse(path, fp.read(), sections, remap, self.read)
+
+    def write(self, path, fp=None):
+        if not fp:
+            fp = util.posixfile(path, 'w')
+        for section in self:
+            d = self[section]
+            fp.write('[%s]\n' % section)
+            for key in d:
+                fp.write('%s = %s\n' % (key, d[key]))
+            fp.write('\n')
diff --git a/mercurial/query.py b/mercurial/query.py
new file mode 100644
--- /dev/null
+++ b/mercurial/query.py
@@ -0,0 +1,42 @@ 
+# query.py - user querying for mercurial
+#
+# Copyright 2015 Mathias De Maré <mathias.demare@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+def _generateresponsestr(responses, default):
+    defaultidx = responses.index(default)
+    if defaultidx >= 0 and len(responses[defaultidx]) > 0:
+        responses[defaultidx] = responses[defaultidx].title()
+    return '(%s)' % '/'.join(responses)
+
+def textquery(repo, question, emptyallowed=False):
+    """Query the user using an open question"""
+    repo.ui.write('%s\n' % question)
+    while True:
+        res = raw_input()
+        if emptyallowed or len(res):
+            return res
+
+def optionquery(repo, question, responses, default):
+    """Query the user using question and a given set of parameters.
+    Once a valid response is given, the query returns
+    the match from the list of responses."""
+    repo.ui.write('%s %s\n' % (question, _generateresponsestr(responses, default)))
+    while True:
+        res = raw_input()
+        if not len(res) and default:
+            return default
+        for response in responses:
+            if response.startswith(res):
+                return response
+
+def binaryquery(repo, question, default=True):
+    """Query the user using question and 'yes/no'"""
+    if default:
+        default = 'yes'
+    else:
+        default = 'no'
+    res = optionquery(repo, question, ['yes', 'no'], default)
+    return res == 'yes'