diff -r 000000000000 -r 6474c204b198 tools/mercurial/hgsetup/wizard.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/mercurial/hgsetup/wizard.py Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,310 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this, +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from __future__ import unicode_literals + +import difflib +import errno +import os +import shutil +import sys +import which + +from configobj import ConfigObjError +from StringIO import StringIO + +from mozversioncontrol.repoupdate import ( + update_mercurial_repo, + update_git_repo, +) + +from .config import ( + HOST_FINGERPRINTS, + MercurialConfig, +) + + +INITIAL_MESSAGE = ''' +I'm going to help you ensure your Mercurial is configured for optimal +development on Mozilla projects. + +If your environment is missing some recommended settings, I'm going to prompt +you whether you want me to make changes: I won't change anything you might not +want me changing without your permission! + +If your config is up-to-date, I'm just going to ensure all 3rd party extensions +are up to date and you won't have to do anything. + +To begin, press the enter/return key. +'''.strip() + +MISSING_USERNAME = ''' +You don't have a username defined in your Mercurial config file. In order to +send patches to Mozilla, you'll need to attach a name and email address. If you +aren't comfortable giving us your full name, pseudonames are acceptable. +'''.strip() + +BAD_DIFF_SETTINGS = ''' +Mozilla developers produce patches in a standard format, but your Mercurial is +not configured to produce patches in that format. +'''.strip() + +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 + +Would you like to activate bzexport +'''.strip() + +MQEXT_INFO = ''' +The mqext extension (https://bitbucket.org/sfink/mqext) provides a number of +useful abilities to Mercurial, including automatically committing changes to +your mq patch queue. + +Would you like to activate mqext +'''.strip() + +QIMPORTBZ_INFO = ''' +The qimportbz extension +(https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/qimportbz/README) makes it possible to +import patches from Bugzilla using a friendly bz:// URL handler. e.g. +|hg qimport bz://123456|. + +Would you like to activate qimportbz +'''.strip() + +QNEWCURRENTUSER_INFO = ''' +The mercurial queues command |hg qnew|, which creates new patches in your patch +queue does not set patch author information by default. Author information +should be included when uploading for review. +'''.strip() + +FINISHED = ''' +Your Mercurial should now be properly configured and recommended extensions +should be up to date! +'''.strip() + + +class MercurialSetupWizard(object): + """Command-line wizard to help users configure Mercurial.""" + + def __init__(self, state_dir): + # We use normpath since Mercurial expects the hgrc to use native path + # separators, but state_dir uses unix style paths even on Windows. + self.state_dir = os.path.normpath(state_dir) + self.ext_dir = os.path.join(self.state_dir, 'mercurial', 'extensions') + self.vcs_tools_dir = os.path.join(self.state_dir, 'version-control-tools') + self.update_vcs_tools = False + + def run(self, config_paths): + try: + os.makedirs(self.ext_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + try: + hg = which.which('hg') + except which.WhichError as e: + print(e) + print('Try running |mach bootstrap| to ensure your environment is ' + 'up to date.') + return 1 + + try: + c = MercurialConfig(config_paths) + except ConfigObjError as e: + print('Error importing existing Mercurial config!\n' + '%s\n' + 'If using quotes, they must wrap the entire string.' % e) + return 1 + + print(INITIAL_MESSAGE) + raw_input() + + if not c.have_valid_username(): + print(MISSING_USERNAME) + print('') + + name = self._prompt('What is your name?') + email = self._prompt('What is your email address?') + c.set_username(name, email) + print('Updated your username.') + print('') + + if not c.have_recommended_diff_settings(): + print(BAD_DIFF_SETTINGS) + print('') + if self._prompt_yn('Would you like me to fix this for you'): + c.ensure_recommended_diff_settings() + print('Fixed patch settings.') + print('') + + self.prompt_native_extension(c, 'progress', + 'Would you like to see progress bars during Mercurial operations') + + self.prompt_native_extension(c, 'color', + 'Would you like Mercurial to colorize output to your terminal') + + self.prompt_native_extension(c, 'rebase', + 'Would you like to enable the rebase extension to allow you to move' + ' changesets around (which can help maintain a linear history)') + + self.prompt_native_extension(c, 'mq', + 'Would you like to activate the mq extension to manage patches') + + self.prompt_external_extension(c, 'bzexport', BZEXPORT_INFO) + + if 'mq' in c.extensions: + self.prompt_external_extension(c, 'mqext', MQEXT_INFO, + os.path.join(self.ext_dir, 'mqext')) + + if 'mqext' in c.extensions: + self.update_mercurial_repo( + hg, + 'https://bitbucket.org/sfink/mqext', + os.path.join(self.ext_dir, 'mqext'), + 'default', + 'Ensuring mqext extension is up to date...') + + if 'mqext' in c.extensions and not c.have_mqext_autocommit_mq(): + if self._prompt_yn('Would you like to configure mqext to ' + 'automatically commit changes as you modify patches'): + c.ensure_mqext_autocommit_mq() + print('Configured mqext to auto-commit.\n') + + self.prompt_external_extension(c, 'qimportbz', QIMPORTBZ_INFO) + + if not c.have_qnew_currentuser_default(): + print(QNEWCURRENTUSER_INFO) + if self._prompt_yn('Would you like qnew to set patch author by ' + 'default'): + c.ensure_qnew_currentuser_default() + print('Configured qnew to set patch author by default.') + print('') + + if self.update_vcs_tools: + self.update_mercurial_repo( + hg, + 'https://hg.mozilla.org/hgcustom/version-control-tools', + self.vcs_tools_dir, + 'default', + 'Ensuring version-control-tools is up to date...') + + # Look for and clean up old extensions. + for ext in {'bzexport', 'qimportbz'}: + 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) + + c.add_mozilla_host_fingerprints() + + b = StringIO() + c.write(b) + new_lines = [line.rstrip() for line in b.getvalue().splitlines()] + old_lines = [] + + config_path = c.config_path + if os.path.exists(config_path): + with open(config_path, 'rt') as fh: + old_lines = [line.rstrip() for line in fh.readlines()] + + diff = list(difflib.unified_diff(old_lines, new_lines, + 'hgrc.old', 'hgrc.new')) + + if len(diff): + print('Your Mercurial config file needs updating. I can do this ' + 'for you if you like!') + if self._prompt_yn('Would you like to see a diff of the changes ' + 'first'): + for line in diff: + print(line) + print('') + + if self._prompt_yn('Would you like me to update your hgrc file'): + with open(config_path, 'wt') as fh: + c.write(fh) + print('Wrote changes to %s.' % config_path) + else: + print('hgrc changes not written to file. I would have ' + 'written the following:\n') + c.write(sys.stdout) + return 1 + + print(FINISHED) + return 0 + + def prompt_native_extension(self, c, name, prompt_text): + # Ask the user if the specified extension bundled with Mercurial should be enabled. + if name in c.extensions: + return + if self._prompt_yn(prompt_text): + c.activate_extension(name) + print('Activated %s extension.\n' % name) + + def prompt_external_extension(self, c, name, prompt_text, path=None): + # Ask the user if the specified extension should be enabled. Defaults + # to treating the extension as one in version-control-tools/hgext/ + # in a directory with the same name as the extension and thus also + # flagging the version-control-tools repo as needing an update. + if name not in c.extensions: + if not self._prompt_yn(prompt_text): + return + print('Activated %s extension.\n' % name) + if not path: + path = os.path.join(self.vcs_tools_dir, 'hgext', name) + self.update_vcs_tools = True + c.activate_extension(name, path) + + def update_mercurial_repo(self, hg, url, dest, branch, msg): + # We always pass the host fingerprints that we "know" to be canonical + # because the existing config may have outdated fingerprints and this + # may cause Mercurial to abort. + return self._update_repo(hg, url, dest, branch, msg, + update_mercurial_repo, hostfingerprints=HOST_FINGERPRINTS) + + def update_git_repo(self, git, url, dest, ref, msg): + return self._update_repo(git, url, dest, ref, msg, update_git_repo) + + def _update_repo(self, binary, url, dest, branch, msg, fn, *args, **kwargs): + print('=' * 80) + print(msg) + try: + fn(binary, url, dest, branch, *args, **kwargs) + finally: + print('=' * 80) + print('') + + def _prompt(self, msg): + print(msg) + + while True: + response = raw_input() + + if response: + return response + + print('You must type something!') + + def _prompt_yn(self, msg): + print('%s? [Y/n]' % msg) + + while True: + choice = raw_input().lower().strip() + + if not choice: + return True + + if choice in ('y', 'yes'): + return True + + if choice in ('n', 'no'): + return False + + print('Must reply with one of {yes, no, y, n}.')