mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-08-18 07:29:28 +00:00
654 lines
24 KiB
Python
654 lines
24 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 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()
|
||
|
|
||
|
OLDEST_NON_LEGACY_VERSION = LooseVersion('3.5.2')
|
||
|
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.3')
|
||
|
|
||
|
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.3')
|
||
|
|
||
|
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.3')
|
||
|
|
||
|
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()
|
||
|
|
||
|
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')
|
||
|
|
||
|
HGWATCHMAN_INFO = '''
|
||
|
The hgwatchman extension 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 installed, the hgwatchman extension will launch a background
|
||
|
watchman file watching daemon for accessed Mercurial repositories. It
|
||
|
should "just work."
|
||
|
|
||
|
Would you like to install hgwatchman
|
||
|
'''.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.
|
||
|
if ('hgwatchman' not in c.extensions
|
||
|
and sys.platform.startswith('darwin')
|
||
|
and hg_version >= HGWATCHMAN_MINIMUM_VERSION
|
||
|
and self._prompt_yn(HGWATCHMAN_INFO)):
|
||
|
# 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)
|
||
|
|
||
|
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)
|
||
|
|
||
|
# 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 '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)
|
||
|
|
||
|
# 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, 'load_default_certs')
|
||
|
if hg_version < LooseVersion('3.4') or not have_modern_ssl:
|
||
|
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:
|
||
|
# 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}.')
|