configwizard: prompt to install and configure code review extensions (bug 1277406); r?glob draft
authorGregory Szorc <gps@mozilla.com>
Wed, 01 Jun 2016 15:18:37 -0700
changeset 8502 0f0c05eb2366e55f6d4aa3820f2e02d3aa68a3a2
parent 8501 643710237bfe3b4f65bc0bf5064d214ac8877fa3
child 8503 23f092c0d3174ebc51cf230973669d4006b0d362
push id918
push userbmo:gps@mozilla.com
push dateThu, 09 Jun 2016 19:23:31 +0000
reviewersglob
bugs1277406
configwizard: prompt to install and configure code review extensions (bug 1277406); r?glob This code is more complicated than most things. Good thing we now have tests for it! MozReview-Commit-ID: 2dDHyVCR2uL
hgext/configwizard/__init__.py
hgext/configwizard/hgsetup/config.py
hgext/configwizard/hgsetup/wizard.py
hgext/configwizard/tests/test-codereview.t
--- a/hgext/configwizard/__init__.py
+++ b/hgext/configwizard/__init__.py
@@ -113,30 +113,77 @@ The firefoxtree extension is *strongly* 
 a) aggregate multiple Firefox repositories into a single local repo
 b) perform head/bookmark-based development (as opposed to mq)
 
 (Relevant config option: extensions.firefoxtree)
 
 Would you like to activate firefoxtree (Yn)? $$ &Yes $$ &No
 '''.strip()
 
+CODEREVIEW_INFO = '''
+Commits to Mozilla projects are typically sent to MozReview. This is the
+preferred code review tool at Mozilla.
+
+Some still practice a legacy code review workflow that uploads patches
+to Bugzilla.
+
+1. MozReview only (preferred)
+2. Both MozReview and Bugzilla
+3. Bugzilla only
+
+Which code review tools will you be submitting code to? $$ &1 $$ &2 $$ &3
+'''.strip()
+
+MISSING_BUGZILLA_CREDENTIALS = '''
+You do not have a Bugzilla API Key defined in your Mercurial config.
+
+In order to communicate with Bugzilla and services (like MozReview) that
+use Bugzilla for authentication, you'll need to supply an API Key.
+
+The Bugzilla API Key is optional. However, if you don't supply one,
+certain features may not work and/or you'll be prompted for one.
+
+You should only need to configure a Bugzilla API Key once.
+'''.lstrip()
+
+BUGZILLA_API_KEY_INSTRUCTIONS = '''
+Bugzilla API Keys can only be obtained through the Bugzilla web interface.
+
+Please perform the following steps:
+
+  1) Open https://bugzilla.mozilla.org/userprefs.cgi?tab=apikey
+  2) Generate a new API Key
+  3) Copy the generated key and paste it here
+'''.lstrip()
+
+LEGACY_BUGZILLA_CREDENTIALS_DETECTED = '''
+Your existing Mercurial config uses a legacy method for defining Bugzilla
+credentials. Bugzilla API Keys are the most secure and preferred method
+for defining Bugzilla credentials. Bugzilla API Keys are also required
+if you have enabled 2 Factor Authentication in Bugzilla.
+
+For security reasons, the legacy credentials are being removed from the
+config.
+'''.lstrip()
+
 testedwith = '3.5 3.6 3.7 3.8'
 buglink = 'https://bugzilla.mozilla.org/enter_bug.cgi?product=Developer%20Services&component=General'
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 
 wizardsteps = {
     'hgversion',
     'username',
     'diff',
     'color',
     'historyediting',
     'fsmonitor',
     'firefoxtree',
+    'codereview',
     'configchange',
 }
 
 @command('configwizard', [
     ('s', 'statedir', '', _('directory to store state')),
     ], _('hg configwizard'), optionalrepo=True)
 def configwizard(ui, repo, statedir=None, **opts):
     """Ensure your Mercurial configuration is up to date."""
@@ -173,16 +220,19 @@ def configwizard(ui, repo, statedir=None
         _checkhistoryediting(ui, cw)
 
     if 'fsmonitor' in runsteps:
         _checkfsmonitor(ui, cw, hgversion)
 
     if 'firefoxtree' in runsteps:
         _promptvctextension(ui, cw, 'firefoxtree', FIREFOXTREE_INFO)
 
+    if 'codereview' in runsteps:
+        _checkcodereview(ui, cw)
+
     if 'configchange' in runsteps:
         return _handleconfigchange(ui, cw)
 
     return 0
 
 
 def _checkhgversion(ui, hgversion):
     if hgversion >= OLDEST_NON_LEGACY_VERSION:
@@ -208,20 +258,20 @@ def uiprompt(ui, msg, default=None):
     This prevents entire prompt text from rendering as a special color which
     may be hard to read.
     """
     lines = msg.splitlines(True)
     ui.write(''.join(lines[0:-1]))
     return ui.prompt(lines[-1], default=default)
 
 
-def uipromptchoice(ui, msg):
+def uipromptchoice(ui, msg, default=0):
     lines = msg.splitlines(True)
     ui.write(''.join(lines[0:-1]))
-    return ui.promptchoice(lines[-1])
+    return ui.promptchoice(lines[-1], default=default)
 
 
 def _checkusername(ui, cw):
     if ui.config('ui', 'username'):
         return
 
     ui.write(MISSING_USERNAME)
 
@@ -264,40 +314,52 @@ def _promptnativeextension(ui, cw, ext, 
 
     if not uipromptchoice(ui, '%s (Yn) $$ &Yes $$ &No' % msg):
         if 'extensions' not in cw.c:
             cw.c['extensions'] = {}
 
         cw.c['extensions'][ext] = ''
 
 
+def _vctextpath(ext, path=None):
+    here = os.path.dirname(os.path.abspath(__file__))
+    ext_dir = os.path.normpath(os.path.join(here, '..'))
+    ext_path = os.path.join(ext_dir, ext)
+    if path:
+        ext_path = os.path.join(ext_path, path)
+
+    return ext_path
+
+
+def _enableext(cw, name, value):
+    if 'extensions' not in cw.c:
+        cw.c['extensions'] = {}
+
+    cw.c['extensions'][name] = value
+
+
 def _promptvctextension(ui, cw, ext, msg):
     if ui.hasconfig('extensions', ext):
         return
 
-    here = os.path.dirname(os.path.abspath(__file__))
-    ext_dir = os.path.normpath(os.path.join(here, '..'))
-    ext_path = os.path.join(ext_dir, ext)
+    ext_path = _vctextpath(ext)
 
     # Verify the extension loads before prompting to enable it. This is
     # done out of paranoia.
     result = subprocess.check_output([sys.argv[0],
                                       '--config', 'extensions.testmodule=%s' % ext_path,
                                       '--config', 'ui.traceback=true'],
                                      stderr=subprocess.STDOUT)
     if 'Traceback' in result:
         return
 
     if uipromptchoice(ui, '%s (Yn) $$ &Yes $$ &No' % msg):
         return
 
-    if 'extensions' not in cw.c:
-        cw.c['extensions'] = {}
-
-    cw.c['extensions'][ext] = ext_path
+    _enableext(cw, ext, ext_path)
 
 
 def _checkhistoryediting(ui, cw):
     if all(ui.hasconfig('extensions', e) for e in ('histedit', 'rebase')):
         return
 
     if ui.promptchoice('Enable history rewriting commands (Yn)? $$ &Yes $$ &No'):
         return
@@ -326,16 +388,68 @@ def _checkfsmonitor(ui, cw, hgversion):
 
     # Mercurial 3.8+ has fsmonitor built-in.
     if hgversion >= (3, 8, 0):
         _promptnativeextension(ui, cw, 'fsmonitor', FSMONITOR_INFO)
     else:
         ui.write(FSMONITOR_NOT_AVAILABLE)
 
 
+def _checkcodereview(ui, cw):
+    # We don't check for bzexport if reviewboard is enabled because
+    # bzexport is legacy.
+    if ui.hasconfig('extensions', 'reviewboard'):
+        return
+
+    if ui.promptchoice('Will you be submitting commits to Mozilla (Yn)? $$ &Yes $$ &No'):
+        return
+
+    confrb = False
+    answer = uipromptchoice(ui, CODEREVIEW_INFO, default=0) + 1
+    if answer in (1, 2):
+        _enableext(cw, 'reviewboard', _vctextpath('reviewboard', 'client.py'))
+        confrb = True
+
+    if answer in (2, 3):
+        _enableext(cw, 'bzexport', _vctextpath('bzexport'))
+
+    # Now verify Bugzilla credentials and other config foo is set.
+    bzuser = ui.config('bugzilla', 'username')
+    bzapikey = ui.config('bugzilla', 'apikey')
+
+    if not bzuser or not bzapikey:
+        ui.write(MISSING_BUGZILLA_CREDENTIALS)
+
+    if not bzuser:
+        bzuser = ui.prompt('What is your Bugzilla email address? (optional)', default='')
+
+    if bzuser and not bzapikey:
+        ui.write(BUGZILLA_API_KEY_INSTRUCTIONS)
+        bzapikey = ui.prompt('Please enter a Bugzilla API Key: (optional)', default='')
+
+    if bzuser or bzapikey:
+        if 'bugzilla' not in cw.c:
+            cw.c['bugzilla'] = {}
+
+    if bzuser:
+        cw.c['bugzilla']['username'] = bzuser
+    if bzapikey:
+        cw.c['bugzilla']['apikey'] = bzapikey
+
+    if any(ui.hasconfig('bugzilla', c) for c in ('password', 'userid', 'cookie')):
+        ui.write(LEGACY_BUGZILLA_CREDENTIALS_DETECTED)
+
+    for c in ('password', 'userid', 'cookie'):
+        try:
+            del cw.c['bugzilla'][c]
+        except KeyError:
+            pass
+
+    # TODO configure mozilla.ircnick and the "review" path
+
 def _handleconfigchange(ui, cw):
     # Obtain the old and new content so we can show a diff.
     newbuf = io.BytesIO()
     cw.write(newbuf)
     newbuf.seek(0)
     newlines = [l.rstrip() for l in newbuf.readlines()]
     oldlines = []
     if os.path.exists(cw.path):
--- a/hgext/configwizard/hgsetup/config.py
+++ b/hgext/configwizard/hgsetup/config.py
@@ -40,45 +40,16 @@ class MercurialConfig(object):
         if not path:
             path = ''
 
         if 'extensions' not in self._c:
             self._c['extensions'] = {}
 
         self._c['extensions'][name] = path
 
-    def get_bugzilla_credentials(self):
-        if 'bugzilla' not in self._c:
-            return None, None, None, None, None
-
-        b = self._c['bugzilla']
-        return (
-            b.get('username', None),
-            b.get('password', None),
-            b.get('userid', None),
-            b.get('cookie', None),
-            b.get('apikey', None),
-        )
-
-    def set_bugzilla_credentials(self, username, api_key):
-        b = self._c.setdefault('bugzilla', {})
-        if username:
-            b['username'] = username
-        if api_key:
-            b['apikey'] = api_key
-
-    def clear_legacy_bugzilla_credentials(self):
-        if 'bugzilla' not in self._c:
-            return
-
-        b = self._c['bugzilla']
-        for k in ('password', 'userid', 'cookie'):
-            if k in b:
-                del b[k]
-
     def have_wip(self):
         return 'wip' in self._c.get('alias', {})
 
     def install_wip_alias(self):
         """hg wip shows a concise view of work in progress."""
         alias = self._c.setdefault('alias', {})
         alias['wip'] = 'log --graph --rev=wip --template=wip'
 
--- a/hgext/configwizard/hgsetup/wizard.py
+++ b/hgext/configwizard/hgsetup/wizard.py
@@ -22,75 +22,21 @@ from mozversioncontrol import get_hg_pat
 
 from .update import MercurialUpdater
 from .config import (
     config_file,
     MercurialConfig,
     ParseException,
 )
 
-
-BZEXPORT_INFO = '''
-If you plan on uploading patches to Mozilla, there is an extension called
-bzexport that makes it easy to upload patches from the command line via the
-|hg bzexport| command. More info is available at
-https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/bzexport/README
-
-(Relevant config option: extensions.bzexport)
-
-Would you like to activate bzexport
-'''.strip()
-
 FINISHED = '''
 Your Mercurial should now be properly configured and recommended extensions
 should be up to date!
 '''.strip()
 
-REVIEWBOARD_MINIMUM_VERSION = LooseVersion('3.5')
-
-REVIEWBOARD_INCOMPATIBLE = '''
-Your Mercurial is too old to use the reviewboard extension, which is necessary
-to conduct code review.
-
-Please upgrade to Mercurial %s or newer to use this extension.
-'''.strip()
-
-MISSING_BUGZILLA_CREDENTIALS = '''
-You do not have your Bugzilla API Key defined in your Mercurial config.
-
-Various extensions make use of a Bugzilla API Key to interface with
-Bugzilla to enrich your development experience.
-
-The Bugzilla API Key is optional. If you do not provide one, associated
-functionality will not be enabled, we will attempt to find a Bugzilla cookie
-from a Firefox profile, or you will be prompted for your Bugzilla credentials
-when they are needed.
-
-You should only need to configure a Bugzilla API Key once.
-'''.lstrip()
-
-BUGZILLA_API_KEY_INSTRUCTIONS = '''
-Bugzilla API Keys can only be obtained through the Bugzilla web interface.
-
-Please perform the following steps:
-
-  1) Open https://bugzilla.mozilla.org/userprefs.cgi?tab=apikey
-  2) Generate a new API Key
-  3) Copy the generated key and paste it here
-'''.lstrip()
-
-LEGACY_BUGZILLA_CREDENTIALS_DETECTED = '''
-Your existing Mercurial config uses a legacy method for defining Bugzilla
-credentials. Bugzilla API Keys are the most secure and preferred method
-for defining Bugzilla credentials. Bugzilla API Keys are also required
-if you have enabled 2 Factor Authentication in Bugzilla.
-
-All consumers formerly looking at these options should support API Keys.
-'''.lstrip()
-
 PUSHTOTRY_MINIMUM_VERSION = LooseVersion('3.5')
 
 PUSHTOTRY_INFO = '''
 The push-to-try extension generates a temporary commit with a given
 try syntax and pushes it to the try server. The extension is intended
 to be used in concert with other tools generating try syntax so that
 they can push to try without depending on mq or other workarounds.
 
