tools/mercurial/hgsetup/wizard.py

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 # This Source Code Form is subject to the terms of the Mozilla Public
     2 # License, v. 2.0. If a copy of the MPL was not distributed with this,
     3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     5 from __future__ import unicode_literals
     7 import difflib
     8 import errno
     9 import os
    10 import shutil
    11 import sys
    12 import which
    14 from configobj import ConfigObjError
    15 from StringIO import StringIO
    17 from mozversioncontrol.repoupdate import (
    18     update_mercurial_repo,
    19     update_git_repo,
    20 )
    22 from .config import (
    23     HOST_FINGERPRINTS,
    24     MercurialConfig,
    25 )
    28 INITIAL_MESSAGE = '''
    29 I'm going to help you ensure your Mercurial is configured for optimal
    30 development on Mozilla projects.
    32 If your environment is missing some recommended settings, I'm going to prompt
    33 you whether you want me to make changes: I won't change anything you might not
    34 want me changing without your permission!
    36 If your config is up-to-date, I'm just going to ensure all 3rd party extensions
    37 are up to date and you won't have to do anything.
    39 To begin, press the enter/return key.
    40 '''.strip()
    42 MISSING_USERNAME = '''
    43 You don't have a username defined in your Mercurial config file. In order to
    44 send patches to Mozilla, you'll need to attach a name and email address. If you
    45 aren't comfortable giving us your full name, pseudonames are acceptable.
    46 '''.strip()
    48 BAD_DIFF_SETTINGS = '''
    49 Mozilla developers produce patches in a standard format, but your Mercurial is
    50 not configured to produce patches in that format.
    51 '''.strip()
    53 BZEXPORT_INFO = '''
    54 If you plan on uploading patches to Mozilla, there is an extension called
    55 bzexport that makes it easy to upload patches from the command line via the
    56 |hg bzexport| command. More info is available at
    57 https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/bzexport/README
    59 Would you like to activate bzexport
    60 '''.strip()
    62 MQEXT_INFO = '''
    63 The mqext extension (https://bitbucket.org/sfink/mqext) provides a number of
    64 useful abilities to Mercurial, including automatically committing changes to
    65 your mq patch queue.
    67 Would you like to activate mqext
    68 '''.strip()
    70 QIMPORTBZ_INFO = '''
    71 The qimportbz extension
    72 (https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/qimportbz/README) makes it possible to
    73 import patches from Bugzilla using a friendly bz:// URL handler. e.g.
    74 |hg qimport bz://123456|.
    76 Would you like to activate qimportbz
    77 '''.strip()
    79 QNEWCURRENTUSER_INFO = '''
    80 The mercurial queues command |hg qnew|, which creates new patches in your patch
    81 queue does not set patch author information by default. Author information
    82 should be included when uploading for review.
    83 '''.strip()
    85 FINISHED = '''
    86 Your Mercurial should now be properly configured and recommended extensions
    87 should be up to date!
    88 '''.strip()
    91 class MercurialSetupWizard(object):
    92     """Command-line wizard to help users configure Mercurial."""
    94     def __init__(self, state_dir):
    95         # We use normpath since Mercurial expects the hgrc to use native path
    96         # separators, but state_dir uses unix style paths even on Windows.
    97         self.state_dir = os.path.normpath(state_dir)
    98         self.ext_dir = os.path.join(self.state_dir, 'mercurial', 'extensions')
    99         self.vcs_tools_dir = os.path.join(self.state_dir, 'version-control-tools')
   100         self.update_vcs_tools = False
   102     def run(self, config_paths):
   103         try:
   104             os.makedirs(self.ext_dir)
   105         except OSError as e:
   106             if e.errno != errno.EEXIST:
   107                 raise
   109         try:
   110             hg = which.which('hg')
   111         except which.WhichError as e:
   112             print(e)
   113             print('Try running |mach bootstrap| to ensure your environment is '
   114                 'up to date.')
   115             return 1
   117         try:
   118             c = MercurialConfig(config_paths)
   119         except ConfigObjError as e:
   120             print('Error importing existing Mercurial config!\n'
   121                   '%s\n'
   122                   'If using quotes, they must wrap the entire string.' % e)
   123             return 1
   125         print(INITIAL_MESSAGE)
   126         raw_input()
   128         if not c.have_valid_username():
   129             print(MISSING_USERNAME)
   130             print('')
   132             name = self._prompt('What is your name?')
   133             email = self._prompt('What is your email address?')
   134             c.set_username(name, email)
   135             print('Updated your username.')
   136             print('')
   138         if not c.have_recommended_diff_settings():
   139             print(BAD_DIFF_SETTINGS)
   140             print('')
   141             if self._prompt_yn('Would you like me to fix this for you'):
   142                 c.ensure_recommended_diff_settings()
   143                 print('Fixed patch settings.')
   144                 print('')
   146         self.prompt_native_extension(c, 'progress',
   147             'Would you like to see progress bars during Mercurial operations')
   149         self.prompt_native_extension(c, 'color',
   150             'Would you like Mercurial to colorize output to your terminal')
   152         self.prompt_native_extension(c, 'rebase',
   153             'Would you like to enable the rebase extension to allow you to move'
   154             ' changesets around (which can help maintain a linear history)')
   156         self.prompt_native_extension(c, 'mq',
   157             'Would you like to activate the mq extension to manage patches')
   159         self.prompt_external_extension(c, 'bzexport', BZEXPORT_INFO)
   161         if 'mq' in c.extensions:
   162             self.prompt_external_extension(c, 'mqext', MQEXT_INFO,
   163                                            os.path.join(self.ext_dir, 'mqext'))
   165             if 'mqext' in c.extensions:
   166                 self.update_mercurial_repo(
   167                     hg,
   168                     'https://bitbucket.org/sfink/mqext',
   169                     os.path.join(self.ext_dir, 'mqext'),
   170                     'default',
   171                     'Ensuring mqext extension is up to date...')
   173             if 'mqext' in c.extensions and not c.have_mqext_autocommit_mq():
   174                 if self._prompt_yn('Would you like to configure mqext to '
   175                     'automatically commit changes as you modify patches'):
   176                     c.ensure_mqext_autocommit_mq()
   177                     print('Configured mqext to auto-commit.\n')
   179             self.prompt_external_extension(c, 'qimportbz', QIMPORTBZ_INFO)
   181             if not c.have_qnew_currentuser_default():
   182                 print(QNEWCURRENTUSER_INFO)
   183                 if self._prompt_yn('Would you like qnew to set patch author by '
   184                                    'default'):
   185                     c.ensure_qnew_currentuser_default()
   186                     print('Configured qnew to set patch author by default.')
   187                     print('')
   189         if self.update_vcs_tools:
   190             self.update_mercurial_repo(
   191                 hg,
   192                 'https://hg.mozilla.org/hgcustom/version-control-tools',
   193                 self.vcs_tools_dir,
   194                 'default',
   195                 'Ensuring version-control-tools is up to date...')
   197         # Look for and clean up old extensions.
   198         for ext in {'bzexport', 'qimportbz'}:
   199             path = os.path.join(self.ext_dir, ext)
   200             if os.path.exists(path):
   201                 if self._prompt_yn('Would you like to remove the old and no '
   202                     'longer referenced repository at %s' % path):
   203                     print('Cleaning up old repository: %s' % path)
   204                     shutil.rmtree(path)
   206         c.add_mozilla_host_fingerprints()
   208         b = StringIO()
   209         c.write(b)
   210         new_lines = [line.rstrip() for line in b.getvalue().splitlines()]
   211         old_lines = []
   213         config_path = c.config_path
   214         if os.path.exists(config_path):
   215             with open(config_path, 'rt') as fh:
   216                 old_lines = [line.rstrip() for line in fh.readlines()]
   218         diff = list(difflib.unified_diff(old_lines, new_lines,
   219             'hgrc.old', 'hgrc.new'))
   221         if len(diff):
   222             print('Your Mercurial config file needs updating. I can do this '
   223                 'for you if you like!')
   224             if self._prompt_yn('Would you like to see a diff of the changes '
   225                 'first'):
   226                 for line in diff:
   227                     print(line)
   228                 print('')
   230             if self._prompt_yn('Would you like me to update your hgrc file'):
   231                 with open(config_path, 'wt') as fh:
   232                     c.write(fh)
   233                 print('Wrote changes to %s.' % config_path)
   234             else:
   235                 print('hgrc changes not written to file. I would have '
   236                     'written the following:\n')
   237                 c.write(sys.stdout)
   238                 return 1
   240         print(FINISHED)
   241         return 0
   243     def prompt_native_extension(self, c, name, prompt_text):
   244         # Ask the user if the specified extension bundled with Mercurial should be enabled.
   245         if name in c.extensions:
   246             return
   247         if self._prompt_yn(prompt_text):
   248             c.activate_extension(name)
   249             print('Activated %s extension.\n' % name)
   251     def prompt_external_extension(self, c, name, prompt_text, path=None):
   252         # Ask the user if the specified extension should be enabled. Defaults
   253         # to treating the extension as one in version-control-tools/hgext/
   254         # in a directory with the same name as the extension and thus also
   255         # flagging the version-control-tools repo as needing an update.
   256         if name not in c.extensions:
   257             if not self._prompt_yn(prompt_text):
   258                 return
   259             print('Activated %s extension.\n' % name)
   260         if not path:
   261             path = os.path.join(self.vcs_tools_dir, 'hgext', name)
   262             self.update_vcs_tools = True
   263         c.activate_extension(name, path)
   265     def update_mercurial_repo(self, hg, url, dest, branch, msg):
   266         # We always pass the host fingerprints that we "know" to be canonical
   267         # because the existing config may have outdated fingerprints and this
   268         # may cause Mercurial to abort.
   269         return self._update_repo(hg, url, dest, branch, msg,
   270             update_mercurial_repo, hostfingerprints=HOST_FINGERPRINTS)
   272     def update_git_repo(self, git, url, dest, ref, msg):
   273         return self._update_repo(git, url, dest, ref, msg, update_git_repo)
   275     def _update_repo(self, binary, url, dest, branch, msg, fn, *args, **kwargs):
   276         print('=' * 80)
   277         print(msg)
   278         try:
   279             fn(binary, url, dest, branch, *args, **kwargs)
   280         finally:
   281             print('=' * 80)
   282             print('')
   284     def _prompt(self, msg):
   285         print(msg)
   287         while True:
   288             response = raw_input()
   290             if response:
   291                 return response
   293             print('You must type something!')
   295     def _prompt_yn(self, msg):
   296         print('%s? [Y/n]' % msg)
   298         while True:
   299             choice = raw_input().lower().strip()
   301             if not choice:
   302                 return True
   304             if choice in ('y', 'yes'):
   305                 return True
   307             if choice in ('n', 'no'):
   308                 return False
   310             print('Must reply with one of {yes, no, y, n}.')

mercurial