tools/mercurial/hgsetup/wizard.py

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:68b241e6ba27
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/.
4
5 from __future__ import unicode_literals
6
7 import difflib
8 import errno
9 import os
10 import shutil
11 import sys
12 import which
13
14 from configobj import ConfigObjError
15 from StringIO import StringIO
16
17 from mozversioncontrol.repoupdate import (
18 update_mercurial_repo,
19 update_git_repo,
20 )
21
22 from .config import (
23 HOST_FINGERPRINTS,
24 MercurialConfig,
25 )
26
27
28 INITIAL_MESSAGE = '''
29 I'm going to help you ensure your Mercurial is configured for optimal
30 development on Mozilla projects.
31
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!
35
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.
38
39 To begin, press the enter/return key.
40 '''.strip()
41
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()
47
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()
52
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
58
59 Would you like to activate bzexport
60 '''.strip()
61
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.
66
67 Would you like to activate mqext
68 '''.strip()
69
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|.
75
76 Would you like to activate qimportbz
77 '''.strip()
78
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()
84
85 FINISHED = '''
86 Your Mercurial should now be properly configured and recommended extensions
87 should be up to date!
88 '''.strip()
89
90
91 class MercurialSetupWizard(object):
92 """Command-line wizard to help users configure Mercurial."""
93
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
101
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
108
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
116
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
124
125 print(INITIAL_MESSAGE)
126 raw_input()
127
128 if not c.have_valid_username():
129 print(MISSING_USERNAME)
130 print('')
131
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('')
137
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('')
145
146 self.prompt_native_extension(c, 'progress',
147 'Would you like to see progress bars during Mercurial operations')
148
149 self.prompt_native_extension(c, 'color',
150 'Would you like Mercurial to colorize output to your terminal')
151
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)')
155
156 self.prompt_native_extension(c, 'mq',
157 'Would you like to activate the mq extension to manage patches')
158
159 self.prompt_external_extension(c, 'bzexport', BZEXPORT_INFO)
160
161 if 'mq' in c.extensions:
162 self.prompt_external_extension(c, 'mqext', MQEXT_INFO,
163 os.path.join(self.ext_dir, 'mqext'))
164
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...')
172
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')
178
179 self.prompt_external_extension(c, 'qimportbz', QIMPORTBZ_INFO)
180
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('')
188
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...')
196
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)
205
206 c.add_mozilla_host_fingerprints()
207
208 b = StringIO()
209 c.write(b)
210 new_lines = [line.rstrip() for line in b.getvalue().splitlines()]
211 old_lines = []
212
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()]
217
218 diff = list(difflib.unified_diff(old_lines, new_lines,
219 'hgrc.old', 'hgrc.new'))
220
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('')
229
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
239
240 print(FINISHED)
241 return 0
242
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)
250
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)
264
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)
271
272 def update_git_repo(self, git, url, dest, ref, msg):
273 return self._update_repo(git, url, dest, ref, msg, update_git_repo)
274
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('')
283
284 def _prompt(self, msg):
285 print(msg)
286
287 while True:
288 response = raw_input()
289
290 if response:
291 return response
292
293 print('You must type something!')
294
295 def _prompt_yn(self, msg):
296 print('%s? [Y/n]' % msg)
297
298 while True:
299 choice = raw_input().lower().strip()
300
301 if not choice:
302 return True
303
304 if choice in ('y', 'yes'):
305 return True
306
307 if choice in ('n', 'no'):
308 return False
309
310 print('Must reply with one of {yes, no, y, n}.')

mercurial