зеркало из https://github.com/mozilla/gecko-dev.git
Bug 794580 - mach mercurial-setup; r=nalexander
DONTBUILD (NPOTB) --HG-- extra : rebase_source : b5cfc81d1a0537b5ae25a76c3ccc604383f60f6c
This commit is contained in:
Родитель
8dc51dec14
Коммит
c079fd38e7
|
@ -28,6 +28,7 @@ SEARCH_PATHS = [
|
|||
'python/mach',
|
||||
'python/mozboot',
|
||||
'python/mozbuild',
|
||||
'python/mozversioncontrol',
|
||||
'python/blessings',
|
||||
'python/configobj',
|
||||
'python/psutil',
|
||||
|
@ -67,6 +68,8 @@ MACH_MODULES = [
|
|||
'testing/mochitest/mach_commands.py',
|
||||
'testing/xpcshell/mach_commands.py',
|
||||
'testing/talos/mach_commands.py',
|
||||
'testing/xpcshell/mach_commands.py',
|
||||
'tools/mercurial/mach_commands.py',
|
||||
'tools/mach_commands.py',
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# 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
|
||||
import subprocess
|
||||
|
||||
|
||||
# The logic here is far from robust. Improvements are welcome.
|
||||
|
||||
def update_mercurial_repo(hg, repo, path, revision='default'):
|
||||
"""Ensure a HG repository exists at a path and is up to date."""
|
||||
if os.path.exists(path):
|
||||
subprocess.check_call([hg, 'pull', repo], cwd=path)
|
||||
else:
|
||||
subprocess.check_call([hg, 'clone', repo, path])
|
||||
|
||||
subprocess.check_call([hg, 'update', '-r', revision], cwd=path)
|
||||
|
||||
|
||||
def update_git_repo(git, repo, path, revision='origin/master'):
|
||||
"""Ensure a Git repository exists at a path and is up to date."""
|
||||
if os.path.exists(path):
|
||||
subprocess.check_call([git, 'fetch', '--all'], cwd=path)
|
||||
else:
|
||||
subprocess.check_call([git, 'clone', repo, path])
|
||||
|
||||
subprocess.check_call([git, 'checkout', revision], cwd=path)
|
|
@ -0,0 +1,113 @@
|
|||
# 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
|
||||
|
||||
|
||||
BUGZILLA_FINGERPRINT = '45:77:35:fd:6f:2c:1c:c2:90:4b:f7:b4:4d:60:c6:97:c5:5c:47:27'
|
||||
HG_FINGERPRINT = '10:78:e8:57:2d:95:de:7c:de:90:bd:22:e1:38:17:67:c5:a7:9c:14'
|
||||
|
||||
|
||||
class MercurialConfig(object):
|
||||
"""Interface for manipulating a Mercurial config file."""
|
||||
|
||||
def __init__(self, infile=None):
|
||||
"""Create a new instance, optionally from an existing hgrc file."""
|
||||
|
||||
self._c = ConfigObj(infile=infile, encoding='utf-8',
|
||||
write_empty_values=True)
|
||||
|
||||
@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'] = {}
|
||||
|
||||
self._c['hostfingerprints']['bugzilla.mozilla.org'] = \
|
||||
BUGZILLA_FINGERPRINT
|
||||
self._c['hostfingerprints']['hg.mozilla.org'] = HG_FINGERPRINT
|
||||
|
||||
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 autocommit_mq(self, value=True):
|
||||
if 'mqext' not in self._c:
|
||||
self._c['mqext'] = {}
|
||||
|
||||
if value:
|
||||
self._c['mqext']['mqcommit'] = 'auto'
|
||||
else:
|
||||
try:
|
||||
del self._c['mqext']['mqcommit']
|
||||
except KeyError:
|
||||
pass
|
|
@ -0,0 +1,285 @@
|
|||
# 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 sys
|
||||
import which
|
||||
|
||||
from StringIO import StringIO
|
||||
|
||||
from mozversioncontrol.repoupdate import (
|
||||
update_mercurial_repo,
|
||||
update_git_repo,
|
||||
)
|
||||
|
||||
from .config import 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()
|
||||
|
||||
EXTENSIONS_BEGIN = '''
|
||||
I can help you configure a number of Mercurial extensions to make your life
|
||||
easier and more productive. I'm going to ask you a series of questions about
|
||||
what extensions you want enabled.
|
||||
'''.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/users/tmielczarek_mozilla.com/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.
|
||||
'''.strip()
|
||||
|
||||
QIMPORTBZ_INFO = '''
|
||||
The qimportbz extension
|
||||
(https://hg.mozilla.org/users/robarnold_cmu.edu/qimportbz) makes it possible to
|
||||
import patches from Bugzilla using a friendly bz:// URL handler. e.g.
|
||||
|hg qimport bz://123456|.
|
||||
'''.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):
|
||||
self.state_dir = state_dir
|
||||
self.ext_dir = os.path.join(state_dir, 'mercurial', 'extensions')
|
||||
|
||||
def run(self, config_path):
|
||||
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
|
||||
|
||||
c = MercurialConfig(config_path)
|
||||
|
||||
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('')
|
||||
|
||||
active = c.extensions
|
||||
|
||||
if 'progress' not in active:
|
||||
if self._prompt_yn('Would you like to see progress bars during '
|
||||
'long-running Mercurial operations'):
|
||||
c.activate_extension('progress')
|
||||
print('Activated progress extension.')
|
||||
print('')
|
||||
|
||||
if 'color' not in active:
|
||||
if self._prompt_yn('Would you like Mercurial to colorize output '
|
||||
'to your terminal'):
|
||||
c.activate_extension('color')
|
||||
print('Activated color extension.')
|
||||
print('')
|
||||
|
||||
update_bzexport = 'bzexport' in active
|
||||
if 'bzexport' not in active:
|
||||
print(BZEXPORT_INFO)
|
||||
if self._prompt_yn('Would you like to activate bzexport'):
|
||||
update_bzexport = True
|
||||
c.activate_extension('bzexport', os.path.join(self.ext_dir,
|
||||
'bzexport'))
|
||||
print('Activated bzexport extension.')
|
||||
print('')
|
||||
|
||||
if update_bzexport:
|
||||
self.update_mercurial_repo(
|
||||
hg,
|
||||
'https://hg.mozilla.org/users/tmielczarek_mozilla.com/bzexport',
|
||||
os.path.join(self.ext_dir, 'bzexport'),
|
||||
'default',
|
||||
'Ensuring bzexport extension is up to date...')
|
||||
|
||||
if 'mq' not in active:
|
||||
if self._prompt_yn('Would you like to activate the mq extension '
|
||||
'to manage patches'):
|
||||
c.activate_extension('mq')
|
||||
print('Activated mq extension.')
|
||||
print('')
|
||||
|
||||
active = c.extensions
|
||||
|
||||
if 'mq' in active:
|
||||
update_mqext = 'mqext' in active
|
||||
if 'mqext' not in active:
|
||||
print(MQEXT_INFO)
|
||||
if self._prompt_yn('Would you like to activate mqext and '
|
||||
'automatically commit changes as you modify patches'):
|
||||
update_mqext = True
|
||||
c.activate_extension('mqext', os.path.join(self.ext_dir,
|
||||
'mqext'))
|
||||
c.autocommit_mq(True)
|
||||
print('Activated mqext extension.')
|
||||
print('')
|
||||
|
||||
if update_mqext:
|
||||
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...')
|
||||
|
||||
update_qimportbz = 'qimportbz' in active
|
||||
if 'qimportbz' not in active:
|
||||
print(QIMPORTBZ_INFO)
|
||||
if self._prompt_yn('Would you like to activate qimportbz'):
|
||||
update_qimportbz = True
|
||||
c.activate_extension('qimportbz',
|
||||
os.path.join(self.ext_dir, 'qimportbz'))
|
||||
print('Activated qimportbz extension.')
|
||||
print('')
|
||||
|
||||
if update_qimportbz:
|
||||
self.update_mercurial_repo(
|
||||
hg,
|
||||
'https://hg.mozilla.org/users/robarnold_cmu.edu/qimportbz',
|
||||
os.path.join(self.ext_dir, 'qimportbz'),
|
||||
'default',
|
||||
'Ensuring qimportbz extension is up to date...')
|
||||
|
||||
c.add_mozilla_host_fingerprints()
|
||||
|
||||
b = StringIO()
|
||||
c.write(b)
|
||||
new_lines = [line.rstrip() for line in b.getvalue().splitlines()]
|
||||
old_lines = []
|
||||
|
||||
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 update_mercurial_repo(self, hg, url, dest, branch, msg):
|
||||
return self._update_repo(hg, url, dest, branch, msg,
|
||||
update_mercurial_repo)
|
||||
|
||||
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):
|
||||
print('=' * 80)
|
||||
print(msg)
|
||||
try:
|
||||
fn(binary, url, dest, branch)
|
||||
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, no}.')
|
|
@ -0,0 +1,37 @@
|
|||
# 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 print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from mach.decorators import (
|
||||
CommandProvider,
|
||||
Command,
|
||||
)
|
||||
|
||||
|
||||
@CommandProvider
|
||||
class VersionControlCommands(object):
|
||||
def __init__(self, context):
|
||||
self._context = context
|
||||
|
||||
@Command('mercurial-setup', category='devenv',
|
||||
description='Help configure Mercurial for optimal development.')
|
||||
def mercurial_bootstrap(self):
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
|
||||
from hgsetup.wizard import MercurialSetupWizard
|
||||
|
||||
wizard = MercurialSetupWizard(self._context.state_dir)
|
||||
result = wizard.run(os.path.expanduser('~/.hgrc'))
|
||||
|
||||
# Touch a file so we can periodically prompt to update extensions.
|
||||
state_path = os.path.join(self._context.state_dir,
|
||||
'mercurial/setup.lastcheck')
|
||||
with open(state_path, 'a'):
|
||||
os.utime(state_path, None)
|
||||
|
||||
return result
|
Загрузка…
Ссылка в новой задаче