@@ -165,66 +111,23 @@ class MercurialSetupWizard(object):
 
         hg = get_hg_path()
         config_path = config_file(config_paths)
 
         self.updater.update_all()
 
         hg_version = get_hg_version(hg)
 
-        if 'reviewboard' not in c.extensions:
-            if hg_version < REVIEWBOARD_MINIMUM_VERSION:
-                print(REVIEWBOARD_INCOMPATIBLE % REVIEWBOARD_MINIMUM_VERSION)
-            else:
-                p = os.path.join(self.vcs_tools_dir, 'hgext', 'reviewboard',
-                    'client.py')
-                self.prompt_external_extension(c, 'reviewboard',
-                    'Would you like to enable the reviewboard extension so '
-                    'you can easily initiate code reviews against Mozilla '
-                    'projects',
-                    path=p)
-
-        self.prompt_external_extension(c, 'bzexport', BZEXPORT_INFO)
-
         if hg_version >= PUSHTOTRY_MINIMUM_VERSION:
             self.prompt_external_extension(c, 'push-to-try', PUSHTOTRY_INFO)
 
         if not c.have_wip():
             if self._prompt_yn(WIP_INFO):
                 c.install_wip_alias()
 
-        if 'reviewboard' in c.extensions:
-            bzuser, bzpass, bzuserid, bzcookie, bzapikey = c.get_bugzilla_credentials()
-
-            if not bzuser or not bzapikey:
-                print(MISSING_BUGZILLA_CREDENTIALS)
-
-            if not bzuser:
-                bzuser = self._prompt('What is your Bugzilla email address? (optional)',
-                    allow_empty=True)
-
-            if bzuser and not bzapikey:
-                print(BUGZILLA_API_KEY_INSTRUCTIONS)
-                bzapikey = self._prompt('Please enter a Bugzilla API Key: (optional)',
-                    allow_empty=True)
-
-            if bzuser or bzapikey:
-                c.set_bugzilla_credentials(bzuser, bzapikey)
-
-            if bzpass or bzuserid or bzcookie:
-                print(LEGACY_BUGZILLA_CREDENTIALS_DETECTED)
-
-                # Clear legacy credentials automatically if an API Key is
-                # found as it supercedes all other credentials.
-                if bzapikey:
-                    print('The legacy credentials have been removed.\n')
-                    c.clear_legacy_bugzilla_credentials()
-                elif self._prompt_yn('Remove legacy credentials'):
-                    c.clear_legacy_bugzilla_credentials()
-
         # Look for and clean up old extensions.
         for ext in {'bzexport', 'qimportbz', 'mqext'}:
             path = os.path.join(self.ext_dir, ext)
             if os.path.exists(path):
                 if self._prompt_yn('Would you like to remove the old and no '
                     'longer referenced repository at %s' % path):
                     print('Cleaning up old repository: %s' % path)
                     shutil.rmtree(path)
