зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1277406 - Delete now-unused Mercurial setup wizard; r=glandium
The wizard has been ported to the version-control-tools repository and in-tree consumers have been switched to consume it from there. This code is now dead. Kill it. References to the now-defunct code have been removed/updated. MozReview-Commit-ID: 5fpCXdNIp8L --HG-- extra : rebase_source : 6c1e2363793fe2cd3a506ce5d962788657871203 extra : histedit_source : c40d2203aaa54bbd48e4e2b46178e277dcdc2e3f
This commit is contained in:
Родитель
854ef84c06
Коммит
f0ab95aa08
|
@ -90,7 +90,6 @@ SEARCH_PATHS = [
|
|||
'testing/web-platform/harness',
|
||||
'testing/web-platform/tests/tools/wptserve',
|
||||
'testing/xpcshell',
|
||||
'tools/mercurial',
|
||||
'xpcom/idl-parser',
|
||||
]
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ We recommend the following tools for installing Python:
|
|||
|
||||
# Upgrade Mercurial older than this.
|
||||
# This should match OLDEST_NON_LEGACY_VERSION from
|
||||
# tools/mercurial/hgsetup/wizard.py.
|
||||
# the hg setup wizard in version-control-tools.
|
||||
MODERN_MERCURIAL_VERSION = LooseVersion('3.7.3')
|
||||
|
||||
# Upgrade Python older than this.
|
||||
|
|
|
@ -1,235 +0,0 @@
|
|||
# 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
|
||||
|
||||
from configobj import ConfigObj
|
||||
import codecs
|
||||
import re
|
||||
import os
|
||||
|
||||
|
||||
HOST_FINGERPRINTS = {
|
||||
'bitbucket.org': '3f:d3:c5:17:23:3c:cd:f5:2d:17:76:06:93:7e:ee:97:42:21:14:aa',
|
||||
'bugzilla.mozilla.org': '7c:7a:c4:6c:91:3b:6b:89:cf:f2:8c:13:b8:02:c4:25:bd:1e:25:17',
|
||||
'hg.mozilla.org': 'af:27:b9:34:47:4e:e5:98:01:f6:83:2b:51:c9:aa:d8:df:fb:1a:27',
|
||||
}
|
||||
|
||||
|
||||
def config_file(files):
|
||||
"""Select the most appropriate config file from a list."""
|
||||
if not files:
|
||||
return None
|
||||
|
||||
if len(files) > 1:
|
||||
picky = [(os.path.getsize(f), f) for f in files if os.path.isfile(f)]
|
||||
if picky:
|
||||
return max(picky)[1]
|
||||
|
||||
return files[0]
|
||||
|
||||
|
||||
class ParseException(Exception):
|
||||
def __init__(self, line, msg):
|
||||
self.line = line
|
||||
super(Exception, self).__init__(msg)
|
||||
|
||||
|
||||
class MercurialConfig(object):
|
||||
"""Interface for manipulating a Mercurial config file."""
|
||||
|
||||
def __init__(self, path=None):
|
||||
"""Create a new instance, optionally from an existing hgrc file."""
|
||||
|
||||
self.config_path = path
|
||||
|
||||
# Mercurial configuration files allow an %include directive to include
|
||||
# other files, this is not supported by ConfigObj, so throw a useful
|
||||
# error saying this.
|
||||
if os.path.exists(path):
|
||||
with codecs.open(path, 'r', encoding='utf-8') as f:
|
||||
for i, line in enumerate(f):
|
||||
if line.startswith('%include'):
|
||||
raise ParseException(i + 1,
|
||||
'%include directive is not supported by MercurialConfig')
|
||||
if line.startswith(';'):
|
||||
raise ParseException(i + 1,
|
||||
'semicolon (;) comments are not supported; '
|
||||
'use # instead')
|
||||
|
||||
# write_empty_values is necessary to prevent built-in extensions (which
|
||||
# have no value) from being dropped on write.
|
||||
# list_values aren't needed by Mercurial and disabling them prevents
|
||||
# quotes from being added.
|
||||
self._c = ConfigObj(infile=path, encoding='utf-8',
|
||||
write_empty_values=True, list_values=False)
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return self._c
|
||||
|
||||
@property
|
||||
def extensions(self):
|
||||
"""Returns the set of currently enabled extensions (by name)."""
|
||||
return set(self._c.get('extensions', {}).keys())
|
||||
|
||||
def write(self, fh):
|
||||
return self._c.write(fh)
|
||||
|
||||
def have_valid_username(self):
|
||||
if 'ui' not in self._c:
|
||||
return False
|
||||
|
||||
if 'username' not in self._c['ui']:
|
||||
return False
|
||||
|
||||
# TODO perform actual validation here.
|
||||
|
||||
return True
|
||||
|
||||
def add_mozilla_host_fingerprints(self):
|
||||
"""Add host fingerprints so SSL connections don't warn."""
|
||||
if 'hostfingerprints' not in self._c:
|
||||
self._c['hostfingerprints'] = {}
|
||||
|
||||
for k, v in HOST_FINGERPRINTS.items():
|
||||
self._c['hostfingerprints'][k] = v
|
||||
|
||||
def update_mozilla_host_fingerprints(self):
|
||||
"""Update host fingerprints if they are present."""
|
||||
if 'hostfingerprints' not in self._c:
|
||||
return
|
||||
|
||||
for k, v in HOST_FINGERPRINTS.items():
|
||||
if k in self._c['hostfingerprints']:
|
||||
self._c['hostfingerprints'][k] = v
|
||||
|
||||
def set_username(self, name, email):
|
||||
"""Set the username to use for commits.
|
||||
|
||||
The username consists of a name (typically <firstname> <lastname>) and
|
||||
a well-formed e-mail address.
|
||||
"""
|
||||
if 'ui' not in self._c:
|
||||
self._c['ui'] = {}
|
||||
|
||||
username = '%s <%s>' % (name, email)
|
||||
|
||||
self._c['ui']['username'] = username.strip()
|
||||
|
||||
def activate_extension(self, name, path=None):
|
||||
"""Activate an extension.
|
||||
|
||||
An extension is defined by its name (in the config) and a filesystem
|
||||
path). For built-in extensions, an empty path is specified.
|
||||
"""
|
||||
if not path:
|
||||
path = ''
|
||||
|
||||
if 'extensions' not in self._c:
|
||||
self._c['extensions'] = {}
|
||||
|
||||
self._c['extensions'][name] = path
|
||||
|
||||
def have_recommended_diff_settings(self):
|
||||
if 'diff' not in self._c:
|
||||
return False
|
||||
|
||||
old = dict(self._c['diff'])
|
||||
try:
|
||||
self.ensure_recommended_diff_settings()
|
||||
finally:
|
||||
self._c['diff'].update(old)
|
||||
|
||||
return self._c['diff'] == old
|
||||
|
||||
def ensure_recommended_diff_settings(self):
|
||||
if 'diff' not in self._c:
|
||||
self._c['diff'] = {}
|
||||
|
||||
d = self._c['diff']
|
||||
d['git'] = 1
|
||||
d['showfunc'] = 1
|
||||
d['unified'] = 8
|
||||
|
||||
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_clonebundles(self):
|
||||
return 'clonebundles' in self._c.get('experimental', {})
|
||||
|
||||
def activate_clonebundles(self):
|
||||
exp = self._c.setdefault('experimental', {})
|
||||
exp['clonebundles'] = 'true'
|
||||
|
||||
# bundleclone is redundant with clonebundles. Remove it if it
|
||||
# is installed.
|
||||
ext = self._c.get('extensions', {})
|
||||
try:
|
||||
del ext['bundleclone']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
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'
|
||||
|
||||
revsetalias = self._c.setdefault('revsetalias', {})
|
||||
revsetalias['wip'] = ('('
|
||||
'parents(not public()) '
|
||||
'or not public() '
|
||||
'or . '
|
||||
'or (head() and branch(default))'
|
||||
') and (not obsolete() or unstable()^) '
|
||||
'and not closed()')
|
||||
|
||||
templates = self._c.setdefault('templates', {})
|
||||
templates['wip'] = ("'"
|
||||
# prefix with branch name
|
||||
'{label("log.branch", branches)} '
|
||||
# rev:node
|
||||
'{label("changeset.{phase}", rev)}'
|
||||
'{label("changeset.{phase}", ":")}'
|
||||
'{label("changeset.{phase}", short(node))} '
|
||||
# just the username part of the author, for brevity
|
||||
'{label("grep.user", author|user)}'
|
||||
# tags and bookmarks
|
||||
'{label("log.tag", if(tags," {tags}"))}'
|
||||
'{label("log.tag", if(fxheads," {fxheads}"))} '
|
||||
'{label("log.bookmark", if(bookmarks," {bookmarks}"))}'
|
||||
'\\n'
|
||||
# first line of commit message
|
||||
'{label(ifcontains(rev, revset("."), "desc.here"),desc|firstline)}'
|
||||
"'"
|
||||
)
|
|
@ -1,81 +0,0 @@
|
|||
# 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 os
|
||||
|
||||
from mozversioncontrol import get_hg_path
|
||||
from mozversioncontrol.repoupdate import update_mercurial_repo
|
||||
|
||||
from .config import (
|
||||
HOST_FINGERPRINTS,
|
||||
)
|
||||
|
||||
FINISHED = '''
|
||||
Your Mercurial recommended extensions are now up to date!
|
||||
'''.lstrip()
|
||||
|
||||
|
||||
class MercurialUpdater(object):
|
||||
|
||||
def __init__(self, state_dir):
|
||||
self.state_dir = os.path.normpath(state_dir)
|
||||
self.vcs_tools_dir = os.path.join(self.state_dir, 'version-control-tools')
|
||||
self.hgwatchman_dir = os.path.join(self.state_dir, 'hgwatchman')
|
||||
|
||||
def update_all(self):
|
||||
hg = get_hg_path()
|
||||
|
||||
repo_existed = os.path.isdir(self.vcs_tools_dir)
|
||||
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...')
|
||||
self.update_mercurial_repo(
|
||||
hg,
|
||||
'https://bitbucket.org/facebook/hgwatchman',
|
||||
self.hgwatchman_dir,
|
||||
'5ca0f920df7ec8a93d322f06d554f778184cdcd1',
|
||||
'Ensuring hgwatchman is up to date...')
|
||||
if repo_existed:
|
||||
print(FINISHED)
|
||||
return 0
|
||||
|
||||
def update_mercurial_repo(self, hg, url, dest, branch, msg):
|
||||
# Disable common extensions whose older versions may cause `hg`
|
||||
# invocations to abort.
|
||||
disable_exts = [
|
||||
'bzexport',
|
||||
'bzpost',
|
||||
'firefoxtree',
|
||||
'hgwatchman',
|
||||
'mozext',
|
||||
'mqext',
|
||||
'qimportbz',
|
||||
'push-to-try',
|
||||
'reviewboard',
|
||||
]
|
||||
global_args = []
|
||||
for ext in disable_exts:
|
||||
global_args.extend(['--config', 'extensions.%s=!' % ext])
|
||||
|
||||
# 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,
|
||||
global_args=global_args)
|
||||
|
||||
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('')
|
|
@ -1,611 +0,0 @@
|
|||
# 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 ssl
|
||||
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()
|
||||
|
||||
# This should match MODERN_MERCURIAL_VERSION in
|
||||
# python/mozboot/mozboot/base.py.
|
||||
OLDEST_NON_LEGACY_VERSION = LooseVersion('3.7.3')
|
||||
LEGACY_MERCURIAL = '''
|
||||
You are running an out of date Mercurial client (%s).
|
||||
|
||||
For a faster and better Mercurial experience, we HIGHLY recommend you
|
||||
upgrade.
|
||||
|
||||
Legacy versions of Mercurial have known security vulnerabilities. Failure
|
||||
to upgrade may leave you exposed. You are highly encouraged to upgrade
|
||||
in case you aren't running a patched version.
|
||||
'''.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()
|
||||
|
||||
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()
|
||||
|
||||
BZPOST_MINIMUM_VERSION = LooseVersion('3.5')
|
||||
|
||||
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.5')
|
||||
|
||||
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.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.
|
||||
|
||||
(Relevant config option: extensions.push-to-try)
|
||||
|
||||
Would you like to activate push-to-try
|
||||
'''.strip()
|
||||
|
||||
CLONEBUNDLES_INFO = '''
|
||||
Mercurial 3.6 and hg.mozilla.org support transparently cloning from a CDN,
|
||||
making clones faster and more reliable.
|
||||
|
||||
(Relevant config option: experimental.clonebundles)
|
||||
|
||||
Would you like to activate this feature and have faster clones
|
||||
'''.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()
|
||||
|
||||
WIP_INFO = '''
|
||||
It is common to want a quick view of changesets that are in progress.
|
||||
|
||||
The ``hg wip`` command provides should a view.
|
||||
|
||||
Example Usage:
|
||||
|
||||
$ hg wip
|
||||
o 4084:fcfa34d0387b dminor @
|
||||
| mozreview: use repository name when displaying treeherder results (bug 1230548) r=mcote
|
||||
| @ 4083:786baf6d476a gps
|
||||
| | mozreview: create child review requests from batch API
|
||||
| o 4082:3f100fa4a94f gps
|
||||
| | mozreview: copy more read-only processing code; r?smacleod
|
||||
| o 4081:939417680cbe gps
|
||||
|/ mozreview: add web API to submit an entire series of commits (bug 1229468); r?smacleod
|
||||
|
||||
(Not shown are the colors that help denote the state each changeset
|
||||
is in.)
|
||||
|
||||
(Relevant config options: alias.wip, revsetalias.wip, templates.wip)
|
||||
|
||||
Would you like to install the `hg wip` alias?
|
||||
'''.strip()
|
||||
|
||||
HGWATCHMAN_MINIMUM_VERSION = LooseVersion('3.5.2')
|
||||
FSMONITOR_MINIMUM_VERSION = LooseVersion('3.8')
|
||||
|
||||
FSMONITOR_INFO = '''
|
||||
Filesystem monitor integrates the watchman filesystem watching
|
||||
tool with Mercurial. Commands like `hg status`, `hg diff`, and
|
||||
`hg commit` that need to examine filesystem state can query watchman
|
||||
and obtain filesystem state nearly instantaneously. The result is much
|
||||
faster command execution.
|
||||
|
||||
When enabled, the filesystem monitor tool will launch a background
|
||||
watchman file watching daemon for accessed Mercurial repositories.
|
||||
|
||||
Would you like to enable filesystem monitor tool
|
||||
'''.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`)')
|
||||
|
||||
# hgwatchman is provided by MozillaBuild and we don't yet support
|
||||
# Linux/BSD.
|
||||
# Note that the hgwatchman project has been renamed to fsmonitor and has
|
||||
# been moved into Mercurial core, as of version 3.8. So, if your Mercurial
|
||||
# version is modern enough (>=3.8), you could set fsmonitor in hgrc file
|
||||
# directly.
|
||||
if ('hgwatchman' not in c.extensions
|
||||
and sys.platform.startswith('darwin')
|
||||
and hg_version >= HGWATCHMAN_MINIMUM_VERSION
|
||||
and self._prompt_yn(FSMONITOR_INFO)):
|
||||
if (hg_version >= FSMONITOR_MINIMUM_VERSION):
|
||||
c.activate_extension('fsmonitor')
|
||||
else:
|
||||
# Unlike other extensions, we need to run an installer
|
||||
# to compile a Python C extension.
|
||||
try:
|
||||
subprocess.check_output(
|
||||
['make', 'local'],
|
||||
cwd=self.updater.hgwatchman_dir,
|
||||
stderr=subprocess.STDOUT)
|
||||
|
||||
ext_path = os.path.join(self.updater.hgwatchman_dir,
|
||||
'hgwatchman')
|
||||
if self.can_use_extension(c, 'hgwatchman', ext_path):
|
||||
c.activate_extension('hgwatchman', ext_path)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print('Error compiling hgwatchman; will not install hgwatchman')
|
||||
print(e.output)
|
||||
|
||||
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)
|
||||
|
||||
# Functionality from bundleclone is experimental in Mercurial 3.6.
|
||||
# There was a bug in 3.6, so look for 3.6.1.
|
||||
if hg_version >= LooseVersion('3.6.1'):
|
||||
if not c.have_clonebundles() and self._prompt_yn(CLONEBUNDLES_INFO):
|
||||
c.activate_clonebundles()
|
||||
print('Enabled the clonebundles feature.\n')
|
||||
elif 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 not c.have_wip():
|
||||
if self._prompt_yn(WIP_INFO):
|
||||
c.install_wip_alias()
|
||||
|
||||
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)
|
||||
|
||||
# Python + Mercurial didn't have terrific TLS handling until Python
|
||||
# 2.7.9 and Mercurial 3.4. For this reason, it was recommended to pin
|
||||
# certificates in Mercurial config files. In modern versions of
|
||||
# Mercurial, the system CA store is used and old, legacy TLS protocols
|
||||
# are disabled. The default connection/security setting should
|
||||
# be sufficient and pinning certificates is no longer needed.
|
||||
have_modern_ssl = hasattr(ssl, 'SSLContext')
|
||||
if hg_version < LooseVersion('3.4') or not have_modern_ssl:
|
||||
c.add_mozilla_host_fingerprints()
|
||||
|
||||
# We always update fingerprints if they are present. We /could/ offer to
|
||||
# remove fingerprints if running modern Python and Mercurial. But that
|
||||
# just adds more UI complexity and isn't worth it.
|
||||
c.update_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:
|
||||
# We replace the user's home directory with ~ so the
|
||||
# config file doesn't depend on the path to the home
|
||||
# directory
|
||||
path = os.path.join(self.vcs_tools_dir.replace(os.path.expanduser('~'), '~'), '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}.')
|
Загрузка…
Ссылка в новой задаче