mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
0cbaaff5e7
All extensions in version-control-tools should support Bugzilla API Keys now. MozReview requires them. We'll likely remove support for passwords and cookie auth in the future. This commit transitions the Mercurial setup wizard to API Keys exclusively. DONTBUILD (NPOTB)
560 lines
20 KiB
Python
560 lines
20 KiB
Python
# 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 stat
|
|
import sys
|
|
import subprocess
|
|
|
|
from distutils.version import LooseVersion
|
|
|
|
from configobj import ConfigObjError
|
|
from StringIO import StringIO
|
|
|
|
from mozversioncontrol import get_hg_path, get_hg_version
|
|
|
|
from .update import MercurialUpdater
|
|
from .config import (
|
|
config_file,
|
|
MercurialConfig,
|
|
ParseException,
|
|
)
|
|
|
|
|
|
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()
|
|
|
|
OLDEST_NON_LEGACY_VERSION = LooseVersion('3.2.4')
|
|
LEGACY_MERCURIAL = '''
|
|
You are running an out of date Mercurial client (%s).
|
|
|
|
For a faster and better Mercurial experience, we HIGHLY recommend you
|
|
upgrade.
|
|
'''.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.
|
|
|
|
(Relevant config option: ui.username)
|
|
'''.strip()
|
|
|
|
BAD_DIFF_SETTINGS = '''
|
|
Mozilla developers produce patches in a standard format, but your Mercurial is
|
|
not configured to produce patches in that format.
|
|
|
|
(Relevant config options: diff.git, diff.showfunc, diff.unified)
|
|
'''.strip()
|
|
|
|
MQ_INFO = '''
|
|
The mq extension manages patches as separate files. It provides an
|
|
alternative to the recommended bookmark-based development workflow.
|
|
|
|
If you are a newcomer to Mercurial or are coming from Git, it is
|
|
recommended to avoid mq.
|
|
|
|
(Relevant config option: extensions.mq)
|
|
|
|
Would you like to activate the mq extension
|
|
'''.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
|
|
|
|
(Relevant config option: extensions.bzexport)
|
|
|
|
Would you like to activate bzexport
|
|
'''.strip()
|
|
|
|
MQEXT_INFO = '''
|
|
The mqext extension adds a number of features, including automatically committing
|
|
changes to your mq patch queue. More info is available at
|
|
https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/mqext/README.txt
|
|
|
|
(Relevant config option: extensions.mqext)
|
|
|
|
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|.
|
|
|
|
(Relevant config option: extensions.qimportbz)
|
|
|
|
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()
|
|
|
|
REVIEWBOARD_MINIMUM_VERSION = LooseVersion('3.1')
|
|
|
|
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()
|
|
|
|
BZPOST_MINIMUM_VERSION = LooseVersion('3.1')
|
|
|
|
BZPOST_INFO = '''
|
|
The bzpost extension automatically records the URLs of pushed commits to
|
|
referenced Bugzilla bugs after push.
|
|
|
|
(Relevant config option: extensions.bzpost)
|
|
|
|
Would you like to activate bzpost
|
|
'''.strip()
|
|
|
|
FIREFOXTREE_MINIMUM_VERSION = LooseVersion('3.1')
|
|
|
|
FIREFOXTREE_INFO = '''
|
|
The firefoxtree extension makes interacting with the multiple Firefox
|
|
repositories easier:
|
|
|
|
* Aliases for common trees are pre-defined. e.g. `hg pull central`
|
|
* Pulling from known Firefox trees will create "remote refs" appearing as
|
|
tags. e.g. pulling from fx-team will produce a "fx-team" tag.
|
|
* The `hg fxheads` command will list the heads of all pulled Firefox repos
|
|
for easy reference.
|
|
* `hg push` will limit itself to pushing a single head when pushing to
|
|
Firefox repos.
|
|
* A pre-push hook will prevent you from pushing multiple heads to known
|
|
Firefox repos. This acts quicker than a server-side hook.
|
|
|
|
The firefoxtree extension is *strongly* recommended if you:
|
|
|
|
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
|
|
'''.strip()
|
|
|
|
PUSHTOTRY_MINIMUM_VERSION = LooseVersion('3.3')
|
|
|
|
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.
|
|
|
|
(Relevant config option: extensions.push-to-try)
|
|
|
|
Would you like to activate push-to-try
|
|
'''.strip()
|
|
|
|
BUNDLECLONE_MINIMUM_VERSION = LooseVersion('3.1')
|
|
|
|
BUNDLECLONE_INFO = '''
|
|
The bundleclone extension makes cloning faster and saves server resources.
|
|
|
|
We highly recommend you activate this extension.
|
|
|
|
(Relevant config option: extensions.bundleclone)
|
|
|
|
Would you like to activate bundleclone
|
|
'''.strip()
|
|
|
|
FILE_PERMISSIONS_WARNING = '''
|
|
Your hgrc file is currently readable by others.
|
|
|
|
Sensitive information such as your Bugzilla credentials could be
|
|
stolen if others have access to this file/machine.
|
|
'''.strip()
|
|
|
|
MULTIPLE_VCT = '''
|
|
*** WARNING ***
|
|
|
|
Multiple version-control-tools repositories are referenced in your
|
|
Mercurial config. Extensions and other code within the
|
|
version-control-tools repository could run with inconsistent results.
|
|
|
|
Please manually edit the following file to reference a single
|
|
version-control-tools repository:
|
|
|
|
%s
|
|
'''.lstrip()
|
|
|
|
|
|
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.updater = MercurialUpdater(state_dir)
|
|
|
|
def run(self, config_paths):
|
|
try:
|
|
os.makedirs(self.ext_dir)
|
|
except OSError as e:
|
|
if e.errno != errno.EEXIST:
|
|
raise
|
|
|
|
hg = get_hg_path()
|
|
config_path = config_file(config_paths)
|
|
|
|
try:
|
|
c = MercurialConfig(config_path)
|
|
except ConfigObjError as e:
|
|
print('Error importing existing Mercurial config: %s\n' % config_path)
|
|
for error in e.errors:
|
|
print(error.message)
|
|
|
|
return 1
|
|
except ParseException as e:
|
|
print('Error importing existing Mercurial config: %s\n' % config_path)
|
|
print('Line %d: %s' % (e.line, e.message))
|
|
|
|
return 1
|
|
|
|
self.updater.update_all()
|
|
|
|
print(INITIAL_MESSAGE)
|
|
raw_input()
|
|
|
|
hg_version = get_hg_version(hg)
|
|
if hg_version < OLDEST_NON_LEGACY_VERSION:
|
|
print(LEGACY_MERCURIAL % hg_version)
|
|
print('')
|
|
|
|
if os.name == 'nt':
|
|
print('Please upgrade to the latest MozillaBuild to upgrade '
|
|
'your Mercurial install.')
|
|
print('')
|
|
else:
|
|
print('Please run |mach bootstrap| to upgrade your Mercurial '
|
|
'install.')
|
|
print('')
|
|
|
|
if not self._prompt_yn('Would you like to continue using an old '
|
|
'Mercurial version'):
|
|
return 1
|
|
|
|
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('')
|
|
|
|
# Progress is built into core and enabled by default in Mercurial 3.5.
|
|
if hg_version < LooseVersion('3.5'):
|
|
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, 'histedit',
|
|
'Would you like to enable the histedit extension to allow history '
|
|
'rewriting via the "histedit" command (similar to '
|
|
'`git rebase -i`)')
|
|
|
|
self.prompt_native_extension(c, 'mq', MQ_INFO)
|
|
|
|
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 >= BZPOST_MINIMUM_VERSION:
|
|
self.prompt_external_extension(c, 'bzpost', BZPOST_INFO)
|
|
|
|
if hg_version >= FIREFOXTREE_MINIMUM_VERSION:
|
|
self.prompt_external_extension(c, 'firefoxtree', FIREFOXTREE_INFO)
|
|
|
|
if hg_version >= BUNDLECLONE_MINIMUM_VERSION:
|
|
self.prompt_external_extension(c, 'bundleclone', BUNDLECLONE_INFO)
|
|
|
|
if hg_version >= PUSHTOTRY_MINIMUM_VERSION:
|
|
self.prompt_external_extension(c, 'push-to-try', PUSHTOTRY_INFO)
|
|
|
|
if 'mq' in c.extensions:
|
|
self.prompt_external_extension(c, 'mqext', MQEXT_INFO)
|
|
|
|
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 'reviewboard' in c.extensions or 'bzpost' 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)
|
|
|
|
c.add_mozilla_host_fingerprints()
|
|
|
|
# References to multiple version-control-tools checkouts can confuse
|
|
# version-control-tools, since various Mercurial extensions resolve
|
|
# dependencies via __file__ and repos could reference another copy.
|
|
seen_vct = set()
|
|
for k, v in c.config.get('extensions', {}).items():
|
|
if 'version-control-tools' not in v:
|
|
continue
|
|
|
|
i = v.index('version-control-tools')
|
|
vct = v[0:i + len('version-control-tools')]
|
|
seen_vct.add(os.path.realpath(os.path.expanduser(vct)))
|
|
|
|
if len(seen_vct) > 1:
|
|
print(MULTIPLE_VCT % c.config_path)
|
|
|
|
# At this point the config should be finalized.
|
|
|
|
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
|
|
|
|
if sys.platform != 'win32':
|
|
# Config file may contain sensitive content, such as passwords.
|
|
# Prompt to remove global permissions.
|
|
mode = os.stat(config_path).st_mode
|
|
if mode & (stat.S_IRWXG | stat.S_IRWXO):
|
|
print(FILE_PERMISSIONS_WARNING)
|
|
if self._prompt_yn('Remove permissions for others to '
|
|
'read your hgrc file'):
|
|
# We don't care about sticky and set UID bits because
|
|
# this is a regular file.
|
|
mode = mode & stat.S_IRWXU
|
|
print('Changing permissions of %s' % config_path)
|
|
os.chmod(config_path, mode)
|
|
|
|
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 can_use_extension(self, c, name, path=None):
|
|
# Load extension to hg and search stdout for printed exceptions
|
|
if not path:
|
|
path = os.path.join(self.vcs_tools_dir, 'hgext', name)
|
|
result = subprocess.check_output(['hg',
|
|
'--config', 'extensions.testmodule=%s' % path,
|
|
'--config', 'ui.traceback=true'],
|
|
stderr=subprocess.STDOUT)
|
|
return b"Traceback" not in result
|
|
|
|
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.can_use_extension(c, name, path):
|
|
return
|
|
print(name)
|
|
print('=' * len(name))
|
|
print('')
|
|
if not self._prompt_yn(prompt_text):
|
|
print('')
|
|
return
|
|
if not path:
|
|
path = os.path.join(self.vcs_tools_dir, 'hgext', name)
|
|
c.activate_extension(name, path)
|
|
print('Activated %s extension.\n' % name)
|
|
|
|
def _prompt(self, msg, allow_empty=False):
|
|
print(msg)
|
|
|
|
while True:
|
|
response = raw_input().decode('utf-8')
|
|
|
|
if response:
|
|
return response
|
|
|
|
if allow_empty:
|
|
return None
|
|
|
|
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}.')
|