new file mode 100644
--- /dev/null
+++ b/hgext/configwizard/tests/test-codereview.t
@@ -0,0 +1,218 @@
+  $ . $TESTDIR/hgext/configwizard/tests/helpers.sh
+
+Saying no to code review doesn't go through that part of wizard
+
+  $ hg --config ui.interactive=true --config configwizard.steps=codereview,configchange configwizard << EOF
+  > 
+  > n
+  > EOF
+  This wizard will guide you through configuring Mercurial for an optimal
+  experience contributing to Mozilla projects.
+  
+  The wizard makes no changes without your permission.
+  
+  To begin, press the enter/return key.
+   <RETURN>
+  Will you be submitting commits to Mozilla (Yn)?  n
+
+No prompt if extensions already enabled
+
+  $ hg --config configwizard.steps=codereview --config extensions.reviewboard=$TESTDIR/hgext/reviewboard/client.py configwizard
+  This wizard will guide you through configuring Mercurial for an optimal
+  experience contributing to Mozilla projects.
+  
+  The wizard makes no changes without your permission.
+  
+  To begin, press the enter/return key.
+   <RETURN>
+
+reviewboard is enabled when requested
+
+  $ hg --config configwizard.steps=codereview,configchange configwizard
+  This wizard will guide you through configuring Mercurial for an optimal
+  experience contributing to Mozilla projects.
+  
+  The wizard makes no changes without your permission.
+  
+  To begin, press the enter/return key.
+   <RETURN>
+  Will you be submitting commits to Mozilla (Yn)?  y
+  Commits to Mozilla projects are typically sent to MozReview. This is the
+  preferred code review tool at Mozilla.
+  
+  Some still practice a legacy code review workflow that uploads patches
+  to Bugzilla.
+  
+  1. MozReview only (preferred)
+  2. Both MozReview and Bugzilla
+  3. Bugzilla only
+  
+  Which code review tools will you be submitting code to?  1
+  You do not have a Bugzilla API Key defined in your Mercurial config.
+  
+  In order to communicate with Bugzilla and services (like MozReview) that
+  use Bugzilla for authentication, you'll need to supply an API Key.
+  
+  The Bugzilla API Key is optional. However, if you don't supply one,
+  certain features may not work and/or you'll be prompted for one.
+  
+  You should only need to configure a Bugzilla API Key once.
+  What is your Bugzilla email address? (optional) 
+  Your config file needs updating.
+  Would you like to see a diff of the changes first (Yn)?  y
+  --- hgrc.old
+  +++ hgrc.new
+  @@ -0,0 +1,2 @@
+  +[extensions]
+  +reviewboard = */hgext/reviewboard/client.py (glob)
+  
+  Write changes to hgrc file (Yn)?  y
+
+  $ cat .hgrc
+  [extensions]
+  reviewboard = */hgext/reviewboard/client.py (glob)
+
+only bzexport can be enabled when requested
+
+  $ hg --config ui.interactive=true --config configwizard.steps=codereview,configchange configwizard << EOF
+  > 
+  > y
+  > 3
+  > someone@example.com
+  > apikey
+  > y
+  > y
+  > EOF
+  This wizard will guide you through configuring Mercurial for an optimal
+  experience contributing to Mozilla projects.
+  
+  The wizard makes no changes without your permission.
+  
+  To begin, press the enter/return key.
+   <RETURN>
+  Will you be submitting commits to Mozilla (Yn)?  y
+  Commits to Mozilla projects are typically sent to MozReview. This is the
+  preferred code review tool at Mozilla.
+  
+  Some still practice a legacy code review workflow that uploads patches
+  to Bugzilla.
+  
+  1. MozReview only (preferred)
+  2. Both MozReview and Bugzilla
+  3. Bugzilla only
+  
+  Which code review tools will you be submitting code to?  3
+  You do not have a Bugzilla API Key defined in your Mercurial config.
+  
+  In order to communicate with Bugzilla and services (like MozReview) that
+  use Bugzilla for authentication, you'll need to supply an API Key.
+  
+  The Bugzilla API Key is optional. However, if you don't supply one,
+  certain features may not work and/or you'll be prompted for one.
+  
+  You should only need to configure a Bugzilla API Key once.
+  What is your Bugzilla email address? (optional) someone@example.com
+  Bugzilla API Keys can only be obtained through the Bugzilla web interface.
+  
+  Please perform the following steps:
+  
+    1) Open https://bugzilla.mozilla.org/userprefs.cgi?tab=apikey
+    2) Generate a new API Key
+    3) Copy the generated key and paste it here
+  Please enter a Bugzilla API Key: (optional) apikey
+  Your config file needs updating.
+  Would you like to see a diff of the changes first (Yn)?  y
+  --- hgrc.old
+  +++ hgrc.new
+  @@ -1,2 +1,6 @@
+   [extensions]
+   reviewboard = */hgext/reviewboard/client.py (glob)
+  +bzexport = */hgext/bzexport (glob)
+  +[bugzilla]
+  +username = someone@example.com
+  +apikey = apikey
+  
+  Write changes to hgrc file (Yn)?  y
+
+  $ cat .hgrc
+  [extensions]
+  reviewboard = */hgext/reviewboard/client.py (glob)
+  bzexport = */hgext/bzexport (glob)
+  [bugzilla]
+  username = someone@example.com
+  apikey = apikey
+
+Legacy credentials are removed from config file
+
+  $ cat >> .hgrc << EOF
+  > cookie = bzcookie
+  > EOF
+
+  $ hg --config ui.interactive=true --config bugzilla.cookie=cookie --config configwizard.steps=codereview,configchange configwizard << EOF
+  > 
+  > y
+  > 3
+  > someone2@example.com
+  > apikey2
+  > y
+  > y
+  > EOF
+  This wizard will guide you through configuring Mercurial for an optimal
+  experience contributing to Mozilla projects.
+  
+  The wizard makes no changes without your permission.
+  
+  To begin, press the enter/return key.
+   <RETURN>
+  Will you be submitting commits to Mozilla (Yn)?  y
+  Commits to Mozilla projects are typically sent to MozReview. This is the
+  preferred code review tool at Mozilla.
+  
+  Some still practice a legacy code review workflow that uploads patches
+  to Bugzilla.
+  
+  1. MozReview only (preferred)
+  2. Both MozReview and Bugzilla
+  3. Bugzilla only
+  
+  Which code review tools will you be submitting code to?  3
+  You do not have a Bugzilla API Key defined in your Mercurial config.
+  
+  In order to communicate with Bugzilla and services (like MozReview) that
+  use Bugzilla for authentication, you'll need to supply an API Key.
+  
+  The Bugzilla API Key is optional. However, if you don't supply one,
+  certain features may not work and/or you'll be prompted for one.
+  
+  You should only need to configure a Bugzilla API Key once.
+  What is your Bugzilla email address? (optional) someone2@example.com
+  Bugzilla API Keys can only be obtained through the Bugzilla web interface.
+  
+  Please perform the following steps:
+  
+    1) Open https://bugzilla.mozilla.org/userprefs.cgi?tab=apikey
+    2) Generate a new API Key
+    3) Copy the generated key and paste it here
+  Please enter a Bugzilla API Key: (optional) apikey2
+  Your existing Mercurial config uses a legacy method for defining Bugzilla
+  credentials. Bugzilla API Keys are the most secure and preferred method
+  for defining Bugzilla credentials. Bugzilla API Keys are also required
+  if you have enabled 2 Factor Authentication in Bugzilla.
+  
+  For security reasons, the legacy credentials are being removed from the
+  config.
+  Your config file needs updating.
+  Would you like to see a diff of the changes first (Yn)?  y
+  --- hgrc.old
+  +++ hgrc.new
+  @@ -2,6 +2,5 @@
+   reviewboard = */hgext/reviewboard/client.py (glob)
+   bzexport = */hgext/bzexport (glob)
+   [bugzilla]
+  -username = someone@example.com
+  -apikey = apikey
+  -cookie = bzcookie
+  +username = someone2@example.com
+  +apikey = apikey2
+  
+  Write changes to hgrc file (Yn)?  y