Bug 794580 - mach mercurial-setup; r=nalexander

DONTBUILD (NPOTB)

--HG--
extra : rebase_source : b5cfc81d1a0537b5ae25a76c3ccc604383f60f6c
This commit is contained in:
Gregory Szorc 2013-07-29 16:58:40 -07:00
Родитель 8dc51dec14
Коммит c079fd38e7
7 изменённых файлов: 468 добавлений и 0 удалений

Просмотреть файл

@ -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