##// END OF EJS Templates
pager: flush outputs before firing pager process...
Yuya Nishihara -
r31490:8122cc5c default
parent child Browse files
Show More
@@ -1,742 +1,741 b''
1 # patchbomb.py - sending Mercurial changesets as patch emails
1 # patchbomb.py - sending Mercurial changesets as patch emails
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to send changesets as (a series of) patch emails
8 '''command to send changesets as (a series of) patch emails
9
9
10 The series is started off with a "[PATCH 0 of N]" introduction, which
10 The series is started off with a "[PATCH 0 of N]" introduction, which
11 describes the series as a whole.
11 describes the series as a whole.
12
12
13 Each patch email has a Subject line of "[PATCH M of N] ...", using the
13 Each patch email has a Subject line of "[PATCH M of N] ...", using the
14 first line of the changeset description as the subject text. The
14 first line of the changeset description as the subject text. The
15 message contains two or three body parts:
15 message contains two or three body parts:
16
16
17 - The changeset description.
17 - The changeset description.
18 - [Optional] The result of running diffstat on the patch.
18 - [Optional] The result of running diffstat on the patch.
19 - The patch itself, as generated by :hg:`export`.
19 - The patch itself, as generated by :hg:`export`.
20
20
21 Each message refers to the first in the series using the In-Reply-To
21 Each message refers to the first in the series using the In-Reply-To
22 and References headers, so they will show up as a sequence in threaded
22 and References headers, so they will show up as a sequence in threaded
23 mail and news readers, and in mail archives.
23 mail and news readers, and in mail archives.
24
24
25 To configure other defaults, add a section like this to your
25 To configure other defaults, add a section like this to your
26 configuration file::
26 configuration file::
27
27
28 [email]
28 [email]
29 from = My Name <my@email>
29 from = My Name <my@email>
30 to = recipient1, recipient2, ...
30 to = recipient1, recipient2, ...
31 cc = cc1, cc2, ...
31 cc = cc1, cc2, ...
32 bcc = bcc1, bcc2, ...
32 bcc = bcc1, bcc2, ...
33 reply-to = address1, address2, ...
33 reply-to = address1, address2, ...
34
34
35 Use ``[patchbomb]`` as configuration section name if you need to
35 Use ``[patchbomb]`` as configuration section name if you need to
36 override global ``[email]`` address settings.
36 override global ``[email]`` address settings.
37
37
38 Then you can use the :hg:`email` command to mail a series of
38 Then you can use the :hg:`email` command to mail a series of
39 changesets as a patchbomb.
39 changesets as a patchbomb.
40
40
41 You can also either configure the method option in the email section
41 You can also either configure the method option in the email section
42 to be a sendmail compatible mailer or fill out the [smtp] section so
42 to be a sendmail compatible mailer or fill out the [smtp] section so
43 that the patchbomb extension can automatically send patchbombs
43 that the patchbomb extension can automatically send patchbombs
44 directly from the commandline. See the [email] and [smtp] sections in
44 directly from the commandline. See the [email] and [smtp] sections in
45 hgrc(5) for details.
45 hgrc(5) for details.
46
46
47 By default, :hg:`email` will prompt for a ``To`` or ``CC`` header if
47 By default, :hg:`email` will prompt for a ``To`` or ``CC`` header if
48 you do not supply one via configuration or the command line. You can
48 you do not supply one via configuration or the command line. You can
49 override this to never prompt by configuring an empty value::
49 override this to never prompt by configuring an empty value::
50
50
51 [email]
51 [email]
52 cc =
52 cc =
53
53
54 You can control the default inclusion of an introduction message with the
54 You can control the default inclusion of an introduction message with the
55 ``patchbomb.intro`` configuration option. The configuration is always
55 ``patchbomb.intro`` configuration option. The configuration is always
56 overwritten by command line flags like --intro and --desc::
56 overwritten by command line flags like --intro and --desc::
57
57
58 [patchbomb]
58 [patchbomb]
59 intro=auto # include introduction message if more than 1 patch (default)
59 intro=auto # include introduction message if more than 1 patch (default)
60 intro=never # never include an introduction message
60 intro=never # never include an introduction message
61 intro=always # always include an introduction message
61 intro=always # always include an introduction message
62
62
63 You can specify a template for flags to be added in subject prefixes. Flags
63 You can specify a template for flags to be added in subject prefixes. Flags
64 specified by --flag option are exported as ``{flags}`` keyword::
64 specified by --flag option are exported as ``{flags}`` keyword::
65
65
66 [patchbomb]
66 [patchbomb]
67 flagtemplate = "{separate(' ',
67 flagtemplate = "{separate(' ',
68 ifeq(branch, 'default', '', branch|upper),
68 ifeq(branch, 'default', '', branch|upper),
69 flags)}"
69 flags)}"
70
70
71 You can set patchbomb to always ask for confirmation by setting
71 You can set patchbomb to always ask for confirmation by setting
72 ``patchbomb.confirm`` to true.
72 ``patchbomb.confirm`` to true.
73 '''
73 '''
74 from __future__ import absolute_import
74 from __future__ import absolute_import
75
75
76 import email as emailmod
76 import email as emailmod
77 import errno
77 import errno
78 import os
78 import os
79 import socket
79 import socket
80 import tempfile
80 import tempfile
81
81
82 from mercurial.i18n import _
82 from mercurial.i18n import _
83 from mercurial import (
83 from mercurial import (
84 cmdutil,
84 cmdutil,
85 commands,
85 commands,
86 error,
86 error,
87 formatter,
87 formatter,
88 hg,
88 hg,
89 mail,
89 mail,
90 node as nodemod,
90 node as nodemod,
91 patch,
91 patch,
92 scmutil,
92 scmutil,
93 templater,
93 templater,
94 util,
94 util,
95 )
95 )
96 stringio = util.stringio
96 stringio = util.stringio
97
97
98 cmdtable = {}
98 cmdtable = {}
99 command = cmdutil.command(cmdtable)
99 command = cmdutil.command(cmdtable)
100 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
100 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
101 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
101 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
102 # be specifying the version(s) of Mercurial they are tested with, or
102 # be specifying the version(s) of Mercurial they are tested with, or
103 # leave the attribute unspecified.
103 # leave the attribute unspecified.
104 testedwith = 'ships-with-hg-core'
104 testedwith = 'ships-with-hg-core'
105
105
106 def _addpullheader(seq, ctx):
106 def _addpullheader(seq, ctx):
107 """Add a header pointing to a public URL where the changeset is available
107 """Add a header pointing to a public URL where the changeset is available
108 """
108 """
109 repo = ctx.repo()
109 repo = ctx.repo()
110 # experimental config: patchbomb.publicurl
110 # experimental config: patchbomb.publicurl
111 # waiting for some logic that check that the changeset are available on the
111 # waiting for some logic that check that the changeset are available on the
112 # destination before patchbombing anything.
112 # destination before patchbombing anything.
113 pullurl = repo.ui.config('patchbomb', 'publicurl')
113 pullurl = repo.ui.config('patchbomb', 'publicurl')
114 if pullurl is not None:
114 if pullurl is not None:
115 return ('Available At %s\n'
115 return ('Available At %s\n'
116 '# hg pull %s -r %s' % (pullurl, pullurl, ctx))
116 '# hg pull %s -r %s' % (pullurl, pullurl, ctx))
117 return None
117 return None
118
118
119 def uisetup(ui):
119 def uisetup(ui):
120 cmdutil.extraexport.append('pullurl')
120 cmdutil.extraexport.append('pullurl')
121 cmdutil.extraexportmap['pullurl'] = _addpullheader
121 cmdutil.extraexportmap['pullurl'] = _addpullheader
122
122
123
123
124 def prompt(ui, prompt, default=None, rest=':'):
124 def prompt(ui, prompt, default=None, rest=':'):
125 if default:
125 if default:
126 prompt += ' [%s]' % default
126 prompt += ' [%s]' % default
127 return ui.prompt(prompt + rest, default)
127 return ui.prompt(prompt + rest, default)
128
128
129 def introwanted(ui, opts, number):
129 def introwanted(ui, opts, number):
130 '''is an introductory message apparently wanted?'''
130 '''is an introductory message apparently wanted?'''
131 introconfig = ui.config('patchbomb', 'intro', 'auto')
131 introconfig = ui.config('patchbomb', 'intro', 'auto')
132 if opts.get('intro') or opts.get('desc'):
132 if opts.get('intro') or opts.get('desc'):
133 intro = True
133 intro = True
134 elif introconfig == 'always':
134 elif introconfig == 'always':
135 intro = True
135 intro = True
136 elif introconfig == 'never':
136 elif introconfig == 'never':
137 intro = False
137 intro = False
138 elif introconfig == 'auto':
138 elif introconfig == 'auto':
139 intro = 1 < number
139 intro = 1 < number
140 else:
140 else:
141 ui.write_err(_('warning: invalid patchbomb.intro value "%s"\n')
141 ui.write_err(_('warning: invalid patchbomb.intro value "%s"\n')
142 % introconfig)
142 % introconfig)
143 ui.write_err(_('(should be one of always, never, auto)\n'))
143 ui.write_err(_('(should be one of always, never, auto)\n'))
144 intro = 1 < number
144 intro = 1 < number
145 return intro
145 return intro
146
146
147 def _formatflags(ui, repo, rev, flags):
147 def _formatflags(ui, repo, rev, flags):
148 """build flag string optionally by template"""
148 """build flag string optionally by template"""
149 tmpl = ui.config('patchbomb', 'flagtemplate')
149 tmpl = ui.config('patchbomb', 'flagtemplate')
150 if not tmpl:
150 if not tmpl:
151 return ' '.join(flags)
151 return ' '.join(flags)
152 out = util.stringio()
152 out = util.stringio()
153 opts = {'template': templater.unquotestring(tmpl)}
153 opts = {'template': templater.unquotestring(tmpl)}
154 with formatter.templateformatter(ui, out, 'patchbombflag', opts) as fm:
154 with formatter.templateformatter(ui, out, 'patchbombflag', opts) as fm:
155 fm.startitem()
155 fm.startitem()
156 fm.context(ctx=repo[rev])
156 fm.context(ctx=repo[rev])
157 fm.write('flags', '%s', fm.formatlist(flags, name='flag'))
157 fm.write('flags', '%s', fm.formatlist(flags, name='flag'))
158 return out.getvalue()
158 return out.getvalue()
159
159
160 def _formatprefix(ui, repo, rev, flags, idx, total, numbered):
160 def _formatprefix(ui, repo, rev, flags, idx, total, numbered):
161 """build prefix to patch subject"""
161 """build prefix to patch subject"""
162 flag = _formatflags(ui, repo, rev, flags)
162 flag = _formatflags(ui, repo, rev, flags)
163 if flag:
163 if flag:
164 flag = ' ' + flag
164 flag = ' ' + flag
165
165
166 if not numbered:
166 if not numbered:
167 return '[PATCH%s]' % flag
167 return '[PATCH%s]' % flag
168 else:
168 else:
169 tlen = len(str(total))
169 tlen = len(str(total))
170 return '[PATCH %0*d of %d%s]' % (tlen, idx, total, flag)
170 return '[PATCH %0*d of %d%s]' % (tlen, idx, total, flag)
171
171
172 def makepatch(ui, repo, rev, patchlines, opts, _charsets, idx, total, numbered,
172 def makepatch(ui, repo, rev, patchlines, opts, _charsets, idx, total, numbered,
173 patchname=None):
173 patchname=None):
174
174
175 desc = []
175 desc = []
176 node = None
176 node = None
177 body = ''
177 body = ''
178
178
179 for line in patchlines:
179 for line in patchlines:
180 if line.startswith('#'):
180 if line.startswith('#'):
181 if line.startswith('# Node ID'):
181 if line.startswith('# Node ID'):
182 node = line.split()[-1]
182 node = line.split()[-1]
183 continue
183 continue
184 if line.startswith('diff -r') or line.startswith('diff --git'):
184 if line.startswith('diff -r') or line.startswith('diff --git'):
185 break
185 break
186 desc.append(line)
186 desc.append(line)
187
187
188 if not patchname and not node:
188 if not patchname and not node:
189 raise ValueError
189 raise ValueError
190
190
191 if opts.get('attach') and not opts.get('body'):
191 if opts.get('attach') and not opts.get('body'):
192 body = ('\n'.join(desc[1:]).strip() or
192 body = ('\n'.join(desc[1:]).strip() or
193 'Patch subject is complete summary.')
193 'Patch subject is complete summary.')
194 body += '\n\n\n'
194 body += '\n\n\n'
195
195
196 if opts.get('plain'):
196 if opts.get('plain'):
197 while patchlines and patchlines[0].startswith('# '):
197 while patchlines and patchlines[0].startswith('# '):
198 patchlines.pop(0)
198 patchlines.pop(0)
199 if patchlines:
199 if patchlines:
200 patchlines.pop(0)
200 patchlines.pop(0)
201 while patchlines and not patchlines[0].strip():
201 while patchlines and not patchlines[0].strip():
202 patchlines.pop(0)
202 patchlines.pop(0)
203
203
204 ds = patch.diffstat(patchlines)
204 ds = patch.diffstat(patchlines)
205 if opts.get('diffstat'):
205 if opts.get('diffstat'):
206 body += ds + '\n\n'
206 body += ds + '\n\n'
207
207
208 addattachment = opts.get('attach') or opts.get('inline')
208 addattachment = opts.get('attach') or opts.get('inline')
209 if not addattachment or opts.get('body'):
209 if not addattachment or opts.get('body'):
210 body += '\n'.join(patchlines)
210 body += '\n'.join(patchlines)
211
211
212 if addattachment:
212 if addattachment:
213 msg = emailmod.MIMEMultipart.MIMEMultipart()
213 msg = emailmod.MIMEMultipart.MIMEMultipart()
214 if body:
214 if body:
215 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
215 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
216 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch',
216 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch',
217 opts.get('test'))
217 opts.get('test'))
218 binnode = nodemod.bin(node)
218 binnode = nodemod.bin(node)
219 # if node is mq patch, it will have the patch file's name as a tag
219 # if node is mq patch, it will have the patch file's name as a tag
220 if not patchname:
220 if not patchname:
221 patchtags = [t for t in repo.nodetags(binnode)
221 patchtags = [t for t in repo.nodetags(binnode)
222 if t.endswith('.patch') or t.endswith('.diff')]
222 if t.endswith('.patch') or t.endswith('.diff')]
223 if patchtags:
223 if patchtags:
224 patchname = patchtags[0]
224 patchname = patchtags[0]
225 elif total > 1:
225 elif total > 1:
226 patchname = cmdutil.makefilename(repo, '%b-%n.patch',
226 patchname = cmdutil.makefilename(repo, '%b-%n.patch',
227 binnode, seqno=idx,
227 binnode, seqno=idx,
228 total=total)
228 total=total)
229 else:
229 else:
230 patchname = cmdutil.makefilename(repo, '%b.patch', binnode)
230 patchname = cmdutil.makefilename(repo, '%b.patch', binnode)
231 disposition = 'inline'
231 disposition = 'inline'
232 if opts.get('attach'):
232 if opts.get('attach'):
233 disposition = 'attachment'
233 disposition = 'attachment'
234 p['Content-Disposition'] = disposition + '; filename=' + patchname
234 p['Content-Disposition'] = disposition + '; filename=' + patchname
235 msg.attach(p)
235 msg.attach(p)
236 else:
236 else:
237 msg = mail.mimetextpatch(body, display=opts.get('test'))
237 msg = mail.mimetextpatch(body, display=opts.get('test'))
238
238
239 prefix = _formatprefix(ui, repo, rev, opts.get('flag'), idx, total,
239 prefix = _formatprefix(ui, repo, rev, opts.get('flag'), idx, total,
240 numbered)
240 numbered)
241 subj = desc[0].strip().rstrip('. ')
241 subj = desc[0].strip().rstrip('. ')
242 if not numbered:
242 if not numbered:
243 subj = ' '.join([prefix, opts.get('subject') or subj])
243 subj = ' '.join([prefix, opts.get('subject') or subj])
244 else:
244 else:
245 subj = ' '.join([prefix, subj])
245 subj = ' '.join([prefix, subj])
246 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
246 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
247 msg['X-Mercurial-Node'] = node
247 msg['X-Mercurial-Node'] = node
248 msg['X-Mercurial-Series-Index'] = '%i' % idx
248 msg['X-Mercurial-Series-Index'] = '%i' % idx
249 msg['X-Mercurial-Series-Total'] = '%i' % total
249 msg['X-Mercurial-Series-Total'] = '%i' % total
250 return msg, subj, ds
250 return msg, subj, ds
251
251
252 def _getpatches(repo, revs, **opts):
252 def _getpatches(repo, revs, **opts):
253 """return a list of patches for a list of revisions
253 """return a list of patches for a list of revisions
254
254
255 Each patch in the list is itself a list of lines.
255 Each patch in the list is itself a list of lines.
256 """
256 """
257 ui = repo.ui
257 ui = repo.ui
258 prev = repo['.'].rev()
258 prev = repo['.'].rev()
259 for r in revs:
259 for r in revs:
260 if r == prev and (repo[None].files() or repo[None].deleted()):
260 if r == prev and (repo[None].files() or repo[None].deleted()):
261 ui.warn(_('warning: working directory has '
261 ui.warn(_('warning: working directory has '
262 'uncommitted changes\n'))
262 'uncommitted changes\n'))
263 output = stringio()
263 output = stringio()
264 cmdutil.export(repo, [r], fp=output,
264 cmdutil.export(repo, [r], fp=output,
265 opts=patch.difffeatureopts(ui, opts, git=True))
265 opts=patch.difffeatureopts(ui, opts, git=True))
266 yield output.getvalue().split('\n')
266 yield output.getvalue().split('\n')
267 def _getbundle(repo, dest, **opts):
267 def _getbundle(repo, dest, **opts):
268 """return a bundle containing changesets missing in "dest"
268 """return a bundle containing changesets missing in "dest"
269
269
270 The `opts` keyword-arguments are the same as the one accepted by the
270 The `opts` keyword-arguments are the same as the one accepted by the
271 `bundle` command.
271 `bundle` command.
272
272
273 The bundle is a returned as a single in-memory binary blob.
273 The bundle is a returned as a single in-memory binary blob.
274 """
274 """
275 ui = repo.ui
275 ui = repo.ui
276 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
276 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
277 tmpfn = os.path.join(tmpdir, 'bundle')
277 tmpfn = os.path.join(tmpdir, 'bundle')
278 btype = ui.config('patchbomb', 'bundletype')
278 btype = ui.config('patchbomb', 'bundletype')
279 if btype:
279 if btype:
280 opts['type'] = btype
280 opts['type'] = btype
281 try:
281 try:
282 commands.bundle(ui, repo, tmpfn, dest, **opts)
282 commands.bundle(ui, repo, tmpfn, dest, **opts)
283 return util.readfile(tmpfn)
283 return util.readfile(tmpfn)
284 finally:
284 finally:
285 try:
285 try:
286 os.unlink(tmpfn)
286 os.unlink(tmpfn)
287 except OSError:
287 except OSError:
288 pass
288 pass
289 os.rmdir(tmpdir)
289 os.rmdir(tmpdir)
290
290
291 def _getdescription(repo, defaultbody, sender, **opts):
291 def _getdescription(repo, defaultbody, sender, **opts):
292 """obtain the body of the introduction message and return it
292 """obtain the body of the introduction message and return it
293
293
294 This is also used for the body of email with an attached bundle.
294 This is also used for the body of email with an attached bundle.
295
295
296 The body can be obtained either from the command line option or entered by
296 The body can be obtained either from the command line option or entered by
297 the user through the editor.
297 the user through the editor.
298 """
298 """
299 ui = repo.ui
299 ui = repo.ui
300 if opts.get('desc'):
300 if opts.get('desc'):
301 body = open(opts.get('desc')).read()
301 body = open(opts.get('desc')).read()
302 else:
302 else:
303 ui.write(_('\nWrite the introductory message for the '
303 ui.write(_('\nWrite the introductory message for the '
304 'patch series.\n\n'))
304 'patch series.\n\n'))
305 body = ui.edit(defaultbody, sender, repopath=repo.path)
305 body = ui.edit(defaultbody, sender, repopath=repo.path)
306 # Save series description in case sendmail fails
306 # Save series description in case sendmail fails
307 msgfile = repo.vfs('last-email.txt', 'wb')
307 msgfile = repo.vfs('last-email.txt', 'wb')
308 msgfile.write(body)
308 msgfile.write(body)
309 msgfile.close()
309 msgfile.close()
310 return body
310 return body
311
311
312 def _getbundlemsgs(repo, sender, bundle, **opts):
312 def _getbundlemsgs(repo, sender, bundle, **opts):
313 """Get the full email for sending a given bundle
313 """Get the full email for sending a given bundle
314
314
315 This function returns a list of "email" tuples (subject, content, None).
315 This function returns a list of "email" tuples (subject, content, None).
316 The list is always one message long in that case.
316 The list is always one message long in that case.
317 """
317 """
318 ui = repo.ui
318 ui = repo.ui
319 _charsets = mail._charsets(ui)
319 _charsets = mail._charsets(ui)
320 subj = (opts.get('subject')
320 subj = (opts.get('subject')
321 or prompt(ui, 'Subject:', 'A bundle for your repository'))
321 or prompt(ui, 'Subject:', 'A bundle for your repository'))
322
322
323 body = _getdescription(repo, '', sender, **opts)
323 body = _getdescription(repo, '', sender, **opts)
324 msg = emailmod.MIMEMultipart.MIMEMultipart()
324 msg = emailmod.MIMEMultipart.MIMEMultipart()
325 if body:
325 if body:
326 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
326 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
327 datapart = emailmod.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
327 datapart = emailmod.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
328 datapart.set_payload(bundle)
328 datapart.set_payload(bundle)
329 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
329 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
330 datapart.add_header('Content-Disposition', 'attachment',
330 datapart.add_header('Content-Disposition', 'attachment',
331 filename=bundlename)
331 filename=bundlename)
332 emailmod.Encoders.encode_base64(datapart)
332 emailmod.Encoders.encode_base64(datapart)
333 msg.attach(datapart)
333 msg.attach(datapart)
334 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
334 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
335 return [(msg, subj, None)]
335 return [(msg, subj, None)]
336
336
337 def _makeintro(repo, sender, revs, patches, **opts):
337 def _makeintro(repo, sender, revs, patches, **opts):
338 """make an introduction email, asking the user for content if needed
338 """make an introduction email, asking the user for content if needed
339
339
340 email is returned as (subject, body, cumulative-diffstat)"""
340 email is returned as (subject, body, cumulative-diffstat)"""
341 ui = repo.ui
341 ui = repo.ui
342 _charsets = mail._charsets(ui)
342 _charsets = mail._charsets(ui)
343
343
344 # use the last revision which is likely to be a bookmarked head
344 # use the last revision which is likely to be a bookmarked head
345 prefix = _formatprefix(ui, repo, revs.last(), opts.get('flag'),
345 prefix = _formatprefix(ui, repo, revs.last(), opts.get('flag'),
346 0, len(patches), numbered=True)
346 0, len(patches), numbered=True)
347 subj = (opts.get('subject') or
347 subj = (opts.get('subject') or
348 prompt(ui, '(optional) Subject: ', rest=prefix, default=''))
348 prompt(ui, '(optional) Subject: ', rest=prefix, default=''))
349 if not subj:
349 if not subj:
350 return None # skip intro if the user doesn't bother
350 return None # skip intro if the user doesn't bother
351
351
352 subj = prefix + ' ' + subj
352 subj = prefix + ' ' + subj
353
353
354 body = ''
354 body = ''
355 if opts.get('diffstat'):
355 if opts.get('diffstat'):
356 # generate a cumulative diffstat of the whole patch series
356 # generate a cumulative diffstat of the whole patch series
357 diffstat = patch.diffstat(sum(patches, []))
357 diffstat = patch.diffstat(sum(patches, []))
358 body = '\n' + diffstat
358 body = '\n' + diffstat
359 else:
359 else:
360 diffstat = None
360 diffstat = None
361
361
362 body = _getdescription(repo, body, sender, **opts)
362 body = _getdescription(repo, body, sender, **opts)
363 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
363 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
364 msg['Subject'] = mail.headencode(ui, subj, _charsets,
364 msg['Subject'] = mail.headencode(ui, subj, _charsets,
365 opts.get('test'))
365 opts.get('test'))
366 return (msg, subj, diffstat)
366 return (msg, subj, diffstat)
367
367
368 def _getpatchmsgs(repo, sender, revs, patchnames=None, **opts):
368 def _getpatchmsgs(repo, sender, revs, patchnames=None, **opts):
369 """return a list of emails from a list of patches
369 """return a list of emails from a list of patches
370
370
371 This involves introduction message creation if necessary.
371 This involves introduction message creation if necessary.
372
372
373 This function returns a list of "email" tuples (subject, content, None).
373 This function returns a list of "email" tuples (subject, content, None).
374 """
374 """
375 ui = repo.ui
375 ui = repo.ui
376 _charsets = mail._charsets(ui)
376 _charsets = mail._charsets(ui)
377 patches = list(_getpatches(repo, revs, **opts))
377 patches = list(_getpatches(repo, revs, **opts))
378 msgs = []
378 msgs = []
379
379
380 ui.write(_('this patch series consists of %d patches.\n\n')
380 ui.write(_('this patch series consists of %d patches.\n\n')
381 % len(patches))
381 % len(patches))
382
382
383 # build the intro message, or skip it if the user declines
383 # build the intro message, or skip it if the user declines
384 if introwanted(ui, opts, len(patches)):
384 if introwanted(ui, opts, len(patches)):
385 msg = _makeintro(repo, sender, revs, patches, **opts)
385 msg = _makeintro(repo, sender, revs, patches, **opts)
386 if msg:
386 if msg:
387 msgs.append(msg)
387 msgs.append(msg)
388
388
389 # are we going to send more than one message?
389 # are we going to send more than one message?
390 numbered = len(msgs) + len(patches) > 1
390 numbered = len(msgs) + len(patches) > 1
391
391
392 # now generate the actual patch messages
392 # now generate the actual patch messages
393 name = None
393 name = None
394 assert len(revs) == len(patches)
394 assert len(revs) == len(patches)
395 for i, (r, p) in enumerate(zip(revs, patches)):
395 for i, (r, p) in enumerate(zip(revs, patches)):
396 if patchnames:
396 if patchnames:
397 name = patchnames[i]
397 name = patchnames[i]
398 msg = makepatch(ui, repo, r, p, opts, _charsets, i + 1,
398 msg = makepatch(ui, repo, r, p, opts, _charsets, i + 1,
399 len(patches), numbered, name)
399 len(patches), numbered, name)
400 msgs.append(msg)
400 msgs.append(msg)
401
401
402 return msgs
402 return msgs
403
403
404 def _getoutgoing(repo, dest, revs):
404 def _getoutgoing(repo, dest, revs):
405 '''Return the revisions present locally but not in dest'''
405 '''Return the revisions present locally but not in dest'''
406 ui = repo.ui
406 ui = repo.ui
407 url = ui.expandpath(dest or 'default-push', dest or 'default')
407 url = ui.expandpath(dest or 'default-push', dest or 'default')
408 url = hg.parseurl(url)[0]
408 url = hg.parseurl(url)[0]
409 ui.status(_('comparing with %s\n') % util.hidepassword(url))
409 ui.status(_('comparing with %s\n') % util.hidepassword(url))
410
410
411 revs = [r for r in revs if r >= 0]
411 revs = [r for r in revs if r >= 0]
412 if not revs:
412 if not revs:
413 revs = [len(repo) - 1]
413 revs = [len(repo) - 1]
414 revs = repo.revs('outgoing(%s) and ::%ld', dest or '', revs)
414 revs = repo.revs('outgoing(%s) and ::%ld', dest or '', revs)
415 if not revs:
415 if not revs:
416 ui.status(_("no changes found\n"))
416 ui.status(_("no changes found\n"))
417 return revs
417 return revs
418
418
419 emailopts = [
419 emailopts = [
420 ('', 'body', None, _('send patches as inline message text (default)')),
420 ('', 'body', None, _('send patches as inline message text (default)')),
421 ('a', 'attach', None, _('send patches as attachments')),
421 ('a', 'attach', None, _('send patches as attachments')),
422 ('i', 'inline', None, _('send patches as inline attachments')),
422 ('i', 'inline', None, _('send patches as inline attachments')),
423 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
423 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
424 ('c', 'cc', [], _('email addresses of copy recipients')),
424 ('c', 'cc', [], _('email addresses of copy recipients')),
425 ('', 'confirm', None, _('ask for confirmation before sending')),
425 ('', 'confirm', None, _('ask for confirmation before sending')),
426 ('d', 'diffstat', None, _('add diffstat output to messages')),
426 ('d', 'diffstat', None, _('add diffstat output to messages')),
427 ('', 'date', '', _('use the given date as the sending date')),
427 ('', 'date', '', _('use the given date as the sending date')),
428 ('', 'desc', '', _('use the given file as the series description')),
428 ('', 'desc', '', _('use the given file as the series description')),
429 ('f', 'from', '', _('email address of sender')),
429 ('f', 'from', '', _('email address of sender')),
430 ('n', 'test', None, _('print messages that would be sent')),
430 ('n', 'test', None, _('print messages that would be sent')),
431 ('m', 'mbox', '', _('write messages to mbox file instead of sending them')),
431 ('m', 'mbox', '', _('write messages to mbox file instead of sending them')),
432 ('', 'reply-to', [], _('email addresses replies should be sent to')),
432 ('', 'reply-to', [], _('email addresses replies should be sent to')),
433 ('s', 'subject', '', _('subject of first message (intro or single patch)')),
433 ('s', 'subject', '', _('subject of first message (intro or single patch)')),
434 ('', 'in-reply-to', '', _('message identifier to reply to')),
434 ('', 'in-reply-to', '', _('message identifier to reply to')),
435 ('', 'flag', [], _('flags to add in subject prefixes')),
435 ('', 'flag', [], _('flags to add in subject prefixes')),
436 ('t', 'to', [], _('email addresses of recipients'))]
436 ('t', 'to', [], _('email addresses of recipients'))]
437
437
438 @command('email',
438 @command('email',
439 [('g', 'git', None, _('use git extended diff format')),
439 [('g', 'git', None, _('use git extended diff format')),
440 ('', 'plain', None, _('omit hg patch header')),
440 ('', 'plain', None, _('omit hg patch header')),
441 ('o', 'outgoing', None,
441 ('o', 'outgoing', None,
442 _('send changes not found in the target repository')),
442 _('send changes not found in the target repository')),
443 ('b', 'bundle', None, _('send changes not in target as a binary bundle')),
443 ('b', 'bundle', None, _('send changes not in target as a binary bundle')),
444 ('', 'bundlename', 'bundle',
444 ('', 'bundlename', 'bundle',
445 _('name of the bundle attachment file'), _('NAME')),
445 _('name of the bundle attachment file'), _('NAME')),
446 ('r', 'rev', [], _('a revision to send'), _('REV')),
446 ('r', 'rev', [], _('a revision to send'), _('REV')),
447 ('', 'force', None, _('run even when remote repository is unrelated '
447 ('', 'force', None, _('run even when remote repository is unrelated '
448 '(with -b/--bundle)')),
448 '(with -b/--bundle)')),
449 ('', 'base', [], _('a base changeset to specify instead of a destination '
449 ('', 'base', [], _('a base changeset to specify instead of a destination '
450 '(with -b/--bundle)'), _('REV')),
450 '(with -b/--bundle)'), _('REV')),
451 ('', 'intro', None, _('send an introduction email for a single patch')),
451 ('', 'intro', None, _('send an introduction email for a single patch')),
452 ] + emailopts + commands.remoteopts,
452 ] + emailopts + commands.remoteopts,
453 _('hg email [OPTION]... [DEST]...'))
453 _('hg email [OPTION]... [DEST]...'))
454 def email(ui, repo, *revs, **opts):
454 def email(ui, repo, *revs, **opts):
455 '''send changesets by email
455 '''send changesets by email
456
456
457 By default, diffs are sent in the format generated by
457 By default, diffs are sent in the format generated by
458 :hg:`export`, one per message. The series starts with a "[PATCH 0
458 :hg:`export`, one per message. The series starts with a "[PATCH 0
459 of N]" introduction, which describes the series as a whole.
459 of N]" introduction, which describes the series as a whole.
460
460
461 Each patch email has a Subject line of "[PATCH M of N] ...", using
461 Each patch email has a Subject line of "[PATCH M of N] ...", using
462 the first line of the changeset description as the subject text.
462 the first line of the changeset description as the subject text.
463 The message contains two or three parts. First, the changeset
463 The message contains two or three parts. First, the changeset
464 description.
464 description.
465
465
466 With the -d/--diffstat option, if the diffstat program is
466 With the -d/--diffstat option, if the diffstat program is
467 installed, the result of running diffstat on the patch is inserted.
467 installed, the result of running diffstat on the patch is inserted.
468
468
469 Finally, the patch itself, as generated by :hg:`export`.
469 Finally, the patch itself, as generated by :hg:`export`.
470
470
471 With the -d/--diffstat or --confirm options, you will be presented
471 With the -d/--diffstat or --confirm options, you will be presented
472 with a final summary of all messages and asked for confirmation before
472 with a final summary of all messages and asked for confirmation before
473 the messages are sent.
473 the messages are sent.
474
474
475 By default the patch is included as text in the email body for
475 By default the patch is included as text in the email body for
476 easy reviewing. Using the -a/--attach option will instead create
476 easy reviewing. Using the -a/--attach option will instead create
477 an attachment for the patch. With -i/--inline an inline attachment
477 an attachment for the patch. With -i/--inline an inline attachment
478 will be created. You can include a patch both as text in the email
478 will be created. You can include a patch both as text in the email
479 body and as a regular or an inline attachment by combining the
479 body and as a regular or an inline attachment by combining the
480 -a/--attach or -i/--inline with the --body option.
480 -a/--attach or -i/--inline with the --body option.
481
481
482 With -o/--outgoing, emails will be generated for patches not found
482 With -o/--outgoing, emails will be generated for patches not found
483 in the destination repository (or only those which are ancestors
483 in the destination repository (or only those which are ancestors
484 of the specified revisions if any are provided)
484 of the specified revisions if any are provided)
485
485
486 With -b/--bundle, changesets are selected as for --outgoing, but a
486 With -b/--bundle, changesets are selected as for --outgoing, but a
487 single email containing a binary Mercurial bundle as an attachment
487 single email containing a binary Mercurial bundle as an attachment
488 will be sent. Use the ``patchbomb.bundletype`` config option to
488 will be sent. Use the ``patchbomb.bundletype`` config option to
489 control the bundle type as with :hg:`bundle --type`.
489 control the bundle type as with :hg:`bundle --type`.
490
490
491 With -m/--mbox, instead of previewing each patchbomb message in a
491 With -m/--mbox, instead of previewing each patchbomb message in a
492 pager or sending the messages directly, it will create a UNIX
492 pager or sending the messages directly, it will create a UNIX
493 mailbox file with the patch emails. This mailbox file can be
493 mailbox file with the patch emails. This mailbox file can be
494 previewed with any mail user agent which supports UNIX mbox
494 previewed with any mail user agent which supports UNIX mbox
495 files.
495 files.
496
496
497 With -n/--test, all steps will run, but mail will not be sent.
497 With -n/--test, all steps will run, but mail will not be sent.
498 You will be prompted for an email recipient address, a subject and
498 You will be prompted for an email recipient address, a subject and
499 an introductory message describing the patches of your patchbomb.
499 an introductory message describing the patches of your patchbomb.
500 Then when all is done, patchbomb messages are displayed.
500 Then when all is done, patchbomb messages are displayed.
501
501
502 In case email sending fails, you will find a backup of your series
502 In case email sending fails, you will find a backup of your series
503 introductory message in ``.hg/last-email.txt``.
503 introductory message in ``.hg/last-email.txt``.
504
504
505 The default behavior of this command can be customized through
505 The default behavior of this command can be customized through
506 configuration. (See :hg:`help patchbomb` for details)
506 configuration. (See :hg:`help patchbomb` for details)
507
507
508 Examples::
508 Examples::
509
509
510 hg email -r 3000 # send patch 3000 only
510 hg email -r 3000 # send patch 3000 only
511 hg email -r 3000 -r 3001 # send patches 3000 and 3001
511 hg email -r 3000 -r 3001 # send patches 3000 and 3001
512 hg email -r 3000:3005 # send patches 3000 through 3005
512 hg email -r 3000:3005 # send patches 3000 through 3005
513 hg email 3000 # send patch 3000 (deprecated)
513 hg email 3000 # send patch 3000 (deprecated)
514
514
515 hg email -o # send all patches not in default
515 hg email -o # send all patches not in default
516 hg email -o DEST # send all patches not in DEST
516 hg email -o DEST # send all patches not in DEST
517 hg email -o -r 3000 # send all ancestors of 3000 not in default
517 hg email -o -r 3000 # send all ancestors of 3000 not in default
518 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
518 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
519
519
520 hg email -b # send bundle of all patches not in default
520 hg email -b # send bundle of all patches not in default
521 hg email -b DEST # send bundle of all patches not in DEST
521 hg email -b DEST # send bundle of all patches not in DEST
522 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
522 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
523 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
523 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
524
524
525 hg email -o -m mbox && # generate an mbox file...
525 hg email -o -m mbox && # generate an mbox file...
526 mutt -R -f mbox # ... and view it with mutt
526 mutt -R -f mbox # ... and view it with mutt
527 hg email -o -m mbox && # generate an mbox file ...
527 hg email -o -m mbox && # generate an mbox file ...
528 formail -s sendmail \\ # ... and use formail to send from the mbox
528 formail -s sendmail \\ # ... and use formail to send from the mbox
529 -bm -t < mbox # ... using sendmail
529 -bm -t < mbox # ... using sendmail
530
530
531 Before using this command, you will need to enable email in your
531 Before using this command, you will need to enable email in your
532 hgrc. See the [email] section in hgrc(5) for details.
532 hgrc. See the [email] section in hgrc(5) for details.
533 '''
533 '''
534
534
535 _charsets = mail._charsets(ui)
535 _charsets = mail._charsets(ui)
536
536
537 bundle = opts.get('bundle')
537 bundle = opts.get('bundle')
538 date = opts.get('date')
538 date = opts.get('date')
539 mbox = opts.get('mbox')
539 mbox = opts.get('mbox')
540 outgoing = opts.get('outgoing')
540 outgoing = opts.get('outgoing')
541 rev = opts.get('rev')
541 rev = opts.get('rev')
542
542
543 if not (opts.get('test') or mbox):
543 if not (opts.get('test') or mbox):
544 # really sending
544 # really sending
545 mail.validateconfig(ui)
545 mail.validateconfig(ui)
546
546
547 if not (revs or rev or outgoing or bundle):
547 if not (revs or rev or outgoing or bundle):
548 raise error.Abort(_('specify at least one changeset with -r or -o'))
548 raise error.Abort(_('specify at least one changeset with -r or -o'))
549
549
550 if outgoing and bundle:
550 if outgoing and bundle:
551 raise error.Abort(_("--outgoing mode always on with --bundle;"
551 raise error.Abort(_("--outgoing mode always on with --bundle;"
552 " do not re-specify --outgoing"))
552 " do not re-specify --outgoing"))
553
553
554 if outgoing or bundle:
554 if outgoing or bundle:
555 if len(revs) > 1:
555 if len(revs) > 1:
556 raise error.Abort(_("too many destinations"))
556 raise error.Abort(_("too many destinations"))
557 if revs:
557 if revs:
558 dest = revs[0]
558 dest = revs[0]
559 else:
559 else:
560 dest = None
560 dest = None
561 revs = []
561 revs = []
562
562
563 if rev:
563 if rev:
564 if revs:
564 if revs:
565 raise error.Abort(_('use only one form to specify the revision'))
565 raise error.Abort(_('use only one form to specify the revision'))
566 revs = rev
566 revs = rev
567
567
568 revs = scmutil.revrange(repo, revs)
568 revs = scmutil.revrange(repo, revs)
569 if outgoing:
569 if outgoing:
570 revs = _getoutgoing(repo, dest, revs)
570 revs = _getoutgoing(repo, dest, revs)
571 if bundle:
571 if bundle:
572 opts['revs'] = [str(r) for r in revs]
572 opts['revs'] = [str(r) for r in revs]
573
573
574 # check if revision exist on the public destination
574 # check if revision exist on the public destination
575 publicurl = repo.ui.config('patchbomb', 'publicurl')
575 publicurl = repo.ui.config('patchbomb', 'publicurl')
576 if publicurl is not None:
576 if publicurl is not None:
577 repo.ui.debug('checking that revision exist in the public repo')
577 repo.ui.debug('checking that revision exist in the public repo')
578 try:
578 try:
579 publicpeer = hg.peer(repo, {}, publicurl)
579 publicpeer = hg.peer(repo, {}, publicurl)
580 except error.RepoError:
580 except error.RepoError:
581 repo.ui.write_err(_('unable to access public repo: %s\n')
581 repo.ui.write_err(_('unable to access public repo: %s\n')
582 % publicurl)
582 % publicurl)
583 raise
583 raise
584 if not publicpeer.capable('known'):
584 if not publicpeer.capable('known'):
585 repo.ui.debug('skipping existence checks: public repo too old')
585 repo.ui.debug('skipping existence checks: public repo too old')
586 else:
586 else:
587 out = [repo[r] for r in revs]
587 out = [repo[r] for r in revs]
588 known = publicpeer.known(h.node() for h in out)
588 known = publicpeer.known(h.node() for h in out)
589 missing = []
589 missing = []
590 for idx, h in enumerate(out):
590 for idx, h in enumerate(out):
591 if not known[idx]:
591 if not known[idx]:
592 missing.append(h)
592 missing.append(h)
593 if missing:
593 if missing:
594 if 1 < len(missing):
594 if 1 < len(missing):
595 msg = _('public "%s" is missing %s and %i others')
595 msg = _('public "%s" is missing %s and %i others')
596 msg %= (publicurl, missing[0], len(missing) - 1)
596 msg %= (publicurl, missing[0], len(missing) - 1)
597 else:
597 else:
598 msg = _('public url %s is missing %s')
598 msg = _('public url %s is missing %s')
599 msg %= (publicurl, missing[0])
599 msg %= (publicurl, missing[0])
600 revhint = ' '.join('-r %s' % h
600 revhint = ' '.join('-r %s' % h
601 for h in repo.set('heads(%ld)', missing))
601 for h in repo.set('heads(%ld)', missing))
602 hint = _("use 'hg push %s %s'") % (publicurl, revhint)
602 hint = _("use 'hg push %s %s'") % (publicurl, revhint)
603 raise error.Abort(msg, hint=hint)
603 raise error.Abort(msg, hint=hint)
604
604
605 # start
605 # start
606 if date:
606 if date:
607 start_time = util.parsedate(date)
607 start_time = util.parsedate(date)
608 else:
608 else:
609 start_time = util.makedate()
609 start_time = util.makedate()
610
610
611 def genmsgid(id):
611 def genmsgid(id):
612 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
612 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
613
613
614 # deprecated config: patchbomb.from
614 # deprecated config: patchbomb.from
615 sender = (opts.get('from') or ui.config('email', 'from') or
615 sender = (opts.get('from') or ui.config('email', 'from') or
616 ui.config('patchbomb', 'from') or
616 ui.config('patchbomb', 'from') or
617 prompt(ui, 'From', ui.username()))
617 prompt(ui, 'From', ui.username()))
618
618
619 if bundle:
619 if bundle:
620 bundledata = _getbundle(repo, dest, **opts)
620 bundledata = _getbundle(repo, dest, **opts)
621 bundleopts = opts.copy()
621 bundleopts = opts.copy()
622 bundleopts.pop('bundle', None) # already processed
622 bundleopts.pop('bundle', None) # already processed
623 msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
623 msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
624 else:
624 else:
625 msgs = _getpatchmsgs(repo, sender, revs, **opts)
625 msgs = _getpatchmsgs(repo, sender, revs, **opts)
626
626
627 showaddrs = []
627 showaddrs = []
628
628
629 def getaddrs(header, ask=False, default=None):
629 def getaddrs(header, ask=False, default=None):
630 configkey = header.lower()
630 configkey = header.lower()
631 opt = header.replace('-', '_').lower()
631 opt = header.replace('-', '_').lower()
632 addrs = opts.get(opt)
632 addrs = opts.get(opt)
633 if addrs:
633 if addrs:
634 showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
634 showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
635 return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))
635 return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))
636
636
637 # not on the command line: fallback to config and then maybe ask
637 # not on the command line: fallback to config and then maybe ask
638 addr = (ui.config('email', configkey) or
638 addr = (ui.config('email', configkey) or
639 ui.config('patchbomb', configkey))
639 ui.config('patchbomb', configkey))
640 if not addr:
640 if not addr:
641 specified = (ui.hasconfig('email', configkey) or
641 specified = (ui.hasconfig('email', configkey) or
642 ui.hasconfig('patchbomb', configkey))
642 ui.hasconfig('patchbomb', configkey))
643 if not specified and ask:
643 if not specified and ask:
644 addr = prompt(ui, header, default=default)
644 addr = prompt(ui, header, default=default)
645 if addr:
645 if addr:
646 showaddrs.append('%s: %s' % (header, addr))
646 showaddrs.append('%s: %s' % (header, addr))
647 return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
647 return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
648 else:
648 else:
649 return default
649 return default
650
650
651 to = getaddrs('To', ask=True)
651 to = getaddrs('To', ask=True)
652 if not to:
652 if not to:
653 # we can get here in non-interactive mode
653 # we can get here in non-interactive mode
654 raise error.Abort(_('no recipient addresses provided'))
654 raise error.Abort(_('no recipient addresses provided'))
655 cc = getaddrs('Cc', ask=True, default='') or []
655 cc = getaddrs('Cc', ask=True, default='') or []
656 bcc = getaddrs('Bcc') or []
656 bcc = getaddrs('Bcc') or []
657 replyto = getaddrs('Reply-To')
657 replyto = getaddrs('Reply-To')
658
658
659 confirm = ui.configbool('patchbomb', 'confirm')
659 confirm = ui.configbool('patchbomb', 'confirm')
660 confirm |= bool(opts.get('diffstat') or opts.get('confirm'))
660 confirm |= bool(opts.get('diffstat') or opts.get('confirm'))
661
661
662 if confirm:
662 if confirm:
663 ui.write(_('\nFinal summary:\n\n'), label='patchbomb.finalsummary')
663 ui.write(_('\nFinal summary:\n\n'), label='patchbomb.finalsummary')
664 ui.write(('From: %s\n' % sender), label='patchbomb.from')
664 ui.write(('From: %s\n' % sender), label='patchbomb.from')
665 for addr in showaddrs:
665 for addr in showaddrs:
666 ui.write('%s\n' % addr, label='patchbomb.to')
666 ui.write('%s\n' % addr, label='patchbomb.to')
667 for m, subj, ds in msgs:
667 for m, subj, ds in msgs:
668 ui.write(('Subject: %s\n' % subj), label='patchbomb.subject')
668 ui.write(('Subject: %s\n' % subj), label='patchbomb.subject')
669 if ds:
669 if ds:
670 ui.write(ds, label='patchbomb.diffstats')
670 ui.write(ds, label='patchbomb.diffstats')
671 ui.write('\n')
671 ui.write('\n')
672 if ui.promptchoice(_('are you sure you want to send (yn)?'
672 if ui.promptchoice(_('are you sure you want to send (yn)?'
673 '$$ &Yes $$ &No')):
673 '$$ &Yes $$ &No')):
674 raise error.Abort(_('patchbomb canceled'))
674 raise error.Abort(_('patchbomb canceled'))
675
675
676 ui.write('\n')
676 ui.write('\n')
677
677
678 parent = opts.get('in_reply_to') or None
678 parent = opts.get('in_reply_to') or None
679 # angle brackets may be omitted, they're not semantically part of the msg-id
679 # angle brackets may be omitted, they're not semantically part of the msg-id
680 if parent is not None:
680 if parent is not None:
681 if not parent.startswith('<'):
681 if not parent.startswith('<'):
682 parent = '<' + parent
682 parent = '<' + parent
683 if not parent.endswith('>'):
683 if not parent.endswith('>'):
684 parent += '>'
684 parent += '>'
685
685
686 sender_addr = emailmod.Utils.parseaddr(sender)[1]
686 sender_addr = emailmod.Utils.parseaddr(sender)[1]
687 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
687 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
688 sendmail = None
688 sendmail = None
689 firstpatch = None
689 firstpatch = None
690 for i, (m, subj, ds) in enumerate(msgs):
690 for i, (m, subj, ds) in enumerate(msgs):
691 try:
691 try:
692 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
692 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
693 if not firstpatch:
693 if not firstpatch:
694 firstpatch = m['Message-Id']
694 firstpatch = m['Message-Id']
695 m['X-Mercurial-Series-Id'] = firstpatch
695 m['X-Mercurial-Series-Id'] = firstpatch
696 except TypeError:
696 except TypeError:
697 m['Message-Id'] = genmsgid('patchbomb')
697 m['Message-Id'] = genmsgid('patchbomb')
698 if parent:
698 if parent:
699 m['In-Reply-To'] = parent
699 m['In-Reply-To'] = parent
700 m['References'] = parent
700 m['References'] = parent
701 if not parent or 'X-Mercurial-Node' not in m:
701 if not parent or 'X-Mercurial-Node' not in m:
702 parent = m['Message-Id']
702 parent = m['Message-Id']
703
703
704 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
704 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
705 m['Date'] = emailmod.Utils.formatdate(start_time[0], localtime=True)
705 m['Date'] = emailmod.Utils.formatdate(start_time[0], localtime=True)
706
706
707 start_time = (start_time[0] + 1, start_time[1])
707 start_time = (start_time[0] + 1, start_time[1])
708 m['From'] = sender
708 m['From'] = sender
709 m['To'] = ', '.join(to)
709 m['To'] = ', '.join(to)
710 if cc:
710 if cc:
711 m['Cc'] = ', '.join(cc)
711 m['Cc'] = ', '.join(cc)
712 if bcc:
712 if bcc:
713 m['Bcc'] = ', '.join(bcc)
713 m['Bcc'] = ', '.join(bcc)
714 if replyto:
714 if replyto:
715 m['Reply-To'] = ', '.join(replyto)
715 m['Reply-To'] = ', '.join(replyto)
716 if opts.get('test'):
716 if opts.get('test'):
717 ui.status(_('displaying '), subj, ' ...\n')
717 ui.status(_('displaying '), subj, ' ...\n')
718 ui.flush()
719 ui.pager('email')
718 ui.pager('email')
720 generator = emailmod.Generator.Generator(ui, mangle_from_=False)
719 generator = emailmod.Generator.Generator(ui, mangle_from_=False)
721 try:
720 try:
722 generator.flatten(m, 0)
721 generator.flatten(m, 0)
723 ui.write('\n')
722 ui.write('\n')
724 except IOError as inst:
723 except IOError as inst:
725 if inst.errno != errno.EPIPE:
724 if inst.errno != errno.EPIPE:
726 raise
725 raise
727 else:
726 else:
728 if not sendmail:
727 if not sendmail:
729 sendmail = mail.connect(ui, mbox=mbox)
728 sendmail = mail.connect(ui, mbox=mbox)
730 ui.status(_('sending '), subj, ' ...\n')
729 ui.status(_('sending '), subj, ' ...\n')
731 ui.progress(_('sending'), i, item=subj, total=len(msgs),
730 ui.progress(_('sending'), i, item=subj, total=len(msgs),
732 unit=_('emails'))
731 unit=_('emails'))
733 if not mbox:
732 if not mbox:
734 # Exim does not remove the Bcc field
733 # Exim does not remove the Bcc field
735 del m['Bcc']
734 del m['Bcc']
736 fp = stringio()
735 fp = stringio()
737 generator = emailmod.Generator.Generator(fp, mangle_from_=False)
736 generator = emailmod.Generator.Generator(fp, mangle_from_=False)
738 generator.flatten(m, 0)
737 generator.flatten(m, 0)
739 sendmail(sender_addr, to + bcc + cc, fp.getvalue())
738 sendmail(sender_addr, to + bcc + cc, fp.getvalue())
740
739
741 ui.progress(_('writing'), None)
740 ui.progress(_('writing'), None)
742 ui.progress(_('sending'), None)
741 ui.progress(_('sending'), None)
@@ -1,1612 +1,1613 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import atexit
10 import atexit
11 import collections
11 import collections
12 import contextlib
12 import contextlib
13 import errno
13 import errno
14 import getpass
14 import getpass
15 import inspect
15 import inspect
16 import os
16 import os
17 import re
17 import re
18 import signal
18 import signal
19 import socket
19 import socket
20 import subprocess
20 import subprocess
21 import sys
21 import sys
22 import tempfile
22 import tempfile
23 import traceback
23 import traceback
24
24
25 from .i18n import _
25 from .i18n import _
26 from .node import hex
26 from .node import hex
27
27
28 from . import (
28 from . import (
29 color,
29 color,
30 config,
30 config,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39
39
40 urlreq = util.urlreq
40 urlreq = util.urlreq
41
41
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 if not c.isalnum())
44 if not c.isalnum())
45
45
46 samplehgrcs = {
46 samplehgrcs = {
47 'user':
47 'user':
48 """# example user config (see 'hg help config' for more info)
48 """# example user config (see 'hg help config' for more info)
49 [ui]
49 [ui]
50 # name and email, e.g.
50 # name and email, e.g.
51 # username = Jane Doe <jdoe@example.com>
51 # username = Jane Doe <jdoe@example.com>
52 username =
52 username =
53
53
54 # uncomment to colorize command output
54 # uncomment to colorize command output
55 # color = auto
55 # color = auto
56
56
57 [extensions]
57 [extensions]
58 # uncomment these lines to enable some popular extensions
58 # uncomment these lines to enable some popular extensions
59 # (see 'hg help extensions' for more info)
59 # (see 'hg help extensions' for more info)
60 #
60 #
61 # pager =""",
61 # pager =""",
62
62
63 'cloned':
63 'cloned':
64 """# example repository config (see 'hg help config' for more info)
64 """# example repository config (see 'hg help config' for more info)
65 [paths]
65 [paths]
66 default = %s
66 default = %s
67
67
68 # path aliases to other clones of this repo in URLs or filesystem paths
68 # path aliases to other clones of this repo in URLs or filesystem paths
69 # (see 'hg help config.paths' for more info)
69 # (see 'hg help config.paths' for more info)
70 #
70 #
71 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
71 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 # my-clone = /home/jdoe/jdoes-clone
73 # my-clone = /home/jdoe/jdoes-clone
74
74
75 [ui]
75 [ui]
76 # name and email (local to this repository, optional), e.g.
76 # name and email (local to this repository, optional), e.g.
77 # username = Jane Doe <jdoe@example.com>
77 # username = Jane Doe <jdoe@example.com>
78 """,
78 """,
79
79
80 'local':
80 'local':
81 """# example repository config (see 'hg help config' for more info)
81 """# example repository config (see 'hg help config' for more info)
82 [paths]
82 [paths]
83 # path aliases to other clones of this repo in URLs or filesystem paths
83 # path aliases to other clones of this repo in URLs or filesystem paths
84 # (see 'hg help config.paths' for more info)
84 # (see 'hg help config.paths' for more info)
85 #
85 #
86 # default = http://example.com/hg/example-repo
86 # default = http://example.com/hg/example-repo
87 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
87 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
88 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
88 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
89 # my-clone = /home/jdoe/jdoes-clone
89 # my-clone = /home/jdoe/jdoes-clone
90
90
91 [ui]
91 [ui]
92 # name and email (local to this repository, optional), e.g.
92 # name and email (local to this repository, optional), e.g.
93 # username = Jane Doe <jdoe@example.com>
93 # username = Jane Doe <jdoe@example.com>
94 """,
94 """,
95
95
96 'global':
96 'global':
97 """# example system-wide hg config (see 'hg help config' for more info)
97 """# example system-wide hg config (see 'hg help config' for more info)
98
98
99 [ui]
99 [ui]
100 # uncomment to colorize command output
100 # uncomment to colorize command output
101 # color = auto
101 # color = auto
102
102
103 [extensions]
103 [extensions]
104 # uncomment these lines to enable some popular extensions
104 # uncomment these lines to enable some popular extensions
105 # (see 'hg help extensions' for more info)
105 # (see 'hg help extensions' for more info)
106 #
106 #
107 # blackbox =
107 # blackbox =
108 # pager =""",
108 # pager =""",
109 }
109 }
110
110
111
111
112 class httppasswordmgrdbproxy(object):
112 class httppasswordmgrdbproxy(object):
113 """Delays loading urllib2 until it's needed."""
113 """Delays loading urllib2 until it's needed."""
114 def __init__(self):
114 def __init__(self):
115 self._mgr = None
115 self._mgr = None
116
116
117 def _get_mgr(self):
117 def _get_mgr(self):
118 if self._mgr is None:
118 if self._mgr is None:
119 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
119 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
120 return self._mgr
120 return self._mgr
121
121
122 def add_password(self, *args, **kwargs):
122 def add_password(self, *args, **kwargs):
123 return self._get_mgr().add_password(*args, **kwargs)
123 return self._get_mgr().add_password(*args, **kwargs)
124
124
125 def find_user_password(self, *args, **kwargs):
125 def find_user_password(self, *args, **kwargs):
126 return self._get_mgr().find_user_password(*args, **kwargs)
126 return self._get_mgr().find_user_password(*args, **kwargs)
127
127
128 def _catchterm(*args):
128 def _catchterm(*args):
129 raise error.SignalInterrupt
129 raise error.SignalInterrupt
130
130
131 class ui(object):
131 class ui(object):
132 def __init__(self, src=None):
132 def __init__(self, src=None):
133 """Create a fresh new ui object if no src given
133 """Create a fresh new ui object if no src given
134
134
135 Use uimod.ui.load() to create a ui which knows global and user configs.
135 Use uimod.ui.load() to create a ui which knows global and user configs.
136 In most cases, you should use ui.copy() to create a copy of an existing
136 In most cases, you should use ui.copy() to create a copy of an existing
137 ui object.
137 ui object.
138 """
138 """
139 # _buffers: used for temporary capture of output
139 # _buffers: used for temporary capture of output
140 self._buffers = []
140 self._buffers = []
141 # 3-tuple describing how each buffer in the stack behaves.
141 # 3-tuple describing how each buffer in the stack behaves.
142 # Values are (capture stderr, capture subprocesses, apply labels).
142 # Values are (capture stderr, capture subprocesses, apply labels).
143 self._bufferstates = []
143 self._bufferstates = []
144 # When a buffer is active, defines whether we are expanding labels.
144 # When a buffer is active, defines whether we are expanding labels.
145 # This exists to prevent an extra list lookup.
145 # This exists to prevent an extra list lookup.
146 self._bufferapplylabels = None
146 self._bufferapplylabels = None
147 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
147 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
148 self._reportuntrusted = True
148 self._reportuntrusted = True
149 self._ocfg = config.config() # overlay
149 self._ocfg = config.config() # overlay
150 self._tcfg = config.config() # trusted
150 self._tcfg = config.config() # trusted
151 self._ucfg = config.config() # untrusted
151 self._ucfg = config.config() # untrusted
152 self._trustusers = set()
152 self._trustusers = set()
153 self._trustgroups = set()
153 self._trustgroups = set()
154 self.callhooks = True
154 self.callhooks = True
155 # Insecure server connections requested.
155 # Insecure server connections requested.
156 self.insecureconnections = False
156 self.insecureconnections = False
157 # Blocked time
157 # Blocked time
158 self.logblockedtimes = False
158 self.logblockedtimes = False
159 # color mode: see mercurial/color.py for possible value
159 # color mode: see mercurial/color.py for possible value
160 self._colormode = None
160 self._colormode = None
161 self._terminfoparams = {}
161 self._terminfoparams = {}
162 self._styles = {}
162 self._styles = {}
163
163
164 if src:
164 if src:
165 self.fout = src.fout
165 self.fout = src.fout
166 self.ferr = src.ferr
166 self.ferr = src.ferr
167 self.fin = src.fin
167 self.fin = src.fin
168 self.pageractive = src.pageractive
168 self.pageractive = src.pageractive
169 self._disablepager = src._disablepager
169 self._disablepager = src._disablepager
170
170
171 self._tcfg = src._tcfg.copy()
171 self._tcfg = src._tcfg.copy()
172 self._ucfg = src._ucfg.copy()
172 self._ucfg = src._ucfg.copy()
173 self._ocfg = src._ocfg.copy()
173 self._ocfg = src._ocfg.copy()
174 self._trustusers = src._trustusers.copy()
174 self._trustusers = src._trustusers.copy()
175 self._trustgroups = src._trustgroups.copy()
175 self._trustgroups = src._trustgroups.copy()
176 self.environ = src.environ
176 self.environ = src.environ
177 self.callhooks = src.callhooks
177 self.callhooks = src.callhooks
178 self.insecureconnections = src.insecureconnections
178 self.insecureconnections = src.insecureconnections
179 self._colormode = src._colormode
179 self._colormode = src._colormode
180 self._terminfoparams = src._terminfoparams.copy()
180 self._terminfoparams = src._terminfoparams.copy()
181 self._styles = src._styles.copy()
181 self._styles = src._styles.copy()
182
182
183 self.fixconfig()
183 self.fixconfig()
184
184
185 self.httppasswordmgrdb = src.httppasswordmgrdb
185 self.httppasswordmgrdb = src.httppasswordmgrdb
186 self._blockedtimes = src._blockedtimes
186 self._blockedtimes = src._blockedtimes
187 else:
187 else:
188 self.fout = util.stdout
188 self.fout = util.stdout
189 self.ferr = util.stderr
189 self.ferr = util.stderr
190 self.fin = util.stdin
190 self.fin = util.stdin
191 self.pageractive = False
191 self.pageractive = False
192 self._disablepager = False
192 self._disablepager = False
193
193
194 # shared read-only environment
194 # shared read-only environment
195 self.environ = encoding.environ
195 self.environ = encoding.environ
196
196
197 self.httppasswordmgrdb = httppasswordmgrdbproxy()
197 self.httppasswordmgrdb = httppasswordmgrdbproxy()
198 self._blockedtimes = collections.defaultdict(int)
198 self._blockedtimes = collections.defaultdict(int)
199
199
200 allowed = self.configlist('experimental', 'exportableenviron')
200 allowed = self.configlist('experimental', 'exportableenviron')
201 if '*' in allowed:
201 if '*' in allowed:
202 self._exportableenviron = self.environ
202 self._exportableenviron = self.environ
203 else:
203 else:
204 self._exportableenviron = {}
204 self._exportableenviron = {}
205 for k in allowed:
205 for k in allowed:
206 if k in self.environ:
206 if k in self.environ:
207 self._exportableenviron[k] = self.environ[k]
207 self._exportableenviron[k] = self.environ[k]
208
208
209 @classmethod
209 @classmethod
210 def load(cls):
210 def load(cls):
211 """Create a ui and load global and user configs"""
211 """Create a ui and load global and user configs"""
212 u = cls()
212 u = cls()
213 # we always trust global config files
213 # we always trust global config files
214 for f in scmutil.rcpath():
214 for f in scmutil.rcpath():
215 u.readconfig(f, trust=True)
215 u.readconfig(f, trust=True)
216 return u
216 return u
217
217
218 def copy(self):
218 def copy(self):
219 return self.__class__(self)
219 return self.__class__(self)
220
220
221 def resetstate(self):
221 def resetstate(self):
222 """Clear internal state that shouldn't persist across commands"""
222 """Clear internal state that shouldn't persist across commands"""
223 if self._progbar:
223 if self._progbar:
224 self._progbar.resetstate() # reset last-print time of progress bar
224 self._progbar.resetstate() # reset last-print time of progress bar
225 self.httppasswordmgrdb = httppasswordmgrdbproxy()
225 self.httppasswordmgrdb = httppasswordmgrdbproxy()
226
226
227 @contextlib.contextmanager
227 @contextlib.contextmanager
228 def timeblockedsection(self, key):
228 def timeblockedsection(self, key):
229 # this is open-coded below - search for timeblockedsection to find them
229 # this is open-coded below - search for timeblockedsection to find them
230 starttime = util.timer()
230 starttime = util.timer()
231 try:
231 try:
232 yield
232 yield
233 finally:
233 finally:
234 self._blockedtimes[key + '_blocked'] += \
234 self._blockedtimes[key + '_blocked'] += \
235 (util.timer() - starttime) * 1000
235 (util.timer() - starttime) * 1000
236
236
237 def formatter(self, topic, opts):
237 def formatter(self, topic, opts):
238 return formatter.formatter(self, topic, opts)
238 return formatter.formatter(self, topic, opts)
239
239
240 def _trusted(self, fp, f):
240 def _trusted(self, fp, f):
241 st = util.fstat(fp)
241 st = util.fstat(fp)
242 if util.isowner(st):
242 if util.isowner(st):
243 return True
243 return True
244
244
245 tusers, tgroups = self._trustusers, self._trustgroups
245 tusers, tgroups = self._trustusers, self._trustgroups
246 if '*' in tusers or '*' in tgroups:
246 if '*' in tusers or '*' in tgroups:
247 return True
247 return True
248
248
249 user = util.username(st.st_uid)
249 user = util.username(st.st_uid)
250 group = util.groupname(st.st_gid)
250 group = util.groupname(st.st_gid)
251 if user in tusers or group in tgroups or user == util.username():
251 if user in tusers or group in tgroups or user == util.username():
252 return True
252 return True
253
253
254 if self._reportuntrusted:
254 if self._reportuntrusted:
255 self.warn(_('not trusting file %s from untrusted '
255 self.warn(_('not trusting file %s from untrusted '
256 'user %s, group %s\n') % (f, user, group))
256 'user %s, group %s\n') % (f, user, group))
257 return False
257 return False
258
258
259 def readconfig(self, filename, root=None, trust=False,
259 def readconfig(self, filename, root=None, trust=False,
260 sections=None, remap=None):
260 sections=None, remap=None):
261 try:
261 try:
262 fp = open(filename, u'rb')
262 fp = open(filename, u'rb')
263 except IOError:
263 except IOError:
264 if not sections: # ignore unless we were looking for something
264 if not sections: # ignore unless we were looking for something
265 return
265 return
266 raise
266 raise
267
267
268 cfg = config.config()
268 cfg = config.config()
269 trusted = sections or trust or self._trusted(fp, filename)
269 trusted = sections or trust or self._trusted(fp, filename)
270
270
271 try:
271 try:
272 cfg.read(filename, fp, sections=sections, remap=remap)
272 cfg.read(filename, fp, sections=sections, remap=remap)
273 fp.close()
273 fp.close()
274 except error.ConfigError as inst:
274 except error.ConfigError as inst:
275 if trusted:
275 if trusted:
276 raise
276 raise
277 self.warn(_("ignored: %s\n") % str(inst))
277 self.warn(_("ignored: %s\n") % str(inst))
278
278
279 if self.plain():
279 if self.plain():
280 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
280 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
281 'logtemplate', 'statuscopies', 'style',
281 'logtemplate', 'statuscopies', 'style',
282 'traceback', 'verbose'):
282 'traceback', 'verbose'):
283 if k in cfg['ui']:
283 if k in cfg['ui']:
284 del cfg['ui'][k]
284 del cfg['ui'][k]
285 for k, v in cfg.items('defaults'):
285 for k, v in cfg.items('defaults'):
286 del cfg['defaults'][k]
286 del cfg['defaults'][k]
287 # Don't remove aliases from the configuration if in the exceptionlist
287 # Don't remove aliases from the configuration if in the exceptionlist
288 if self.plain('alias'):
288 if self.plain('alias'):
289 for k, v in cfg.items('alias'):
289 for k, v in cfg.items('alias'):
290 del cfg['alias'][k]
290 del cfg['alias'][k]
291 if self.plain('revsetalias'):
291 if self.plain('revsetalias'):
292 for k, v in cfg.items('revsetalias'):
292 for k, v in cfg.items('revsetalias'):
293 del cfg['revsetalias'][k]
293 del cfg['revsetalias'][k]
294 if self.plain('templatealias'):
294 if self.plain('templatealias'):
295 for k, v in cfg.items('templatealias'):
295 for k, v in cfg.items('templatealias'):
296 del cfg['templatealias'][k]
296 del cfg['templatealias'][k]
297
297
298 if trusted:
298 if trusted:
299 self._tcfg.update(cfg)
299 self._tcfg.update(cfg)
300 self._tcfg.update(self._ocfg)
300 self._tcfg.update(self._ocfg)
301 self._ucfg.update(cfg)
301 self._ucfg.update(cfg)
302 self._ucfg.update(self._ocfg)
302 self._ucfg.update(self._ocfg)
303
303
304 if root is None:
304 if root is None:
305 root = os.path.expanduser('~')
305 root = os.path.expanduser('~')
306 self.fixconfig(root=root)
306 self.fixconfig(root=root)
307
307
308 def fixconfig(self, root=None, section=None):
308 def fixconfig(self, root=None, section=None):
309 if section in (None, 'paths'):
309 if section in (None, 'paths'):
310 # expand vars and ~
310 # expand vars and ~
311 # translate paths relative to root (or home) into absolute paths
311 # translate paths relative to root (or home) into absolute paths
312 root = root or pycompat.getcwd()
312 root = root or pycompat.getcwd()
313 for c in self._tcfg, self._ucfg, self._ocfg:
313 for c in self._tcfg, self._ucfg, self._ocfg:
314 for n, p in c.items('paths'):
314 for n, p in c.items('paths'):
315 # Ignore sub-options.
315 # Ignore sub-options.
316 if ':' in n:
316 if ':' in n:
317 continue
317 continue
318 if not p:
318 if not p:
319 continue
319 continue
320 if '%%' in p:
320 if '%%' in p:
321 s = self.configsource('paths', n) or 'none'
321 s = self.configsource('paths', n) or 'none'
322 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
322 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
323 % (n, p, s))
323 % (n, p, s))
324 p = p.replace('%%', '%')
324 p = p.replace('%%', '%')
325 p = util.expandpath(p)
325 p = util.expandpath(p)
326 if not util.hasscheme(p) and not os.path.isabs(p):
326 if not util.hasscheme(p) and not os.path.isabs(p):
327 p = os.path.normpath(os.path.join(root, p))
327 p = os.path.normpath(os.path.join(root, p))
328 c.set("paths", n, p)
328 c.set("paths", n, p)
329
329
330 if section in (None, 'ui'):
330 if section in (None, 'ui'):
331 # update ui options
331 # update ui options
332 self.debugflag = self.configbool('ui', 'debug')
332 self.debugflag = self.configbool('ui', 'debug')
333 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
333 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
334 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
334 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
335 if self.verbose and self.quiet:
335 if self.verbose and self.quiet:
336 self.quiet = self.verbose = False
336 self.quiet = self.verbose = False
337 self._reportuntrusted = self.debugflag or self.configbool("ui",
337 self._reportuntrusted = self.debugflag or self.configbool("ui",
338 "report_untrusted", True)
338 "report_untrusted", True)
339 self.tracebackflag = self.configbool('ui', 'traceback', False)
339 self.tracebackflag = self.configbool('ui', 'traceback', False)
340 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
340 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
341
341
342 if section in (None, 'trusted'):
342 if section in (None, 'trusted'):
343 # update trust information
343 # update trust information
344 self._trustusers.update(self.configlist('trusted', 'users'))
344 self._trustusers.update(self.configlist('trusted', 'users'))
345 self._trustgroups.update(self.configlist('trusted', 'groups'))
345 self._trustgroups.update(self.configlist('trusted', 'groups'))
346
346
347 def backupconfig(self, section, item):
347 def backupconfig(self, section, item):
348 return (self._ocfg.backup(section, item),
348 return (self._ocfg.backup(section, item),
349 self._tcfg.backup(section, item),
349 self._tcfg.backup(section, item),
350 self._ucfg.backup(section, item),)
350 self._ucfg.backup(section, item),)
351 def restoreconfig(self, data):
351 def restoreconfig(self, data):
352 self._ocfg.restore(data[0])
352 self._ocfg.restore(data[0])
353 self._tcfg.restore(data[1])
353 self._tcfg.restore(data[1])
354 self._ucfg.restore(data[2])
354 self._ucfg.restore(data[2])
355
355
356 def setconfig(self, section, name, value, source=''):
356 def setconfig(self, section, name, value, source=''):
357 for cfg in (self._ocfg, self._tcfg, self._ucfg):
357 for cfg in (self._ocfg, self._tcfg, self._ucfg):
358 cfg.set(section, name, value, source)
358 cfg.set(section, name, value, source)
359 self.fixconfig(section=section)
359 self.fixconfig(section=section)
360
360
361 def _data(self, untrusted):
361 def _data(self, untrusted):
362 return untrusted and self._ucfg or self._tcfg
362 return untrusted and self._ucfg or self._tcfg
363
363
364 def configsource(self, section, name, untrusted=False):
364 def configsource(self, section, name, untrusted=False):
365 return self._data(untrusted).source(section, name)
365 return self._data(untrusted).source(section, name)
366
366
367 def config(self, section, name, default=None, untrusted=False):
367 def config(self, section, name, default=None, untrusted=False):
368 if isinstance(name, list):
368 if isinstance(name, list):
369 alternates = name
369 alternates = name
370 else:
370 else:
371 alternates = [name]
371 alternates = [name]
372
372
373 for n in alternates:
373 for n in alternates:
374 value = self._data(untrusted).get(section, n, None)
374 value = self._data(untrusted).get(section, n, None)
375 if value is not None:
375 if value is not None:
376 name = n
376 name = n
377 break
377 break
378 else:
378 else:
379 value = default
379 value = default
380
380
381 if self.debugflag and not untrusted and self._reportuntrusted:
381 if self.debugflag and not untrusted and self._reportuntrusted:
382 for n in alternates:
382 for n in alternates:
383 uvalue = self._ucfg.get(section, n)
383 uvalue = self._ucfg.get(section, n)
384 if uvalue is not None and uvalue != value:
384 if uvalue is not None and uvalue != value:
385 self.debug("ignoring untrusted configuration option "
385 self.debug("ignoring untrusted configuration option "
386 "%s.%s = %s\n" % (section, n, uvalue))
386 "%s.%s = %s\n" % (section, n, uvalue))
387 return value
387 return value
388
388
389 def configsuboptions(self, section, name, default=None, untrusted=False):
389 def configsuboptions(self, section, name, default=None, untrusted=False):
390 """Get a config option and all sub-options.
390 """Get a config option and all sub-options.
391
391
392 Some config options have sub-options that are declared with the
392 Some config options have sub-options that are declared with the
393 format "key:opt = value". This method is used to return the main
393 format "key:opt = value". This method is used to return the main
394 option and all its declared sub-options.
394 option and all its declared sub-options.
395
395
396 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
396 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
397 is a dict of defined sub-options where keys and values are strings.
397 is a dict of defined sub-options where keys and values are strings.
398 """
398 """
399 data = self._data(untrusted)
399 data = self._data(untrusted)
400 main = data.get(section, name, default)
400 main = data.get(section, name, default)
401 if self.debugflag and not untrusted and self._reportuntrusted:
401 if self.debugflag and not untrusted and self._reportuntrusted:
402 uvalue = self._ucfg.get(section, name)
402 uvalue = self._ucfg.get(section, name)
403 if uvalue is not None and uvalue != main:
403 if uvalue is not None and uvalue != main:
404 self.debug('ignoring untrusted configuration option '
404 self.debug('ignoring untrusted configuration option '
405 '%s.%s = %s\n' % (section, name, uvalue))
405 '%s.%s = %s\n' % (section, name, uvalue))
406
406
407 sub = {}
407 sub = {}
408 prefix = '%s:' % name
408 prefix = '%s:' % name
409 for k, v in data.items(section):
409 for k, v in data.items(section):
410 if k.startswith(prefix):
410 if k.startswith(prefix):
411 sub[k[len(prefix):]] = v
411 sub[k[len(prefix):]] = v
412
412
413 if self.debugflag and not untrusted and self._reportuntrusted:
413 if self.debugflag and not untrusted and self._reportuntrusted:
414 for k, v in sub.items():
414 for k, v in sub.items():
415 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
415 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
416 if uvalue is not None and uvalue != v:
416 if uvalue is not None and uvalue != v:
417 self.debug('ignoring untrusted configuration option '
417 self.debug('ignoring untrusted configuration option '
418 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
418 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
419
419
420 return main, sub
420 return main, sub
421
421
422 def configpath(self, section, name, default=None, untrusted=False):
422 def configpath(self, section, name, default=None, untrusted=False):
423 'get a path config item, expanded relative to repo root or config file'
423 'get a path config item, expanded relative to repo root or config file'
424 v = self.config(section, name, default, untrusted)
424 v = self.config(section, name, default, untrusted)
425 if v is None:
425 if v is None:
426 return None
426 return None
427 if not os.path.isabs(v) or "://" not in v:
427 if not os.path.isabs(v) or "://" not in v:
428 src = self.configsource(section, name, untrusted)
428 src = self.configsource(section, name, untrusted)
429 if ':' in src:
429 if ':' in src:
430 base = os.path.dirname(src.rsplit(':')[0])
430 base = os.path.dirname(src.rsplit(':')[0])
431 v = os.path.join(base, os.path.expanduser(v))
431 v = os.path.join(base, os.path.expanduser(v))
432 return v
432 return v
433
433
434 def configbool(self, section, name, default=False, untrusted=False):
434 def configbool(self, section, name, default=False, untrusted=False):
435 """parse a configuration element as a boolean
435 """parse a configuration element as a boolean
436
436
437 >>> u = ui(); s = 'foo'
437 >>> u = ui(); s = 'foo'
438 >>> u.setconfig(s, 'true', 'yes')
438 >>> u.setconfig(s, 'true', 'yes')
439 >>> u.configbool(s, 'true')
439 >>> u.configbool(s, 'true')
440 True
440 True
441 >>> u.setconfig(s, 'false', 'no')
441 >>> u.setconfig(s, 'false', 'no')
442 >>> u.configbool(s, 'false')
442 >>> u.configbool(s, 'false')
443 False
443 False
444 >>> u.configbool(s, 'unknown')
444 >>> u.configbool(s, 'unknown')
445 False
445 False
446 >>> u.configbool(s, 'unknown', True)
446 >>> u.configbool(s, 'unknown', True)
447 True
447 True
448 >>> u.setconfig(s, 'invalid', 'somevalue')
448 >>> u.setconfig(s, 'invalid', 'somevalue')
449 >>> u.configbool(s, 'invalid')
449 >>> u.configbool(s, 'invalid')
450 Traceback (most recent call last):
450 Traceback (most recent call last):
451 ...
451 ...
452 ConfigError: foo.invalid is not a boolean ('somevalue')
452 ConfigError: foo.invalid is not a boolean ('somevalue')
453 """
453 """
454
454
455 v = self.config(section, name, None, untrusted)
455 v = self.config(section, name, None, untrusted)
456 if v is None:
456 if v is None:
457 return default
457 return default
458 if isinstance(v, bool):
458 if isinstance(v, bool):
459 return v
459 return v
460 b = util.parsebool(v)
460 b = util.parsebool(v)
461 if b is None:
461 if b is None:
462 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
462 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
463 % (section, name, v))
463 % (section, name, v))
464 return b
464 return b
465
465
466 def configwith(self, convert, section, name, default=None,
466 def configwith(self, convert, section, name, default=None,
467 desc=None, untrusted=False):
467 desc=None, untrusted=False):
468 """parse a configuration element with a conversion function
468 """parse a configuration element with a conversion function
469
469
470 >>> u = ui(); s = 'foo'
470 >>> u = ui(); s = 'foo'
471 >>> u.setconfig(s, 'float1', '42')
471 >>> u.setconfig(s, 'float1', '42')
472 >>> u.configwith(float, s, 'float1')
472 >>> u.configwith(float, s, 'float1')
473 42.0
473 42.0
474 >>> u.setconfig(s, 'float2', '-4.25')
474 >>> u.setconfig(s, 'float2', '-4.25')
475 >>> u.configwith(float, s, 'float2')
475 >>> u.configwith(float, s, 'float2')
476 -4.25
476 -4.25
477 >>> u.configwith(float, s, 'unknown', 7)
477 >>> u.configwith(float, s, 'unknown', 7)
478 7
478 7
479 >>> u.setconfig(s, 'invalid', 'somevalue')
479 >>> u.setconfig(s, 'invalid', 'somevalue')
480 >>> u.configwith(float, s, 'invalid')
480 >>> u.configwith(float, s, 'invalid')
481 Traceback (most recent call last):
481 Traceback (most recent call last):
482 ...
482 ...
483 ConfigError: foo.invalid is not a valid float ('somevalue')
483 ConfigError: foo.invalid is not a valid float ('somevalue')
484 >>> u.configwith(float, s, 'invalid', desc='womble')
484 >>> u.configwith(float, s, 'invalid', desc='womble')
485 Traceback (most recent call last):
485 Traceback (most recent call last):
486 ...
486 ...
487 ConfigError: foo.invalid is not a valid womble ('somevalue')
487 ConfigError: foo.invalid is not a valid womble ('somevalue')
488 """
488 """
489
489
490 v = self.config(section, name, None, untrusted)
490 v = self.config(section, name, None, untrusted)
491 if v is None:
491 if v is None:
492 return default
492 return default
493 try:
493 try:
494 return convert(v)
494 return convert(v)
495 except ValueError:
495 except ValueError:
496 if desc is None:
496 if desc is None:
497 desc = convert.__name__
497 desc = convert.__name__
498 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
498 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
499 % (section, name, desc, v))
499 % (section, name, desc, v))
500
500
501 def configint(self, section, name, default=None, untrusted=False):
501 def configint(self, section, name, default=None, untrusted=False):
502 """parse a configuration element as an integer
502 """parse a configuration element as an integer
503
503
504 >>> u = ui(); s = 'foo'
504 >>> u = ui(); s = 'foo'
505 >>> u.setconfig(s, 'int1', '42')
505 >>> u.setconfig(s, 'int1', '42')
506 >>> u.configint(s, 'int1')
506 >>> u.configint(s, 'int1')
507 42
507 42
508 >>> u.setconfig(s, 'int2', '-42')
508 >>> u.setconfig(s, 'int2', '-42')
509 >>> u.configint(s, 'int2')
509 >>> u.configint(s, 'int2')
510 -42
510 -42
511 >>> u.configint(s, 'unknown', 7)
511 >>> u.configint(s, 'unknown', 7)
512 7
512 7
513 >>> u.setconfig(s, 'invalid', 'somevalue')
513 >>> u.setconfig(s, 'invalid', 'somevalue')
514 >>> u.configint(s, 'invalid')
514 >>> u.configint(s, 'invalid')
515 Traceback (most recent call last):
515 Traceback (most recent call last):
516 ...
516 ...
517 ConfigError: foo.invalid is not a valid integer ('somevalue')
517 ConfigError: foo.invalid is not a valid integer ('somevalue')
518 """
518 """
519
519
520 return self.configwith(int, section, name, default, 'integer',
520 return self.configwith(int, section, name, default, 'integer',
521 untrusted)
521 untrusted)
522
522
523 def configbytes(self, section, name, default=0, untrusted=False):
523 def configbytes(self, section, name, default=0, untrusted=False):
524 """parse a configuration element as a quantity in bytes
524 """parse a configuration element as a quantity in bytes
525
525
526 Units can be specified as b (bytes), k or kb (kilobytes), m or
526 Units can be specified as b (bytes), k or kb (kilobytes), m or
527 mb (megabytes), g or gb (gigabytes).
527 mb (megabytes), g or gb (gigabytes).
528
528
529 >>> u = ui(); s = 'foo'
529 >>> u = ui(); s = 'foo'
530 >>> u.setconfig(s, 'val1', '42')
530 >>> u.setconfig(s, 'val1', '42')
531 >>> u.configbytes(s, 'val1')
531 >>> u.configbytes(s, 'val1')
532 42
532 42
533 >>> u.setconfig(s, 'val2', '42.5 kb')
533 >>> u.setconfig(s, 'val2', '42.5 kb')
534 >>> u.configbytes(s, 'val2')
534 >>> u.configbytes(s, 'val2')
535 43520
535 43520
536 >>> u.configbytes(s, 'unknown', '7 MB')
536 >>> u.configbytes(s, 'unknown', '7 MB')
537 7340032
537 7340032
538 >>> u.setconfig(s, 'invalid', 'somevalue')
538 >>> u.setconfig(s, 'invalid', 'somevalue')
539 >>> u.configbytes(s, 'invalid')
539 >>> u.configbytes(s, 'invalid')
540 Traceback (most recent call last):
540 Traceback (most recent call last):
541 ...
541 ...
542 ConfigError: foo.invalid is not a byte quantity ('somevalue')
542 ConfigError: foo.invalid is not a byte quantity ('somevalue')
543 """
543 """
544
544
545 value = self.config(section, name, None, untrusted)
545 value = self.config(section, name, None, untrusted)
546 if value is None:
546 if value is None:
547 if not isinstance(default, str):
547 if not isinstance(default, str):
548 return default
548 return default
549 value = default
549 value = default
550 try:
550 try:
551 return util.sizetoint(value)
551 return util.sizetoint(value)
552 except error.ParseError:
552 except error.ParseError:
553 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
553 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
554 % (section, name, value))
554 % (section, name, value))
555
555
556 def configlist(self, section, name, default=None, untrusted=False):
556 def configlist(self, section, name, default=None, untrusted=False):
557 """parse a configuration element as a list of comma/space separated
557 """parse a configuration element as a list of comma/space separated
558 strings
558 strings
559
559
560 >>> u = ui(); s = 'foo'
560 >>> u = ui(); s = 'foo'
561 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
561 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
562 >>> u.configlist(s, 'list1')
562 >>> u.configlist(s, 'list1')
563 ['this', 'is', 'a small', 'test']
563 ['this', 'is', 'a small', 'test']
564 """
564 """
565 # default is not always a list
565 # default is not always a list
566 if isinstance(default, bytes):
566 if isinstance(default, bytes):
567 default = config.parselist(default)
567 default = config.parselist(default)
568 return self.configwith(config.parselist, section, name, default or [],
568 return self.configwith(config.parselist, section, name, default or [],
569 'list', untrusted)
569 'list', untrusted)
570
570
571 def hasconfig(self, section, name, untrusted=False):
571 def hasconfig(self, section, name, untrusted=False):
572 return self._data(untrusted).hasitem(section, name)
572 return self._data(untrusted).hasitem(section, name)
573
573
574 def has_section(self, section, untrusted=False):
574 def has_section(self, section, untrusted=False):
575 '''tell whether section exists in config.'''
575 '''tell whether section exists in config.'''
576 return section in self._data(untrusted)
576 return section in self._data(untrusted)
577
577
578 def configitems(self, section, untrusted=False, ignoresub=False):
578 def configitems(self, section, untrusted=False, ignoresub=False):
579 items = self._data(untrusted).items(section)
579 items = self._data(untrusted).items(section)
580 if ignoresub:
580 if ignoresub:
581 newitems = {}
581 newitems = {}
582 for k, v in items:
582 for k, v in items:
583 if ':' not in k:
583 if ':' not in k:
584 newitems[k] = v
584 newitems[k] = v
585 items = newitems.items()
585 items = newitems.items()
586 if self.debugflag and not untrusted and self._reportuntrusted:
586 if self.debugflag and not untrusted and self._reportuntrusted:
587 for k, v in self._ucfg.items(section):
587 for k, v in self._ucfg.items(section):
588 if self._tcfg.get(section, k) != v:
588 if self._tcfg.get(section, k) != v:
589 self.debug("ignoring untrusted configuration option "
589 self.debug("ignoring untrusted configuration option "
590 "%s.%s = %s\n" % (section, k, v))
590 "%s.%s = %s\n" % (section, k, v))
591 return items
591 return items
592
592
593 def walkconfig(self, untrusted=False):
593 def walkconfig(self, untrusted=False):
594 cfg = self._data(untrusted)
594 cfg = self._data(untrusted)
595 for section in cfg.sections():
595 for section in cfg.sections():
596 for name, value in self.configitems(section, untrusted):
596 for name, value in self.configitems(section, untrusted):
597 yield section, name, value
597 yield section, name, value
598
598
599 def plain(self, feature=None):
599 def plain(self, feature=None):
600 '''is plain mode active?
600 '''is plain mode active?
601
601
602 Plain mode means that all configuration variables which affect
602 Plain mode means that all configuration variables which affect
603 the behavior and output of Mercurial should be
603 the behavior and output of Mercurial should be
604 ignored. Additionally, the output should be stable,
604 ignored. Additionally, the output should be stable,
605 reproducible and suitable for use in scripts or applications.
605 reproducible and suitable for use in scripts or applications.
606
606
607 The only way to trigger plain mode is by setting either the
607 The only way to trigger plain mode is by setting either the
608 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
608 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
609
609
610 The return value can either be
610 The return value can either be
611 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
611 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
612 - True otherwise
612 - True otherwise
613 '''
613 '''
614 if ('HGPLAIN' not in encoding.environ and
614 if ('HGPLAIN' not in encoding.environ and
615 'HGPLAINEXCEPT' not in encoding.environ):
615 'HGPLAINEXCEPT' not in encoding.environ):
616 return False
616 return False
617 exceptions = encoding.environ.get('HGPLAINEXCEPT',
617 exceptions = encoding.environ.get('HGPLAINEXCEPT',
618 '').strip().split(',')
618 '').strip().split(',')
619 if feature and exceptions:
619 if feature and exceptions:
620 return feature not in exceptions
620 return feature not in exceptions
621 return True
621 return True
622
622
623 def username(self):
623 def username(self):
624 """Return default username to be used in commits.
624 """Return default username to be used in commits.
625
625
626 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
626 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
627 and stop searching if one of these is set.
627 and stop searching if one of these is set.
628 If not found and ui.askusername is True, ask the user, else use
628 If not found and ui.askusername is True, ask the user, else use
629 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
629 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
630 """
630 """
631 user = encoding.environ.get("HGUSER")
631 user = encoding.environ.get("HGUSER")
632 if user is None:
632 if user is None:
633 user = self.config("ui", ["username", "user"])
633 user = self.config("ui", ["username", "user"])
634 if user is not None:
634 if user is not None:
635 user = os.path.expandvars(user)
635 user = os.path.expandvars(user)
636 if user is None:
636 if user is None:
637 user = encoding.environ.get("EMAIL")
637 user = encoding.environ.get("EMAIL")
638 if user is None and self.configbool("ui", "askusername"):
638 if user is None and self.configbool("ui", "askusername"):
639 user = self.prompt(_("enter a commit username:"), default=None)
639 user = self.prompt(_("enter a commit username:"), default=None)
640 if user is None and not self.interactive():
640 if user is None and not self.interactive():
641 try:
641 try:
642 user = '%s@%s' % (util.getuser(), socket.getfqdn())
642 user = '%s@%s' % (util.getuser(), socket.getfqdn())
643 self.warn(_("no username found, using '%s' instead\n") % user)
643 self.warn(_("no username found, using '%s' instead\n") % user)
644 except KeyError:
644 except KeyError:
645 pass
645 pass
646 if not user:
646 if not user:
647 raise error.Abort(_('no username supplied'),
647 raise error.Abort(_('no username supplied'),
648 hint=_("use 'hg config --edit' "
648 hint=_("use 'hg config --edit' "
649 'to set your username'))
649 'to set your username'))
650 if "\n" in user:
650 if "\n" in user:
651 raise error.Abort(_("username %s contains a newline\n")
651 raise error.Abort(_("username %s contains a newline\n")
652 % repr(user))
652 % repr(user))
653 return user
653 return user
654
654
655 def shortuser(self, user):
655 def shortuser(self, user):
656 """Return a short representation of a user name or email address."""
656 """Return a short representation of a user name or email address."""
657 if not self.verbose:
657 if not self.verbose:
658 user = util.shortuser(user)
658 user = util.shortuser(user)
659 return user
659 return user
660
660
661 def expandpath(self, loc, default=None):
661 def expandpath(self, loc, default=None):
662 """Return repository location relative to cwd or from [paths]"""
662 """Return repository location relative to cwd or from [paths]"""
663 try:
663 try:
664 p = self.paths.getpath(loc)
664 p = self.paths.getpath(loc)
665 if p:
665 if p:
666 return p.rawloc
666 return p.rawloc
667 except error.RepoError:
667 except error.RepoError:
668 pass
668 pass
669
669
670 if default:
670 if default:
671 try:
671 try:
672 p = self.paths.getpath(default)
672 p = self.paths.getpath(default)
673 if p:
673 if p:
674 return p.rawloc
674 return p.rawloc
675 except error.RepoError:
675 except error.RepoError:
676 pass
676 pass
677
677
678 return loc
678 return loc
679
679
680 @util.propertycache
680 @util.propertycache
681 def paths(self):
681 def paths(self):
682 return paths(self)
682 return paths(self)
683
683
684 def pushbuffer(self, error=False, subproc=False, labeled=False):
684 def pushbuffer(self, error=False, subproc=False, labeled=False):
685 """install a buffer to capture standard output of the ui object
685 """install a buffer to capture standard output of the ui object
686
686
687 If error is True, the error output will be captured too.
687 If error is True, the error output will be captured too.
688
688
689 If subproc is True, output from subprocesses (typically hooks) will be
689 If subproc is True, output from subprocesses (typically hooks) will be
690 captured too.
690 captured too.
691
691
692 If labeled is True, any labels associated with buffered
692 If labeled is True, any labels associated with buffered
693 output will be handled. By default, this has no effect
693 output will be handled. By default, this has no effect
694 on the output returned, but extensions and GUI tools may
694 on the output returned, but extensions and GUI tools may
695 handle this argument and returned styled output. If output
695 handle this argument and returned styled output. If output
696 is being buffered so it can be captured and parsed or
696 is being buffered so it can be captured and parsed or
697 processed, labeled should not be set to True.
697 processed, labeled should not be set to True.
698 """
698 """
699 self._buffers.append([])
699 self._buffers.append([])
700 self._bufferstates.append((error, subproc, labeled))
700 self._bufferstates.append((error, subproc, labeled))
701 self._bufferapplylabels = labeled
701 self._bufferapplylabels = labeled
702
702
703 def popbuffer(self):
703 def popbuffer(self):
704 '''pop the last buffer and return the buffered output'''
704 '''pop the last buffer and return the buffered output'''
705 self._bufferstates.pop()
705 self._bufferstates.pop()
706 if self._bufferstates:
706 if self._bufferstates:
707 self._bufferapplylabels = self._bufferstates[-1][2]
707 self._bufferapplylabels = self._bufferstates[-1][2]
708 else:
708 else:
709 self._bufferapplylabels = None
709 self._bufferapplylabels = None
710
710
711 return "".join(self._buffers.pop())
711 return "".join(self._buffers.pop())
712
712
713 def write(self, *args, **opts):
713 def write(self, *args, **opts):
714 '''write args to output
714 '''write args to output
715
715
716 By default, this method simply writes to the buffer or stdout.
716 By default, this method simply writes to the buffer or stdout.
717 Color mode can be set on the UI class to have the output decorated
717 Color mode can be set on the UI class to have the output decorated
718 with color modifier before being written to stdout.
718 with color modifier before being written to stdout.
719
719
720 The color used is controlled by an optional keyword argument, "label".
720 The color used is controlled by an optional keyword argument, "label".
721 This should be a string containing label names separated by space.
721 This should be a string containing label names separated by space.
722 Label names take the form of "topic.type". For example, ui.debug()
722 Label names take the form of "topic.type". For example, ui.debug()
723 issues a label of "ui.debug".
723 issues a label of "ui.debug".
724
724
725 When labeling output for a specific command, a label of
725 When labeling output for a specific command, a label of
726 "cmdname.type" is recommended. For example, status issues
726 "cmdname.type" is recommended. For example, status issues
727 a label of "status.modified" for modified files.
727 a label of "status.modified" for modified files.
728 '''
728 '''
729 if self._buffers and not opts.get('prompt', False):
729 if self._buffers and not opts.get('prompt', False):
730 if self._bufferapplylabels:
730 if self._bufferapplylabels:
731 label = opts.get('label', '')
731 label = opts.get('label', '')
732 self._buffers[-1].extend(self.label(a, label) for a in args)
732 self._buffers[-1].extend(self.label(a, label) for a in args)
733 else:
733 else:
734 self._buffers[-1].extend(args)
734 self._buffers[-1].extend(args)
735 elif self._colormode == 'win32':
735 elif self._colormode == 'win32':
736 # windows color printing is its own can of crab, defer to
736 # windows color printing is its own can of crab, defer to
737 # the color module and that is it.
737 # the color module and that is it.
738 color.win32print(self, self._write, *args, **opts)
738 color.win32print(self, self._write, *args, **opts)
739 else:
739 else:
740 msgs = args
740 msgs = args
741 if self._colormode is not None:
741 if self._colormode is not None:
742 label = opts.get('label', '')
742 label = opts.get('label', '')
743 msgs = [self.label(a, label) for a in args]
743 msgs = [self.label(a, label) for a in args]
744 self._write(*msgs, **opts)
744 self._write(*msgs, **opts)
745
745
746 def _write(self, *msgs, **opts):
746 def _write(self, *msgs, **opts):
747 self._progclear()
747 self._progclear()
748 # opencode timeblockedsection because this is a critical path
748 # opencode timeblockedsection because this is a critical path
749 starttime = util.timer()
749 starttime = util.timer()
750 try:
750 try:
751 for a in msgs:
751 for a in msgs:
752 self.fout.write(a)
752 self.fout.write(a)
753 finally:
753 finally:
754 self._blockedtimes['stdio_blocked'] += \
754 self._blockedtimes['stdio_blocked'] += \
755 (util.timer() - starttime) * 1000
755 (util.timer() - starttime) * 1000
756
756
757 def write_err(self, *args, **opts):
757 def write_err(self, *args, **opts):
758 self._progclear()
758 self._progclear()
759 if self._bufferstates and self._bufferstates[-1][0]:
759 if self._bufferstates and self._bufferstates[-1][0]:
760 self.write(*args, **opts)
760 self.write(*args, **opts)
761 elif self._colormode == 'win32':
761 elif self._colormode == 'win32':
762 # windows color printing is its own can of crab, defer to
762 # windows color printing is its own can of crab, defer to
763 # the color module and that is it.
763 # the color module and that is it.
764 color.win32print(self, self._write_err, *args, **opts)
764 color.win32print(self, self._write_err, *args, **opts)
765 else:
765 else:
766 msgs = args
766 msgs = args
767 if self._colormode is not None:
767 if self._colormode is not None:
768 label = opts.get('label', '')
768 label = opts.get('label', '')
769 msgs = [self.label(a, label) for a in args]
769 msgs = [self.label(a, label) for a in args]
770 self._write_err(*msgs, **opts)
770 self._write_err(*msgs, **opts)
771
771
772 def _write_err(self, *msgs, **opts):
772 def _write_err(self, *msgs, **opts):
773 try:
773 try:
774 with self.timeblockedsection('stdio'):
774 with self.timeblockedsection('stdio'):
775 if not getattr(self.fout, 'closed', False):
775 if not getattr(self.fout, 'closed', False):
776 self.fout.flush()
776 self.fout.flush()
777 for a in msgs:
777 for a in msgs:
778 self.ferr.write(a)
778 self.ferr.write(a)
779 # stderr may be buffered under win32 when redirected to files,
779 # stderr may be buffered under win32 when redirected to files,
780 # including stdout.
780 # including stdout.
781 if not getattr(self.ferr, 'closed', False):
781 if not getattr(self.ferr, 'closed', False):
782 self.ferr.flush()
782 self.ferr.flush()
783 except IOError as inst:
783 except IOError as inst:
784 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
784 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
785 raise
785 raise
786
786
787 def flush(self):
787 def flush(self):
788 # opencode timeblockedsection because this is a critical path
788 # opencode timeblockedsection because this is a critical path
789 starttime = util.timer()
789 starttime = util.timer()
790 try:
790 try:
791 try: self.fout.flush()
791 try: self.fout.flush()
792 except (IOError, ValueError): pass
792 except (IOError, ValueError): pass
793 try: self.ferr.flush()
793 try: self.ferr.flush()
794 except (IOError, ValueError): pass
794 except (IOError, ValueError): pass
795 finally:
795 finally:
796 self._blockedtimes['stdio_blocked'] += \
796 self._blockedtimes['stdio_blocked'] += \
797 (util.timer() - starttime) * 1000
797 (util.timer() - starttime) * 1000
798
798
799 def _isatty(self, fh):
799 def _isatty(self, fh):
800 if self.configbool('ui', 'nontty', False):
800 if self.configbool('ui', 'nontty', False):
801 return False
801 return False
802 return util.isatty(fh)
802 return util.isatty(fh)
803
803
804 def disablepager(self):
804 def disablepager(self):
805 self._disablepager = True
805 self._disablepager = True
806
806
807 def pager(self, command):
807 def pager(self, command):
808 """Start a pager for subsequent command output.
808 """Start a pager for subsequent command output.
809
809
810 Commands which produce a long stream of output should call
810 Commands which produce a long stream of output should call
811 this function to activate the user's preferred pagination
811 this function to activate the user's preferred pagination
812 mechanism (which may be no pager). Calling this function
812 mechanism (which may be no pager). Calling this function
813 precludes any future use of interactive functionality, such as
813 precludes any future use of interactive functionality, such as
814 prompting the user or activating curses.
814 prompting the user or activating curses.
815
815
816 Args:
816 Args:
817 command: The full, non-aliased name of the command. That is, "log"
817 command: The full, non-aliased name of the command. That is, "log"
818 not "history, "summary" not "summ", etc.
818 not "history, "summary" not "summ", etc.
819 """
819 """
820 if (self._disablepager
820 if (self._disablepager
821 or self.pageractive
821 or self.pageractive
822 or command in self.configlist('pager', 'ignore')
822 or command in self.configlist('pager', 'ignore')
823 or not self.configbool('pager', 'enable', True)
823 or not self.configbool('pager', 'enable', True)
824 or not self.configbool('pager', 'attend-' + command, True)
824 or not self.configbool('pager', 'attend-' + command, True)
825 # TODO: if we want to allow HGPLAINEXCEPT=pager,
825 # TODO: if we want to allow HGPLAINEXCEPT=pager,
826 # formatted() will need some adjustment.
826 # formatted() will need some adjustment.
827 or not self.formatted()
827 or not self.formatted()
828 or self.plain()
828 or self.plain()
829 # TODO: expose debugger-enabled on the UI object
829 # TODO: expose debugger-enabled on the UI object
830 or '--debugger' in pycompat.sysargv):
830 or '--debugger' in pycompat.sysargv):
831 # We only want to paginate if the ui appears to be
831 # We only want to paginate if the ui appears to be
832 # interactive, the user didn't say HGPLAIN or
832 # interactive, the user didn't say HGPLAIN or
833 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
833 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
834 return
834 return
835
835
836 # TODO: add a "system defaults" config section so this default
836 # TODO: add a "system defaults" config section so this default
837 # of more(1) can be easily replaced with a global
837 # of more(1) can be easily replaced with a global
838 # configuration file. For example, on OS X the sane default is
838 # configuration file. For example, on OS X the sane default is
839 # less(1), not more(1), and on debian it's
839 # less(1), not more(1), and on debian it's
840 # sensible-pager(1). We should probably also give the system
840 # sensible-pager(1). We should probably also give the system
841 # default editor command similar treatment.
841 # default editor command similar treatment.
842 envpager = encoding.environ.get('PAGER', 'more')
842 envpager = encoding.environ.get('PAGER', 'more')
843 pagercmd = self.config('pager', 'pager', envpager)
843 pagercmd = self.config('pager', 'pager', envpager)
844 if not pagercmd:
844 if not pagercmd:
845 return
845 return
846
846
847 self.debug('starting pager for command %r\n' % command)
847 self.debug('starting pager for command %r\n' % command)
848 self.flush()
848 self.pageractive = True
849 self.pageractive = True
849 # Preserve the formatted-ness of the UI. This is important
850 # Preserve the formatted-ness of the UI. This is important
850 # because we mess with stdout, which might confuse
851 # because we mess with stdout, which might confuse
851 # auto-detection of things being formatted.
852 # auto-detection of things being formatted.
852 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
853 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
853 self.setconfig('ui', 'interactive', False, 'pager')
854 self.setconfig('ui', 'interactive', False, 'pager')
854 if util.safehasattr(signal, "SIGPIPE"):
855 if util.safehasattr(signal, "SIGPIPE"):
855 signal.signal(signal.SIGPIPE, _catchterm)
856 signal.signal(signal.SIGPIPE, _catchterm)
856 self._runpager(pagercmd)
857 self._runpager(pagercmd)
857
858
858 def _runpager(self, command):
859 def _runpager(self, command):
859 """Actually start the pager and set up file descriptors.
860 """Actually start the pager and set up file descriptors.
860
861
861 This is separate in part so that extensions (like chg) can
862 This is separate in part so that extensions (like chg) can
862 override how a pager is invoked.
863 override how a pager is invoked.
863 """
864 """
864 if command == 'cat':
865 if command == 'cat':
865 # Save ourselves some work.
866 # Save ourselves some work.
866 return
867 return
867 # If the command doesn't contain any of these characters, we
868 # If the command doesn't contain any of these characters, we
868 # assume it's a binary and exec it directly. This means for
869 # assume it's a binary and exec it directly. This means for
869 # simple pager command configurations, we can degrade
870 # simple pager command configurations, we can degrade
870 # gracefully and tell the user about their broken pager.
871 # gracefully and tell the user about their broken pager.
871 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
872 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
872 try:
873 try:
873 pager = subprocess.Popen(
874 pager = subprocess.Popen(
874 command, shell=shell, bufsize=-1,
875 command, shell=shell, bufsize=-1,
875 close_fds=util.closefds, stdin=subprocess.PIPE,
876 close_fds=util.closefds, stdin=subprocess.PIPE,
876 stdout=util.stdout, stderr=util.stderr)
877 stdout=util.stdout, stderr=util.stderr)
877 except OSError as e:
878 except OSError as e:
878 if e.errno == errno.ENOENT and not shell:
879 if e.errno == errno.ENOENT and not shell:
879 self.warn(_("missing pager command '%s', skipping pager\n")
880 self.warn(_("missing pager command '%s', skipping pager\n")
880 % command)
881 % command)
881 return
882 return
882 raise
883 raise
883
884
884 # back up original file descriptors
885 # back up original file descriptors
885 stdoutfd = os.dup(util.stdout.fileno())
886 stdoutfd = os.dup(util.stdout.fileno())
886 stderrfd = os.dup(util.stderr.fileno())
887 stderrfd = os.dup(util.stderr.fileno())
887
888
888 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
889 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
889 if self._isatty(util.stderr):
890 if self._isatty(util.stderr):
890 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
891 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
891
892
892 @atexit.register
893 @atexit.register
893 def killpager():
894 def killpager():
894 if util.safehasattr(signal, "SIGINT"):
895 if util.safehasattr(signal, "SIGINT"):
895 signal.signal(signal.SIGINT, signal.SIG_IGN)
896 signal.signal(signal.SIGINT, signal.SIG_IGN)
896 # restore original fds, closing pager.stdin copies in the process
897 # restore original fds, closing pager.stdin copies in the process
897 os.dup2(stdoutfd, util.stdout.fileno())
898 os.dup2(stdoutfd, util.stdout.fileno())
898 os.dup2(stderrfd, util.stderr.fileno())
899 os.dup2(stderrfd, util.stderr.fileno())
899 pager.stdin.close()
900 pager.stdin.close()
900 pager.wait()
901 pager.wait()
901
902
902 def interface(self, feature):
903 def interface(self, feature):
903 """what interface to use for interactive console features?
904 """what interface to use for interactive console features?
904
905
905 The interface is controlled by the value of `ui.interface` but also by
906 The interface is controlled by the value of `ui.interface` but also by
906 the value of feature-specific configuration. For example:
907 the value of feature-specific configuration. For example:
907
908
908 ui.interface.histedit = text
909 ui.interface.histedit = text
909 ui.interface.chunkselector = curses
910 ui.interface.chunkselector = curses
910
911
911 Here the features are "histedit" and "chunkselector".
912 Here the features are "histedit" and "chunkselector".
912
913
913 The configuration above means that the default interfaces for commands
914 The configuration above means that the default interfaces for commands
914 is curses, the interface for histedit is text and the interface for
915 is curses, the interface for histedit is text and the interface for
915 selecting chunk is crecord (the best curses interface available).
916 selecting chunk is crecord (the best curses interface available).
916
917
917 Consider the following example:
918 Consider the following example:
918 ui.interface = curses
919 ui.interface = curses
919 ui.interface.histedit = text
920 ui.interface.histedit = text
920
921
921 Then histedit will use the text interface and chunkselector will use
922 Then histedit will use the text interface and chunkselector will use
922 the default curses interface (crecord at the moment).
923 the default curses interface (crecord at the moment).
923 """
924 """
924 alldefaults = frozenset(["text", "curses"])
925 alldefaults = frozenset(["text", "curses"])
925
926
926 featureinterfaces = {
927 featureinterfaces = {
927 "chunkselector": [
928 "chunkselector": [
928 "text",
929 "text",
929 "curses",
930 "curses",
930 ]
931 ]
931 }
932 }
932
933
933 # Feature-specific interface
934 # Feature-specific interface
934 if feature not in featureinterfaces.keys():
935 if feature not in featureinterfaces.keys():
935 # Programming error, not user error
936 # Programming error, not user error
936 raise ValueError("Unknown feature requested %s" % feature)
937 raise ValueError("Unknown feature requested %s" % feature)
937
938
938 availableinterfaces = frozenset(featureinterfaces[feature])
939 availableinterfaces = frozenset(featureinterfaces[feature])
939 if alldefaults > availableinterfaces:
940 if alldefaults > availableinterfaces:
940 # Programming error, not user error. We need a use case to
941 # Programming error, not user error. We need a use case to
941 # define the right thing to do here.
942 # define the right thing to do here.
942 raise ValueError(
943 raise ValueError(
943 "Feature %s does not handle all default interfaces" %
944 "Feature %s does not handle all default interfaces" %
944 feature)
945 feature)
945
946
946 if self.plain():
947 if self.plain():
947 return "text"
948 return "text"
948
949
949 # Default interface for all the features
950 # Default interface for all the features
950 defaultinterface = "text"
951 defaultinterface = "text"
951 i = self.config("ui", "interface", None)
952 i = self.config("ui", "interface", None)
952 if i in alldefaults:
953 if i in alldefaults:
953 defaultinterface = i
954 defaultinterface = i
954
955
955 choseninterface = defaultinterface
956 choseninterface = defaultinterface
956 f = self.config("ui", "interface.%s" % feature, None)
957 f = self.config("ui", "interface.%s" % feature, None)
957 if f in availableinterfaces:
958 if f in availableinterfaces:
958 choseninterface = f
959 choseninterface = f
959
960
960 if i is not None and defaultinterface != i:
961 if i is not None and defaultinterface != i:
961 if f is not None:
962 if f is not None:
962 self.warn(_("invalid value for ui.interface: %s\n") %
963 self.warn(_("invalid value for ui.interface: %s\n") %
963 (i,))
964 (i,))
964 else:
965 else:
965 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
966 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
966 (i, choseninterface))
967 (i, choseninterface))
967 if f is not None and choseninterface != f:
968 if f is not None and choseninterface != f:
968 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
969 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
969 (feature, f, choseninterface))
970 (feature, f, choseninterface))
970
971
971 return choseninterface
972 return choseninterface
972
973
973 def interactive(self):
974 def interactive(self):
974 '''is interactive input allowed?
975 '''is interactive input allowed?
975
976
976 An interactive session is a session where input can be reasonably read
977 An interactive session is a session where input can be reasonably read
977 from `sys.stdin'. If this function returns false, any attempt to read
978 from `sys.stdin'. If this function returns false, any attempt to read
978 from stdin should fail with an error, unless a sensible default has been
979 from stdin should fail with an error, unless a sensible default has been
979 specified.
980 specified.
980
981
981 Interactiveness is triggered by the value of the `ui.interactive'
982 Interactiveness is triggered by the value of the `ui.interactive'
982 configuration variable or - if it is unset - when `sys.stdin' points
983 configuration variable or - if it is unset - when `sys.stdin' points
983 to a terminal device.
984 to a terminal device.
984
985
985 This function refers to input only; for output, see `ui.formatted()'.
986 This function refers to input only; for output, see `ui.formatted()'.
986 '''
987 '''
987 i = self.configbool("ui", "interactive", None)
988 i = self.configbool("ui", "interactive", None)
988 if i is None:
989 if i is None:
989 # some environments replace stdin without implementing isatty
990 # some environments replace stdin without implementing isatty
990 # usually those are non-interactive
991 # usually those are non-interactive
991 return self._isatty(self.fin)
992 return self._isatty(self.fin)
992
993
993 return i
994 return i
994
995
995 def termwidth(self):
996 def termwidth(self):
996 '''how wide is the terminal in columns?
997 '''how wide is the terminal in columns?
997 '''
998 '''
998 if 'COLUMNS' in encoding.environ:
999 if 'COLUMNS' in encoding.environ:
999 try:
1000 try:
1000 return int(encoding.environ['COLUMNS'])
1001 return int(encoding.environ['COLUMNS'])
1001 except ValueError:
1002 except ValueError:
1002 pass
1003 pass
1003 return scmutil.termsize(self)[0]
1004 return scmutil.termsize(self)[0]
1004
1005
1005 def formatted(self):
1006 def formatted(self):
1006 '''should formatted output be used?
1007 '''should formatted output be used?
1007
1008
1008 It is often desirable to format the output to suite the output medium.
1009 It is often desirable to format the output to suite the output medium.
1009 Examples of this are truncating long lines or colorizing messages.
1010 Examples of this are truncating long lines or colorizing messages.
1010 However, this is not often not desirable when piping output into other
1011 However, this is not often not desirable when piping output into other
1011 utilities, e.g. `grep'.
1012 utilities, e.g. `grep'.
1012
1013
1013 Formatted output is triggered by the value of the `ui.formatted'
1014 Formatted output is triggered by the value of the `ui.formatted'
1014 configuration variable or - if it is unset - when `sys.stdout' points
1015 configuration variable or - if it is unset - when `sys.stdout' points
1015 to a terminal device. Please note that `ui.formatted' should be
1016 to a terminal device. Please note that `ui.formatted' should be
1016 considered an implementation detail; it is not intended for use outside
1017 considered an implementation detail; it is not intended for use outside
1017 Mercurial or its extensions.
1018 Mercurial or its extensions.
1018
1019
1019 This function refers to output only; for input, see `ui.interactive()'.
1020 This function refers to output only; for input, see `ui.interactive()'.
1020 This function always returns false when in plain mode, see `ui.plain()'.
1021 This function always returns false when in plain mode, see `ui.plain()'.
1021 '''
1022 '''
1022 if self.plain():
1023 if self.plain():
1023 return False
1024 return False
1024
1025
1025 i = self.configbool("ui", "formatted", None)
1026 i = self.configbool("ui", "formatted", None)
1026 if i is None:
1027 if i is None:
1027 # some environments replace stdout without implementing isatty
1028 # some environments replace stdout without implementing isatty
1028 # usually those are non-interactive
1029 # usually those are non-interactive
1029 return self._isatty(self.fout)
1030 return self._isatty(self.fout)
1030
1031
1031 return i
1032 return i
1032
1033
1033 def _readline(self, prompt=''):
1034 def _readline(self, prompt=''):
1034 if self._isatty(self.fin):
1035 if self._isatty(self.fin):
1035 try:
1036 try:
1036 # magically add command line editing support, where
1037 # magically add command line editing support, where
1037 # available
1038 # available
1038 import readline
1039 import readline
1039 # force demandimport to really load the module
1040 # force demandimport to really load the module
1040 readline.read_history_file
1041 readline.read_history_file
1041 # windows sometimes raises something other than ImportError
1042 # windows sometimes raises something other than ImportError
1042 except Exception:
1043 except Exception:
1043 pass
1044 pass
1044
1045
1045 # call write() so output goes through subclassed implementation
1046 # call write() so output goes through subclassed implementation
1046 # e.g. color extension on Windows
1047 # e.g. color extension on Windows
1047 self.write(prompt, prompt=True)
1048 self.write(prompt, prompt=True)
1048
1049
1049 # instead of trying to emulate raw_input, swap (self.fin,
1050 # instead of trying to emulate raw_input, swap (self.fin,
1050 # self.fout) with (sys.stdin, sys.stdout)
1051 # self.fout) with (sys.stdin, sys.stdout)
1051 oldin = sys.stdin
1052 oldin = sys.stdin
1052 oldout = sys.stdout
1053 oldout = sys.stdout
1053 sys.stdin = self.fin
1054 sys.stdin = self.fin
1054 sys.stdout = self.fout
1055 sys.stdout = self.fout
1055 # prompt ' ' must exist; otherwise readline may delete entire line
1056 # prompt ' ' must exist; otherwise readline may delete entire line
1056 # - http://bugs.python.org/issue12833
1057 # - http://bugs.python.org/issue12833
1057 with self.timeblockedsection('stdio'):
1058 with self.timeblockedsection('stdio'):
1058 line = raw_input(' ')
1059 line = raw_input(' ')
1059 sys.stdin = oldin
1060 sys.stdin = oldin
1060 sys.stdout = oldout
1061 sys.stdout = oldout
1061
1062
1062 # When stdin is in binary mode on Windows, it can cause
1063 # When stdin is in binary mode on Windows, it can cause
1063 # raw_input() to emit an extra trailing carriage return
1064 # raw_input() to emit an extra trailing carriage return
1064 if os.linesep == '\r\n' and line and line[-1] == '\r':
1065 if os.linesep == '\r\n' and line and line[-1] == '\r':
1065 line = line[:-1]
1066 line = line[:-1]
1066 return line
1067 return line
1067
1068
1068 def prompt(self, msg, default="y"):
1069 def prompt(self, msg, default="y"):
1069 """Prompt user with msg, read response.
1070 """Prompt user with msg, read response.
1070 If ui is not interactive, the default is returned.
1071 If ui is not interactive, the default is returned.
1071 """
1072 """
1072 if not self.interactive():
1073 if not self.interactive():
1073 self.write(msg, ' ', default or '', "\n")
1074 self.write(msg, ' ', default or '', "\n")
1074 return default
1075 return default
1075 try:
1076 try:
1076 r = self._readline(self.label(msg, 'ui.prompt'))
1077 r = self._readline(self.label(msg, 'ui.prompt'))
1077 if not r:
1078 if not r:
1078 r = default
1079 r = default
1079 if self.configbool('ui', 'promptecho'):
1080 if self.configbool('ui', 'promptecho'):
1080 self.write(r, "\n")
1081 self.write(r, "\n")
1081 return r
1082 return r
1082 except EOFError:
1083 except EOFError:
1083 raise error.ResponseExpected()
1084 raise error.ResponseExpected()
1084
1085
1085 @staticmethod
1086 @staticmethod
1086 def extractchoices(prompt):
1087 def extractchoices(prompt):
1087 """Extract prompt message and list of choices from specified prompt.
1088 """Extract prompt message and list of choices from specified prompt.
1088
1089
1089 This returns tuple "(message, choices)", and "choices" is the
1090 This returns tuple "(message, choices)", and "choices" is the
1090 list of tuple "(response character, text without &)".
1091 list of tuple "(response character, text without &)".
1091
1092
1092 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1093 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1093 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1094 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1094 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1095 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1095 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1096 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1096 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1097 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1097 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1098 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1098 """
1099 """
1099
1100
1100 # Sadly, the prompt string may have been built with a filename
1101 # Sadly, the prompt string may have been built with a filename
1101 # containing "$$" so let's try to find the first valid-looking
1102 # containing "$$" so let's try to find the first valid-looking
1102 # prompt to start parsing. Sadly, we also can't rely on
1103 # prompt to start parsing. Sadly, we also can't rely on
1103 # choices containing spaces, ASCII, or basically anything
1104 # choices containing spaces, ASCII, or basically anything
1104 # except an ampersand followed by a character.
1105 # except an ampersand followed by a character.
1105 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1106 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1106 msg = m.group(1)
1107 msg = m.group(1)
1107 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1108 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1108 return (msg,
1109 return (msg,
1109 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1110 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1110 for s in choices])
1111 for s in choices])
1111
1112
1112 def promptchoice(self, prompt, default=0):
1113 def promptchoice(self, prompt, default=0):
1113 """Prompt user with a message, read response, and ensure it matches
1114 """Prompt user with a message, read response, and ensure it matches
1114 one of the provided choices. The prompt is formatted as follows:
1115 one of the provided choices. The prompt is formatted as follows:
1115
1116
1116 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1117 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1117
1118
1118 The index of the choice is returned. Responses are case
1119 The index of the choice is returned. Responses are case
1119 insensitive. If ui is not interactive, the default is
1120 insensitive. If ui is not interactive, the default is
1120 returned.
1121 returned.
1121 """
1122 """
1122
1123
1123 msg, choices = self.extractchoices(prompt)
1124 msg, choices = self.extractchoices(prompt)
1124 resps = [r for r, t in choices]
1125 resps = [r for r, t in choices]
1125 while True:
1126 while True:
1126 r = self.prompt(msg, resps[default])
1127 r = self.prompt(msg, resps[default])
1127 if r.lower() in resps:
1128 if r.lower() in resps:
1128 return resps.index(r.lower())
1129 return resps.index(r.lower())
1129 self.write(_("unrecognized response\n"))
1130 self.write(_("unrecognized response\n"))
1130
1131
1131 def getpass(self, prompt=None, default=None):
1132 def getpass(self, prompt=None, default=None):
1132 if not self.interactive():
1133 if not self.interactive():
1133 return default
1134 return default
1134 try:
1135 try:
1135 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1136 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1136 # disable getpass() only if explicitly specified. it's still valid
1137 # disable getpass() only if explicitly specified. it's still valid
1137 # to interact with tty even if fin is not a tty.
1138 # to interact with tty even if fin is not a tty.
1138 with self.timeblockedsection('stdio'):
1139 with self.timeblockedsection('stdio'):
1139 if self.configbool('ui', 'nontty'):
1140 if self.configbool('ui', 'nontty'):
1140 l = self.fin.readline()
1141 l = self.fin.readline()
1141 if not l:
1142 if not l:
1142 raise EOFError
1143 raise EOFError
1143 return l.rstrip('\n')
1144 return l.rstrip('\n')
1144 else:
1145 else:
1145 return getpass.getpass('')
1146 return getpass.getpass('')
1146 except EOFError:
1147 except EOFError:
1147 raise error.ResponseExpected()
1148 raise error.ResponseExpected()
1148 def status(self, *msg, **opts):
1149 def status(self, *msg, **opts):
1149 '''write status message to output (if ui.quiet is False)
1150 '''write status message to output (if ui.quiet is False)
1150
1151
1151 This adds an output label of "ui.status".
1152 This adds an output label of "ui.status".
1152 '''
1153 '''
1153 if not self.quiet:
1154 if not self.quiet:
1154 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1155 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1155 self.write(*msg, **opts)
1156 self.write(*msg, **opts)
1156 def warn(self, *msg, **opts):
1157 def warn(self, *msg, **opts):
1157 '''write warning message to output (stderr)
1158 '''write warning message to output (stderr)
1158
1159
1159 This adds an output label of "ui.warning".
1160 This adds an output label of "ui.warning".
1160 '''
1161 '''
1161 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1162 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1162 self.write_err(*msg, **opts)
1163 self.write_err(*msg, **opts)
1163 def note(self, *msg, **opts):
1164 def note(self, *msg, **opts):
1164 '''write note to output (if ui.verbose is True)
1165 '''write note to output (if ui.verbose is True)
1165
1166
1166 This adds an output label of "ui.note".
1167 This adds an output label of "ui.note".
1167 '''
1168 '''
1168 if self.verbose:
1169 if self.verbose:
1169 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1170 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1170 self.write(*msg, **opts)
1171 self.write(*msg, **opts)
1171 def debug(self, *msg, **opts):
1172 def debug(self, *msg, **opts):
1172 '''write debug message to output (if ui.debugflag is True)
1173 '''write debug message to output (if ui.debugflag is True)
1173
1174
1174 This adds an output label of "ui.debug".
1175 This adds an output label of "ui.debug".
1175 '''
1176 '''
1176 if self.debugflag:
1177 if self.debugflag:
1177 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1178 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1178 self.write(*msg, **opts)
1179 self.write(*msg, **opts)
1179
1180
1180 def edit(self, text, user, extra=None, editform=None, pending=None,
1181 def edit(self, text, user, extra=None, editform=None, pending=None,
1181 repopath=None):
1182 repopath=None):
1182 extra_defaults = {
1183 extra_defaults = {
1183 'prefix': 'editor',
1184 'prefix': 'editor',
1184 'suffix': '.txt',
1185 'suffix': '.txt',
1185 }
1186 }
1186 if extra is not None:
1187 if extra is not None:
1187 extra_defaults.update(extra)
1188 extra_defaults.update(extra)
1188 extra = extra_defaults
1189 extra = extra_defaults
1189
1190
1190 rdir = None
1191 rdir = None
1191 if self.configbool('experimental', 'editortmpinhg'):
1192 if self.configbool('experimental', 'editortmpinhg'):
1192 rdir = repopath
1193 rdir = repopath
1193 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1194 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1194 suffix=extra['suffix'], text=True,
1195 suffix=extra['suffix'], text=True,
1195 dir=rdir)
1196 dir=rdir)
1196 try:
1197 try:
1197 f = os.fdopen(fd, pycompat.sysstr("w"))
1198 f = os.fdopen(fd, pycompat.sysstr("w"))
1198 f.write(text)
1199 f.write(text)
1199 f.close()
1200 f.close()
1200
1201
1201 environ = {'HGUSER': user}
1202 environ = {'HGUSER': user}
1202 if 'transplant_source' in extra:
1203 if 'transplant_source' in extra:
1203 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1204 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1204 for label in ('intermediate-source', 'source', 'rebase_source'):
1205 for label in ('intermediate-source', 'source', 'rebase_source'):
1205 if label in extra:
1206 if label in extra:
1206 environ.update({'HGREVISION': extra[label]})
1207 environ.update({'HGREVISION': extra[label]})
1207 break
1208 break
1208 if editform:
1209 if editform:
1209 environ.update({'HGEDITFORM': editform})
1210 environ.update({'HGEDITFORM': editform})
1210 if pending:
1211 if pending:
1211 environ.update({'HG_PENDING': pending})
1212 environ.update({'HG_PENDING': pending})
1212
1213
1213 editor = self.geteditor()
1214 editor = self.geteditor()
1214
1215
1215 self.system("%s \"%s\"" % (editor, name),
1216 self.system("%s \"%s\"" % (editor, name),
1216 environ=environ,
1217 environ=environ,
1217 onerr=error.Abort, errprefix=_("edit failed"),
1218 onerr=error.Abort, errprefix=_("edit failed"),
1218 blockedtag='editor')
1219 blockedtag='editor')
1219
1220
1220 f = open(name)
1221 f = open(name)
1221 t = f.read()
1222 t = f.read()
1222 f.close()
1223 f.close()
1223 finally:
1224 finally:
1224 os.unlink(name)
1225 os.unlink(name)
1225
1226
1226 return t
1227 return t
1227
1228
1228 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1229 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1229 blockedtag=None):
1230 blockedtag=None):
1230 '''execute shell command with appropriate output stream. command
1231 '''execute shell command with appropriate output stream. command
1231 output will be redirected if fout is not stdout.
1232 output will be redirected if fout is not stdout.
1232
1233
1233 if command fails and onerr is None, return status, else raise onerr
1234 if command fails and onerr is None, return status, else raise onerr
1234 object as exception.
1235 object as exception.
1235 '''
1236 '''
1236 if blockedtag is None:
1237 if blockedtag is None:
1237 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1238 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1238 out = self.fout
1239 out = self.fout
1239 if any(s[1] for s in self._bufferstates):
1240 if any(s[1] for s in self._bufferstates):
1240 out = self
1241 out = self
1241 with self.timeblockedsection(blockedtag):
1242 with self.timeblockedsection(blockedtag):
1242 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1243 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1243 if rc and onerr:
1244 if rc and onerr:
1244 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1245 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1245 util.explainexit(rc)[0])
1246 util.explainexit(rc)[0])
1246 if errprefix:
1247 if errprefix:
1247 errmsg = '%s: %s' % (errprefix, errmsg)
1248 errmsg = '%s: %s' % (errprefix, errmsg)
1248 raise onerr(errmsg)
1249 raise onerr(errmsg)
1249 return rc
1250 return rc
1250
1251
1251 def _runsystem(self, cmd, environ, cwd, out):
1252 def _runsystem(self, cmd, environ, cwd, out):
1252 """actually execute the given shell command (can be overridden by
1253 """actually execute the given shell command (can be overridden by
1253 extensions like chg)"""
1254 extensions like chg)"""
1254 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1255 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1255
1256
1256 def traceback(self, exc=None, force=False):
1257 def traceback(self, exc=None, force=False):
1257 '''print exception traceback if traceback printing enabled or forced.
1258 '''print exception traceback if traceback printing enabled or forced.
1258 only to call in exception handler. returns true if traceback
1259 only to call in exception handler. returns true if traceback
1259 printed.'''
1260 printed.'''
1260 if self.tracebackflag or force:
1261 if self.tracebackflag or force:
1261 if exc is None:
1262 if exc is None:
1262 exc = sys.exc_info()
1263 exc = sys.exc_info()
1263 cause = getattr(exc[1], 'cause', None)
1264 cause = getattr(exc[1], 'cause', None)
1264
1265
1265 if cause is not None:
1266 if cause is not None:
1266 causetb = traceback.format_tb(cause[2])
1267 causetb = traceback.format_tb(cause[2])
1267 exctb = traceback.format_tb(exc[2])
1268 exctb = traceback.format_tb(exc[2])
1268 exconly = traceback.format_exception_only(cause[0], cause[1])
1269 exconly = traceback.format_exception_only(cause[0], cause[1])
1269
1270
1270 # exclude frame where 'exc' was chained and rethrown from exctb
1271 # exclude frame where 'exc' was chained and rethrown from exctb
1271 self.write_err('Traceback (most recent call last):\n',
1272 self.write_err('Traceback (most recent call last):\n',
1272 ''.join(exctb[:-1]),
1273 ''.join(exctb[:-1]),
1273 ''.join(causetb),
1274 ''.join(causetb),
1274 ''.join(exconly))
1275 ''.join(exconly))
1275 else:
1276 else:
1276 output = traceback.format_exception(exc[0], exc[1], exc[2])
1277 output = traceback.format_exception(exc[0], exc[1], exc[2])
1277 data = r''.join(output)
1278 data = r''.join(output)
1278 if pycompat.ispy3:
1279 if pycompat.ispy3:
1279 enc = pycompat.sysstr(encoding.encoding)
1280 enc = pycompat.sysstr(encoding.encoding)
1280 data = data.encode(enc, errors=r'replace')
1281 data = data.encode(enc, errors=r'replace')
1281 self.write_err(data)
1282 self.write_err(data)
1282 return self.tracebackflag or force
1283 return self.tracebackflag or force
1283
1284
1284 def geteditor(self):
1285 def geteditor(self):
1285 '''return editor to use'''
1286 '''return editor to use'''
1286 if pycompat.sysplatform == 'plan9':
1287 if pycompat.sysplatform == 'plan9':
1287 # vi is the MIPS instruction simulator on Plan 9. We
1288 # vi is the MIPS instruction simulator on Plan 9. We
1288 # instead default to E to plumb commit messages to
1289 # instead default to E to plumb commit messages to
1289 # avoid confusion.
1290 # avoid confusion.
1290 editor = 'E'
1291 editor = 'E'
1291 else:
1292 else:
1292 editor = 'vi'
1293 editor = 'vi'
1293 return (encoding.environ.get("HGEDITOR") or
1294 return (encoding.environ.get("HGEDITOR") or
1294 self.config("ui", "editor") or
1295 self.config("ui", "editor") or
1295 encoding.environ.get("VISUAL") or
1296 encoding.environ.get("VISUAL") or
1296 encoding.environ.get("EDITOR", editor))
1297 encoding.environ.get("EDITOR", editor))
1297
1298
1298 @util.propertycache
1299 @util.propertycache
1299 def _progbar(self):
1300 def _progbar(self):
1300 """setup the progbar singleton to the ui object"""
1301 """setup the progbar singleton to the ui object"""
1301 if (self.quiet or self.debugflag
1302 if (self.quiet or self.debugflag
1302 or self.configbool('progress', 'disable', False)
1303 or self.configbool('progress', 'disable', False)
1303 or not progress.shouldprint(self)):
1304 or not progress.shouldprint(self)):
1304 return None
1305 return None
1305 return getprogbar(self)
1306 return getprogbar(self)
1306
1307
1307 def _progclear(self):
1308 def _progclear(self):
1308 """clear progress bar output if any. use it before any output"""
1309 """clear progress bar output if any. use it before any output"""
1309 if '_progbar' not in vars(self): # nothing loaded yet
1310 if '_progbar' not in vars(self): # nothing loaded yet
1310 return
1311 return
1311 if self._progbar is not None and self._progbar.printed:
1312 if self._progbar is not None and self._progbar.printed:
1312 self._progbar.clear()
1313 self._progbar.clear()
1313
1314
1314 def progress(self, topic, pos, item="", unit="", total=None):
1315 def progress(self, topic, pos, item="", unit="", total=None):
1315 '''show a progress message
1316 '''show a progress message
1316
1317
1317 By default a textual progress bar will be displayed if an operation
1318 By default a textual progress bar will be displayed if an operation
1318 takes too long. 'topic' is the current operation, 'item' is a
1319 takes too long. 'topic' is the current operation, 'item' is a
1319 non-numeric marker of the current position (i.e. the currently
1320 non-numeric marker of the current position (i.e. the currently
1320 in-process file), 'pos' is the current numeric position (i.e.
1321 in-process file), 'pos' is the current numeric position (i.e.
1321 revision, bytes, etc.), unit is a corresponding unit label,
1322 revision, bytes, etc.), unit is a corresponding unit label,
1322 and total is the highest expected pos.
1323 and total is the highest expected pos.
1323
1324
1324 Multiple nested topics may be active at a time.
1325 Multiple nested topics may be active at a time.
1325
1326
1326 All topics should be marked closed by setting pos to None at
1327 All topics should be marked closed by setting pos to None at
1327 termination.
1328 termination.
1328 '''
1329 '''
1329 if self._progbar is not None:
1330 if self._progbar is not None:
1330 self._progbar.progress(topic, pos, item=item, unit=unit,
1331 self._progbar.progress(topic, pos, item=item, unit=unit,
1331 total=total)
1332 total=total)
1332 if pos is None or not self.configbool('progress', 'debug'):
1333 if pos is None or not self.configbool('progress', 'debug'):
1333 return
1334 return
1334
1335
1335 if unit:
1336 if unit:
1336 unit = ' ' + unit
1337 unit = ' ' + unit
1337 if item:
1338 if item:
1338 item = ' ' + item
1339 item = ' ' + item
1339
1340
1340 if total:
1341 if total:
1341 pct = 100.0 * pos / total
1342 pct = 100.0 * pos / total
1342 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1343 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1343 % (topic, item, pos, total, unit, pct))
1344 % (topic, item, pos, total, unit, pct))
1344 else:
1345 else:
1345 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1346 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1346
1347
1347 def log(self, service, *msg, **opts):
1348 def log(self, service, *msg, **opts):
1348 '''hook for logging facility extensions
1349 '''hook for logging facility extensions
1349
1350
1350 service should be a readily-identifiable subsystem, which will
1351 service should be a readily-identifiable subsystem, which will
1351 allow filtering.
1352 allow filtering.
1352
1353
1353 *msg should be a newline-terminated format string to log, and
1354 *msg should be a newline-terminated format string to log, and
1354 then any values to %-format into that format string.
1355 then any values to %-format into that format string.
1355
1356
1356 **opts currently has no defined meanings.
1357 **opts currently has no defined meanings.
1357 '''
1358 '''
1358
1359
1359 def label(self, msg, label):
1360 def label(self, msg, label):
1360 '''style msg based on supplied label
1361 '''style msg based on supplied label
1361
1362
1362 If some color mode is enabled, this will add the necessary control
1363 If some color mode is enabled, this will add the necessary control
1363 characters to apply such color. In addition, 'debug' color mode adds
1364 characters to apply such color. In addition, 'debug' color mode adds
1364 markup showing which label affects a piece of text.
1365 markup showing which label affects a piece of text.
1365
1366
1366 ui.write(s, 'label') is equivalent to
1367 ui.write(s, 'label') is equivalent to
1367 ui.write(ui.label(s, 'label')).
1368 ui.write(ui.label(s, 'label')).
1368 '''
1369 '''
1369 if self._colormode is not None:
1370 if self._colormode is not None:
1370 return color.colorlabel(self, msg, label)
1371 return color.colorlabel(self, msg, label)
1371 return msg
1372 return msg
1372
1373
1373 def develwarn(self, msg, stacklevel=1, config=None):
1374 def develwarn(self, msg, stacklevel=1, config=None):
1374 """issue a developer warning message
1375 """issue a developer warning message
1375
1376
1376 Use 'stacklevel' to report the offender some layers further up in the
1377 Use 'stacklevel' to report the offender some layers further up in the
1377 stack.
1378 stack.
1378 """
1379 """
1379 if not self.configbool('devel', 'all-warnings'):
1380 if not self.configbool('devel', 'all-warnings'):
1380 if config is not None and not self.configbool('devel', config):
1381 if config is not None and not self.configbool('devel', config):
1381 return
1382 return
1382 msg = 'devel-warn: ' + msg
1383 msg = 'devel-warn: ' + msg
1383 stacklevel += 1 # get in develwarn
1384 stacklevel += 1 # get in develwarn
1384 if self.tracebackflag:
1385 if self.tracebackflag:
1385 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1386 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1386 self.log('develwarn', '%s at:\n%s' %
1387 self.log('develwarn', '%s at:\n%s' %
1387 (msg, ''.join(util.getstackframes(stacklevel))))
1388 (msg, ''.join(util.getstackframes(stacklevel))))
1388 else:
1389 else:
1389 curframe = inspect.currentframe()
1390 curframe = inspect.currentframe()
1390 calframe = inspect.getouterframes(curframe, 2)
1391 calframe = inspect.getouterframes(curframe, 2)
1391 self.write_err('%s at: %s:%s (%s)\n'
1392 self.write_err('%s at: %s:%s (%s)\n'
1392 % ((msg,) + calframe[stacklevel][1:4]))
1393 % ((msg,) + calframe[stacklevel][1:4]))
1393 self.log('develwarn', '%s at: %s:%s (%s)\n',
1394 self.log('develwarn', '%s at: %s:%s (%s)\n',
1394 msg, *calframe[stacklevel][1:4])
1395 msg, *calframe[stacklevel][1:4])
1395 curframe = calframe = None # avoid cycles
1396 curframe = calframe = None # avoid cycles
1396
1397
1397 def deprecwarn(self, msg, version):
1398 def deprecwarn(self, msg, version):
1398 """issue a deprecation warning
1399 """issue a deprecation warning
1399
1400
1400 - msg: message explaining what is deprecated and how to upgrade,
1401 - msg: message explaining what is deprecated and how to upgrade,
1401 - version: last version where the API will be supported,
1402 - version: last version where the API will be supported,
1402 """
1403 """
1403 if not (self.configbool('devel', 'all-warnings')
1404 if not (self.configbool('devel', 'all-warnings')
1404 or self.configbool('devel', 'deprec-warn')):
1405 or self.configbool('devel', 'deprec-warn')):
1405 return
1406 return
1406 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1407 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1407 " update your code.)") % version
1408 " update your code.)") % version
1408 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1409 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1409
1410
1410 def exportableenviron(self):
1411 def exportableenviron(self):
1411 """The environment variables that are safe to export, e.g. through
1412 """The environment variables that are safe to export, e.g. through
1412 hgweb.
1413 hgweb.
1413 """
1414 """
1414 return self._exportableenviron
1415 return self._exportableenviron
1415
1416
1416 @contextlib.contextmanager
1417 @contextlib.contextmanager
1417 def configoverride(self, overrides, source=""):
1418 def configoverride(self, overrides, source=""):
1418 """Context manager for temporary config overrides
1419 """Context manager for temporary config overrides
1419 `overrides` must be a dict of the following structure:
1420 `overrides` must be a dict of the following structure:
1420 {(section, name) : value}"""
1421 {(section, name) : value}"""
1421 backups = {}
1422 backups = {}
1422 try:
1423 try:
1423 for (section, name), value in overrides.items():
1424 for (section, name), value in overrides.items():
1424 backups[(section, name)] = self.backupconfig(section, name)
1425 backups[(section, name)] = self.backupconfig(section, name)
1425 self.setconfig(section, name, value, source)
1426 self.setconfig(section, name, value, source)
1426 yield
1427 yield
1427 finally:
1428 finally:
1428 for __, backup in backups.items():
1429 for __, backup in backups.items():
1429 self.restoreconfig(backup)
1430 self.restoreconfig(backup)
1430 # just restoring ui.quiet config to the previous value is not enough
1431 # just restoring ui.quiet config to the previous value is not enough
1431 # as it does not update ui.quiet class member
1432 # as it does not update ui.quiet class member
1432 if ('ui', 'quiet') in overrides:
1433 if ('ui', 'quiet') in overrides:
1433 self.fixconfig(section='ui')
1434 self.fixconfig(section='ui')
1434
1435
1435 class paths(dict):
1436 class paths(dict):
1436 """Represents a collection of paths and their configs.
1437 """Represents a collection of paths and their configs.
1437
1438
1438 Data is initially derived from ui instances and the config files they have
1439 Data is initially derived from ui instances and the config files they have
1439 loaded.
1440 loaded.
1440 """
1441 """
1441 def __init__(self, ui):
1442 def __init__(self, ui):
1442 dict.__init__(self)
1443 dict.__init__(self)
1443
1444
1444 for name, loc in ui.configitems('paths', ignoresub=True):
1445 for name, loc in ui.configitems('paths', ignoresub=True):
1445 # No location is the same as not existing.
1446 # No location is the same as not existing.
1446 if not loc:
1447 if not loc:
1447 continue
1448 continue
1448 loc, sub = ui.configsuboptions('paths', name)
1449 loc, sub = ui.configsuboptions('paths', name)
1449 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1450 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1450
1451
1451 def getpath(self, name, default=None):
1452 def getpath(self, name, default=None):
1452 """Return a ``path`` from a string, falling back to default.
1453 """Return a ``path`` from a string, falling back to default.
1453
1454
1454 ``name`` can be a named path or locations. Locations are filesystem
1455 ``name`` can be a named path or locations. Locations are filesystem
1455 paths or URIs.
1456 paths or URIs.
1456
1457
1457 Returns None if ``name`` is not a registered path, a URI, or a local
1458 Returns None if ``name`` is not a registered path, a URI, or a local
1458 path to a repo.
1459 path to a repo.
1459 """
1460 """
1460 # Only fall back to default if no path was requested.
1461 # Only fall back to default if no path was requested.
1461 if name is None:
1462 if name is None:
1462 if not default:
1463 if not default:
1463 default = ()
1464 default = ()
1464 elif not isinstance(default, (tuple, list)):
1465 elif not isinstance(default, (tuple, list)):
1465 default = (default,)
1466 default = (default,)
1466 for k in default:
1467 for k in default:
1467 try:
1468 try:
1468 return self[k]
1469 return self[k]
1469 except KeyError:
1470 except KeyError:
1470 continue
1471 continue
1471 return None
1472 return None
1472
1473
1473 # Most likely empty string.
1474 # Most likely empty string.
1474 # This may need to raise in the future.
1475 # This may need to raise in the future.
1475 if not name:
1476 if not name:
1476 return None
1477 return None
1477
1478
1478 try:
1479 try:
1479 return self[name]
1480 return self[name]
1480 except KeyError:
1481 except KeyError:
1481 # Try to resolve as a local path or URI.
1482 # Try to resolve as a local path or URI.
1482 try:
1483 try:
1483 # We don't pass sub-options in, so no need to pass ui instance.
1484 # We don't pass sub-options in, so no need to pass ui instance.
1484 return path(None, None, rawloc=name)
1485 return path(None, None, rawloc=name)
1485 except ValueError:
1486 except ValueError:
1486 raise error.RepoError(_('repository %s does not exist') %
1487 raise error.RepoError(_('repository %s does not exist') %
1487 name)
1488 name)
1488
1489
1489 _pathsuboptions = {}
1490 _pathsuboptions = {}
1490
1491
1491 def pathsuboption(option, attr):
1492 def pathsuboption(option, attr):
1492 """Decorator used to declare a path sub-option.
1493 """Decorator used to declare a path sub-option.
1493
1494
1494 Arguments are the sub-option name and the attribute it should set on
1495 Arguments are the sub-option name and the attribute it should set on
1495 ``path`` instances.
1496 ``path`` instances.
1496
1497
1497 The decorated function will receive as arguments a ``ui`` instance,
1498 The decorated function will receive as arguments a ``ui`` instance,
1498 ``path`` instance, and the string value of this option from the config.
1499 ``path`` instance, and the string value of this option from the config.
1499 The function should return the value that will be set on the ``path``
1500 The function should return the value that will be set on the ``path``
1500 instance.
1501 instance.
1501
1502
1502 This decorator can be used to perform additional verification of
1503 This decorator can be used to perform additional verification of
1503 sub-options and to change the type of sub-options.
1504 sub-options and to change the type of sub-options.
1504 """
1505 """
1505 def register(func):
1506 def register(func):
1506 _pathsuboptions[option] = (attr, func)
1507 _pathsuboptions[option] = (attr, func)
1507 return func
1508 return func
1508 return register
1509 return register
1509
1510
1510 @pathsuboption('pushurl', 'pushloc')
1511 @pathsuboption('pushurl', 'pushloc')
1511 def pushurlpathoption(ui, path, value):
1512 def pushurlpathoption(ui, path, value):
1512 u = util.url(value)
1513 u = util.url(value)
1513 # Actually require a URL.
1514 # Actually require a URL.
1514 if not u.scheme:
1515 if not u.scheme:
1515 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1516 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1516 return None
1517 return None
1517
1518
1518 # Don't support the #foo syntax in the push URL to declare branch to
1519 # Don't support the #foo syntax in the push URL to declare branch to
1519 # push.
1520 # push.
1520 if u.fragment:
1521 if u.fragment:
1521 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1522 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1522 'ignoring)\n') % path.name)
1523 'ignoring)\n') % path.name)
1523 u.fragment = None
1524 u.fragment = None
1524
1525
1525 return str(u)
1526 return str(u)
1526
1527
1527 @pathsuboption('pushrev', 'pushrev')
1528 @pathsuboption('pushrev', 'pushrev')
1528 def pushrevpathoption(ui, path, value):
1529 def pushrevpathoption(ui, path, value):
1529 return value
1530 return value
1530
1531
1531 class path(object):
1532 class path(object):
1532 """Represents an individual path and its configuration."""
1533 """Represents an individual path and its configuration."""
1533
1534
1534 def __init__(self, ui, name, rawloc=None, suboptions=None):
1535 def __init__(self, ui, name, rawloc=None, suboptions=None):
1535 """Construct a path from its config options.
1536 """Construct a path from its config options.
1536
1537
1537 ``ui`` is the ``ui`` instance the path is coming from.
1538 ``ui`` is the ``ui`` instance the path is coming from.
1538 ``name`` is the symbolic name of the path.
1539 ``name`` is the symbolic name of the path.
1539 ``rawloc`` is the raw location, as defined in the config.
1540 ``rawloc`` is the raw location, as defined in the config.
1540 ``pushloc`` is the raw locations pushes should be made to.
1541 ``pushloc`` is the raw locations pushes should be made to.
1541
1542
1542 If ``name`` is not defined, we require that the location be a) a local
1543 If ``name`` is not defined, we require that the location be a) a local
1543 filesystem path with a .hg directory or b) a URL. If not,
1544 filesystem path with a .hg directory or b) a URL. If not,
1544 ``ValueError`` is raised.
1545 ``ValueError`` is raised.
1545 """
1546 """
1546 if not rawloc:
1547 if not rawloc:
1547 raise ValueError('rawloc must be defined')
1548 raise ValueError('rawloc must be defined')
1548
1549
1549 # Locations may define branches via syntax <base>#<branch>.
1550 # Locations may define branches via syntax <base>#<branch>.
1550 u = util.url(rawloc)
1551 u = util.url(rawloc)
1551 branch = None
1552 branch = None
1552 if u.fragment:
1553 if u.fragment:
1553 branch = u.fragment
1554 branch = u.fragment
1554 u.fragment = None
1555 u.fragment = None
1555
1556
1556 self.url = u
1557 self.url = u
1557 self.branch = branch
1558 self.branch = branch
1558
1559
1559 self.name = name
1560 self.name = name
1560 self.rawloc = rawloc
1561 self.rawloc = rawloc
1561 self.loc = '%s' % u
1562 self.loc = '%s' % u
1562
1563
1563 # When given a raw location but not a symbolic name, validate the
1564 # When given a raw location but not a symbolic name, validate the
1564 # location is valid.
1565 # location is valid.
1565 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1566 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1566 raise ValueError('location is not a URL or path to a local '
1567 raise ValueError('location is not a URL or path to a local '
1567 'repo: %s' % rawloc)
1568 'repo: %s' % rawloc)
1568
1569
1569 suboptions = suboptions or {}
1570 suboptions = suboptions or {}
1570
1571
1571 # Now process the sub-options. If a sub-option is registered, its
1572 # Now process the sub-options. If a sub-option is registered, its
1572 # attribute will always be present. The value will be None if there
1573 # attribute will always be present. The value will be None if there
1573 # was no valid sub-option.
1574 # was no valid sub-option.
1574 for suboption, (attr, func) in _pathsuboptions.iteritems():
1575 for suboption, (attr, func) in _pathsuboptions.iteritems():
1575 if suboption not in suboptions:
1576 if suboption not in suboptions:
1576 setattr(self, attr, None)
1577 setattr(self, attr, None)
1577 continue
1578 continue
1578
1579
1579 value = func(ui, self, suboptions[suboption])
1580 value = func(ui, self, suboptions[suboption])
1580 setattr(self, attr, value)
1581 setattr(self, attr, value)
1581
1582
1582 def _isvalidlocalpath(self, path):
1583 def _isvalidlocalpath(self, path):
1583 """Returns True if the given path is a potentially valid repository.
1584 """Returns True if the given path is a potentially valid repository.
1584 This is its own function so that extensions can change the definition of
1585 This is its own function so that extensions can change the definition of
1585 'valid' in this case (like when pulling from a git repo into a hg
1586 'valid' in this case (like when pulling from a git repo into a hg
1586 one)."""
1587 one)."""
1587 return os.path.isdir(os.path.join(path, '.hg'))
1588 return os.path.isdir(os.path.join(path, '.hg'))
1588
1589
1589 @property
1590 @property
1590 def suboptions(self):
1591 def suboptions(self):
1591 """Return sub-options and their values for this path.
1592 """Return sub-options and their values for this path.
1592
1593
1593 This is intended to be used for presentation purposes.
1594 This is intended to be used for presentation purposes.
1594 """
1595 """
1595 d = {}
1596 d = {}
1596 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1597 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1597 value = getattr(self, attr)
1598 value = getattr(self, attr)
1598 if value is not None:
1599 if value is not None:
1599 d[subopt] = value
1600 d[subopt] = value
1600 return d
1601 return d
1601
1602
1602 # we instantiate one globally shared progress bar to avoid
1603 # we instantiate one globally shared progress bar to avoid
1603 # competing progress bars when multiple UI objects get created
1604 # competing progress bars when multiple UI objects get created
1604 _progresssingleton = None
1605 _progresssingleton = None
1605
1606
1606 def getprogbar(ui):
1607 def getprogbar(ui):
1607 global _progresssingleton
1608 global _progresssingleton
1608 if _progresssingleton is None:
1609 if _progresssingleton is None:
1609 # passing 'ui' object to the singleton is fishy,
1610 # passing 'ui' object to the singleton is fishy,
1610 # this is how the extension used to work but feel free to rework it.
1611 # this is how the extension used to work but feel free to rework it.
1611 _progresssingleton = progress.progbar(ui)
1612 _progresssingleton = progress.progbar(ui)
1612 return _progresssingleton
1613 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now