Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this,
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 from __future__ import unicode_literals
7 import difflib
8 import errno
9 import os
10 import shutil
11 import sys
12 import which
14 from configobj import ConfigObjError
15 from StringIO import StringIO
17 from mozversioncontrol.repoupdate import (
18 update_mercurial_repo,
19 update_git_repo,
20 )
22 from .config import (
23 HOST_FINGERPRINTS,
24 MercurialConfig,
25 )
28 INITIAL_MESSAGE = '''
29 I'm going to help you ensure your Mercurial is configured for optimal
30 development on Mozilla projects.
32 If your environment is missing some recommended settings, I'm going to prompt
33 you whether you want me to make changes: I won't change anything you might not
34 want me changing without your permission!
36 If your config is up-to-date, I'm just going to ensure all 3rd party extensions
37 are up to date and you won't have to do anything.
39 To begin, press the enter/return key.
40 '''.strip()
42 MISSING_USERNAME = '''
43 You don't have a username defined in your Mercurial config file. In order to
44 send patches to Mozilla, you'll need to attach a name and email address. If you
45 aren't comfortable giving us your full name, pseudonames are acceptable.
46 '''.strip()
48 BAD_DIFF_SETTINGS = '''
49 Mozilla developers produce patches in a standard format, but your Mercurial is
50 not configured to produce patches in that format.
51 '''.strip()
53 BZEXPORT_INFO = '''
54 If you plan on uploading patches to Mozilla, there is an extension called
55 bzexport that makes it easy to upload patches from the command line via the
56 |hg bzexport| command. More info is available at
57 https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/bzexport/README
59 Would you like to activate bzexport
60 '''.strip()
62 MQEXT_INFO = '''
63 The mqext extension (https://bitbucket.org/sfink/mqext) provides a number of
64 useful abilities to Mercurial, including automatically committing changes to
65 your mq patch queue.
67 Would you like to activate mqext
68 '''.strip()
70 QIMPORTBZ_INFO = '''
71 The qimportbz extension
72 (https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/qimportbz/README) makes it possible to
73 import patches from Bugzilla using a friendly bz:// URL handler. e.g.
74 |hg qimport bz://123456|.
76 Would you like to activate qimportbz
77 '''.strip()
79 QNEWCURRENTUSER_INFO = '''
80 The mercurial queues command |hg qnew|, which creates new patches in your patch
81 queue does not set patch author information by default. Author information
82 should be included when uploading for review.
83 '''.strip()
85 FINISHED = '''
86 Your Mercurial should now be properly configured and recommended extensions
87 should be up to date!
88 '''.strip()
91 class MercurialSetupWizard(object):
92 """Command-line wizard to help users configure Mercurial."""
94 def __init__(self, state_dir):
95 # We use normpath since Mercurial expects the hgrc to use native path
96 # separators, but state_dir uses unix style paths even on Windows.
97 self.state_dir = os.path.normpath(state_dir)
98 self.ext_dir = os.path.join(self.state_dir, 'mercurial', 'extensions')
99 self.vcs_tools_dir = os.path.join(self.state_dir, 'version-control-tools')
100 self.update_vcs_tools = False
102 def run(self, config_paths):
103 try:
104 os.makedirs(self.ext_dir)
105 except OSError as e:
106 if e.errno != errno.EEXIST:
107 raise
109 try:
110 hg = which.which('hg')
111 except which.WhichError as e:
112 print(e)
113 print('Try running |mach bootstrap| to ensure your environment is '
114 'up to date.')
115 return 1
117 try:
118 c = MercurialConfig(config_paths)
119 except ConfigObjError as e:
120 print('Error importing existing Mercurial config!\n'
121 '%s\n'
122 'If using quotes, they must wrap the entire string.' % e)
123 return 1
125 print(INITIAL_MESSAGE)
126 raw_input()
128 if not c.have_valid_username():
129 print(MISSING_USERNAME)
130 print('')
132 name = self._prompt('What is your name?')
133 email = self._prompt('What is your email address?')
134 c.set_username(name, email)
135 print('Updated your username.')
136 print('')
138 if not c.have_recommended_diff_settings():
139 print(BAD_DIFF_SETTINGS)
140 print('')
141 if self._prompt_yn('Would you like me to fix this for you'):
142 c.ensure_recommended_diff_settings()
143 print('Fixed patch settings.')
144 print('')
146 self.prompt_native_extension(c, 'progress',
147 'Would you like to see progress bars during Mercurial operations')
149 self.prompt_native_extension(c, 'color',
150 'Would you like Mercurial to colorize output to your terminal')
152 self.prompt_native_extension(c, 'rebase',
153 'Would you like to enable the rebase extension to allow you to move'
154 ' changesets around (which can help maintain a linear history)')
156 self.prompt_native_extension(c, 'mq',
157 'Would you like to activate the mq extension to manage patches')
159 self.prompt_external_extension(c, 'bzexport', BZEXPORT_INFO)
161 if 'mq' in c.extensions:
162 self.prompt_external_extension(c, 'mqext', MQEXT_INFO,
163 os.path.join(self.ext_dir, 'mqext'))
165 if 'mqext' in c.extensions:
166 self.update_mercurial_repo(
167 hg,
168 'https://bitbucket.org/sfink/mqext',
169 os.path.join(self.ext_dir, 'mqext'),
170 'default',
171 'Ensuring mqext extension is up to date...')
173 if 'mqext' in c.extensions and not c.have_mqext_autocommit_mq():
174 if self._prompt_yn('Would you like to configure mqext to '
175 'automatically commit changes as you modify patches'):
176 c.ensure_mqext_autocommit_mq()
177 print('Configured mqext to auto-commit.\n')
179 self.prompt_external_extension(c, 'qimportbz', QIMPORTBZ_INFO)
181 if not c.have_qnew_currentuser_default():
182 print(QNEWCURRENTUSER_INFO)
183 if self._prompt_yn('Would you like qnew to set patch author by '
184 'default'):
185 c.ensure_qnew_currentuser_default()
186 print('Configured qnew to set patch author by default.')
187 print('')
189 if self.update_vcs_tools:
190 self.update_mercurial_repo(
191 hg,
192 'https://hg.mozilla.org/hgcustom/version-control-tools',
193 self.vcs_tools_dir,
194 'default',
195 'Ensuring version-control-tools is up to date...')
197 # Look for and clean up old extensions.
198 for ext in {'bzexport', 'qimportbz'}:
199 path = os.path.join(self.ext_dir, ext)
200 if os.path.exists(path):
201 if self._prompt_yn('Would you like to remove the old and no '
202 'longer referenced repository at %s' % path):
203 print('Cleaning up old repository: %s' % path)
204 shutil.rmtree(path)
206 c.add_mozilla_host_fingerprints()
208 b = StringIO()
209 c.write(b)
210 new_lines = [line.rstrip() for line in b.getvalue().splitlines()]
211 old_lines = []
213 config_path = c.config_path
214 if os.path.exists(config_path):
215 with open(config_path, 'rt') as fh:
216 old_lines = [line.rstrip() for line in fh.readlines()]
218 diff = list(difflib.unified_diff(old_lines, new_lines,
219 'hgrc.old', 'hgrc.new'))
221 if len(diff):
222 print('Your Mercurial config file needs updating. I can do this '
223 'for you if you like!')
224 if self._prompt_yn('Would you like to see a diff of the changes '
225 'first'):
226 for line in diff:
227 print(line)
228 print('')
230 if self._prompt_yn('Would you like me to update your hgrc file'):
231 with open(config_path, 'wt') as fh:
232 c.write(fh)
233 print('Wrote changes to %s.' % config_path)
234 else:
235 print('hgrc changes not written to file. I would have '
236 'written the following:\n')
237 c.write(sys.stdout)
238 return 1
240 print(FINISHED)
241 return 0
243 def prompt_native_extension(self, c, name, prompt_text):
244 # Ask the user if the specified extension bundled with Mercurial should be enabled.
245 if name in c.extensions:
246 return
247 if self._prompt_yn(prompt_text):
248 c.activate_extension(name)
249 print('Activated %s extension.\n' % name)
251 def prompt_external_extension(self, c, name, prompt_text, path=None):
252 # Ask the user if the specified extension should be enabled. Defaults
253 # to treating the extension as one in version-control-tools/hgext/
254 # in a directory with the same name as the extension and thus also
255 # flagging the version-control-tools repo as needing an update.
256 if name not in c.extensions:
257 if not self._prompt_yn(prompt_text):
258 return
259 print('Activated %s extension.\n' % name)
260 if not path:
261 path = os.path.join(self.vcs_tools_dir, 'hgext', name)
262 self.update_vcs_tools = True
263 c.activate_extension(name, path)
265 def update_mercurial_repo(self, hg, url, dest, branch, msg):
266 # We always pass the host fingerprints that we "know" to be canonical
267 # because the existing config may have outdated fingerprints and this
268 # may cause Mercurial to abort.
269 return self._update_repo(hg, url, dest, branch, msg,
270 update_mercurial_repo, hostfingerprints=HOST_FINGERPRINTS)
272 def update_git_repo(self, git, url, dest, ref, msg):
273 return self._update_repo(git, url, dest, ref, msg, update_git_repo)
275 def _update_repo(self, binary, url, dest, branch, msg, fn, *args, **kwargs):
276 print('=' * 80)
277 print(msg)
278 try:
279 fn(binary, url, dest, branch, *args, **kwargs)
280 finally:
281 print('=' * 80)
282 print('')
284 def _prompt(self, msg):
285 print(msg)
287 while True:
288 response = raw_input()
290 if response:
291 return response
293 print('You must type something!')
295 def _prompt_yn(self, msg):
296 print('%s? [Y/n]' % msg)
298 while True:
299 choice = raw_input().lower().strip()
301 if not choice:
302 return True
304 if choice in ('y', 'yes'):
305 return True
307 if choice in ('n', 'no'):
308 return False
310 print('Must reply with one of {yes, no, y, n}.')