##// END OF EJS Templates
configitems: register 'email.to' and 'patchbomb.to'
Yuya Nishihara -
r34912:645b6684 stable
parent child Browse files
Show More
@@ -1,795 +1,798
1 1 # patchbomb.py - sending Mercurial changesets as patch emails
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to send changesets as (a series of) patch emails
9 9
10 10 The series is started off with a "[PATCH 0 of N]" introduction, which
11 11 describes the series as a whole.
12 12
13 13 Each patch email has a Subject line of "[PATCH M of N] ...", using the
14 14 first line of the changeset description as the subject text. The
15 15 message contains two or three body parts:
16 16
17 17 - The changeset description.
18 18 - [Optional] The result of running diffstat on the patch.
19 19 - The patch itself, as generated by :hg:`export`.
20 20
21 21 Each message refers to the first in the series using the In-Reply-To
22 22 and References headers, so they will show up as a sequence in threaded
23 23 mail and news readers, and in mail archives.
24 24
25 25 To configure other defaults, add a section like this to your
26 26 configuration file::
27 27
28 28 [email]
29 29 from = My Name <my@email>
30 30 to = recipient1, recipient2, ...
31 31 cc = cc1, cc2, ...
32 32 bcc = bcc1, bcc2, ...
33 33 reply-to = address1, address2, ...
34 34
35 35 Use ``[patchbomb]`` as configuration section name if you need to
36 36 override global ``[email]`` address settings.
37 37
38 38 Then you can use the :hg:`email` command to mail a series of
39 39 changesets as a patchbomb.
40 40
41 41 You can also either configure the method option in the email section
42 42 to be a sendmail compatible mailer or fill out the [smtp] section so
43 43 that the patchbomb extension can automatically send patchbombs
44 44 directly from the commandline. See the [email] and [smtp] sections in
45 45 hgrc(5) for details.
46 46
47 47 By default, :hg:`email` will prompt for a ``To`` or ``CC`` header if
48 48 you do not supply one via configuration or the command line. You can
49 49 override this to never prompt by configuring an empty value::
50 50
51 51 [email]
52 52 cc =
53 53
54 54 You can control the default inclusion of an introduction message with the
55 55 ``patchbomb.intro`` configuration option. The configuration is always
56 56 overwritten by command line flags like --intro and --desc::
57 57
58 58 [patchbomb]
59 59 intro=auto # include introduction message if more than 1 patch (default)
60 60 intro=never # never include an introduction message
61 61 intro=always # always include an introduction message
62 62
63 63 You can specify a template for flags to be added in subject prefixes. Flags
64 64 specified by --flag option are exported as ``{flags}`` keyword::
65 65
66 66 [patchbomb]
67 67 flagtemplate = "{separate(' ',
68 68 ifeq(branch, 'default', '', branch|upper),
69 69 flags)}"
70 70
71 71 You can set patchbomb to always ask for confirmation by setting
72 72 ``patchbomb.confirm`` to true.
73 73 '''
74 74 from __future__ import absolute_import
75 75
76 76 import email as emailmod
77 77 import errno
78 78 import os
79 79 import socket
80 80 import tempfile
81 81
82 82 from mercurial.i18n import _
83 83 from mercurial import (
84 84 cmdutil,
85 85 commands,
86 86 error,
87 87 formatter,
88 88 hg,
89 89 mail,
90 90 node as nodemod,
91 91 patch,
92 92 registrar,
93 93 repair,
94 94 scmutil,
95 95 templater,
96 96 util,
97 97 )
98 98 stringio = util.stringio
99 99
100 100 cmdtable = {}
101 101 command = registrar.command(cmdtable)
102 102
103 103 configtable = {}
104 104 configitem = registrar.configitem(configtable)
105 105
106 106 configitem('patchbomb', 'bundletype',
107 107 default=None,
108 108 )
109 109 configitem('patchbomb', 'bcc',
110 110 default=None,
111 111 )
112 112 configitem('patchbomb', 'cc',
113 113 default=None,
114 114 )
115 115 configitem('patchbomb', 'confirm',
116 116 default=False,
117 117 )
118 118 configitem('patchbomb', 'flagtemplate',
119 119 default=None,
120 120 )
121 121 configitem('patchbomb', 'from',
122 122 default=None,
123 123 )
124 124 configitem('patchbomb', 'intro',
125 125 default='auto',
126 126 )
127 127 configitem('patchbomb', 'publicurl',
128 128 default=None,
129 129 )
130 130 configitem('patchbomb', 'reply-to',
131 131 default=None,
132 132 )
133 configitem('patchbomb', 'to',
134 default=None,
135 )
133 136
134 137 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
135 138 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
136 139 # be specifying the version(s) of Mercurial they are tested with, or
137 140 # leave the attribute unspecified.
138 141 testedwith = 'ships-with-hg-core'
139 142
140 143 def _addpullheader(seq, ctx):
141 144 """Add a header pointing to a public URL where the changeset is available
142 145 """
143 146 repo = ctx.repo()
144 147 # experimental config: patchbomb.publicurl
145 148 # waiting for some logic that check that the changeset are available on the
146 149 # destination before patchbombing anything.
147 150 publicurl = repo.ui.config('patchbomb', 'publicurl')
148 151 if publicurl:
149 152 return ('Available At %s\n'
150 153 '# hg pull %s -r %s' % (publicurl, publicurl, ctx))
151 154 return None
152 155
153 156 def uisetup(ui):
154 157 cmdutil.extraexport.append('pullurl')
155 158 cmdutil.extraexportmap['pullurl'] = _addpullheader
156 159
157 160 def reposetup(ui, repo):
158 161 if not repo.local():
159 162 return
160 163 repo._wlockfreeprefix.add('last-email.txt')
161 164
162 165 def prompt(ui, prompt, default=None, rest=':'):
163 166 if default:
164 167 prompt += ' [%s]' % default
165 168 return ui.prompt(prompt + rest, default)
166 169
167 170 def introwanted(ui, opts, number):
168 171 '''is an introductory message apparently wanted?'''
169 172 introconfig = ui.config('patchbomb', 'intro')
170 173 if opts.get('intro') or opts.get('desc'):
171 174 intro = True
172 175 elif introconfig == 'always':
173 176 intro = True
174 177 elif introconfig == 'never':
175 178 intro = False
176 179 elif introconfig == 'auto':
177 180 intro = 1 < number
178 181 else:
179 182 ui.write_err(_('warning: invalid patchbomb.intro value "%s"\n')
180 183 % introconfig)
181 184 ui.write_err(_('(should be one of always, never, auto)\n'))
182 185 intro = 1 < number
183 186 return intro
184 187
185 188 def _formatflags(ui, repo, rev, flags):
186 189 """build flag string optionally by template"""
187 190 tmpl = ui.config('patchbomb', 'flagtemplate')
188 191 if not tmpl:
189 192 return ' '.join(flags)
190 193 out = util.stringio()
191 194 opts = {'template': templater.unquotestring(tmpl)}
192 195 with formatter.templateformatter(ui, out, 'patchbombflag', opts) as fm:
193 196 fm.startitem()
194 197 fm.context(ctx=repo[rev])
195 198 fm.write('flags', '%s', fm.formatlist(flags, name='flag'))
196 199 return out.getvalue()
197 200
198 201 def _formatprefix(ui, repo, rev, flags, idx, total, numbered):
199 202 """build prefix to patch subject"""
200 203 flag = _formatflags(ui, repo, rev, flags)
201 204 if flag:
202 205 flag = ' ' + flag
203 206
204 207 if not numbered:
205 208 return '[PATCH%s]' % flag
206 209 else:
207 210 tlen = len(str(total))
208 211 return '[PATCH %0*d of %d%s]' % (tlen, idx, total, flag)
209 212
210 213 def makepatch(ui, repo, rev, patchlines, opts, _charsets, idx, total, numbered,
211 214 patchname=None):
212 215
213 216 desc = []
214 217 node = None
215 218 body = ''
216 219
217 220 for line in patchlines:
218 221 if line.startswith('#'):
219 222 if line.startswith('# Node ID'):
220 223 node = line.split()[-1]
221 224 continue
222 225 if line.startswith('diff -r') or line.startswith('diff --git'):
223 226 break
224 227 desc.append(line)
225 228
226 229 if not patchname and not node:
227 230 raise ValueError
228 231
229 232 if opts.get('attach') and not opts.get('body'):
230 233 body = ('\n'.join(desc[1:]).strip() or
231 234 'Patch subject is complete summary.')
232 235 body += '\n\n\n'
233 236
234 237 if opts.get('plain'):
235 238 while patchlines and patchlines[0].startswith('# '):
236 239 patchlines.pop(0)
237 240 if patchlines:
238 241 patchlines.pop(0)
239 242 while patchlines and not patchlines[0].strip():
240 243 patchlines.pop(0)
241 244
242 245 ds = patch.diffstat(patchlines)
243 246 if opts.get('diffstat'):
244 247 body += ds + '\n\n'
245 248
246 249 addattachment = opts.get('attach') or opts.get('inline')
247 250 if not addattachment or opts.get('body'):
248 251 body += '\n'.join(patchlines)
249 252
250 253 if addattachment:
251 254 msg = emailmod.MIMEMultipart.MIMEMultipart()
252 255 if body:
253 256 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
254 257 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch',
255 258 opts.get('test'))
256 259 binnode = nodemod.bin(node)
257 260 # if node is mq patch, it will have the patch file's name as a tag
258 261 if not patchname:
259 262 patchtags = [t for t in repo.nodetags(binnode)
260 263 if t.endswith('.patch') or t.endswith('.diff')]
261 264 if patchtags:
262 265 patchname = patchtags[0]
263 266 elif total > 1:
264 267 patchname = cmdutil.makefilename(repo, '%b-%n.patch',
265 268 binnode, seqno=idx,
266 269 total=total)
267 270 else:
268 271 patchname = cmdutil.makefilename(repo, '%b.patch', binnode)
269 272 disposition = 'inline'
270 273 if opts.get('attach'):
271 274 disposition = 'attachment'
272 275 p['Content-Disposition'] = disposition + '; filename=' + patchname
273 276 msg.attach(p)
274 277 else:
275 278 msg = mail.mimetextpatch(body, display=opts.get('test'))
276 279
277 280 prefix = _formatprefix(ui, repo, rev, opts.get('flag'), idx, total,
278 281 numbered)
279 282 subj = desc[0].strip().rstrip('. ')
280 283 if not numbered:
281 284 subj = ' '.join([prefix, opts.get('subject') or subj])
282 285 else:
283 286 subj = ' '.join([prefix, subj])
284 287 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
285 288 msg['X-Mercurial-Node'] = node
286 289 msg['X-Mercurial-Series-Index'] = '%i' % idx
287 290 msg['X-Mercurial-Series-Total'] = '%i' % total
288 291 return msg, subj, ds
289 292
290 293 def _getpatches(repo, revs, **opts):
291 294 """return a list of patches for a list of revisions
292 295
293 296 Each patch in the list is itself a list of lines.
294 297 """
295 298 ui = repo.ui
296 299 prev = repo['.'].rev()
297 300 for r in revs:
298 301 if r == prev and (repo[None].files() or repo[None].deleted()):
299 302 ui.warn(_('warning: working directory has '
300 303 'uncommitted changes\n'))
301 304 output = stringio()
302 305 cmdutil.export(repo, [r], fp=output,
303 306 opts=patch.difffeatureopts(ui, opts, git=True))
304 307 yield output.getvalue().split('\n')
305 308 def _getbundle(repo, dest, **opts):
306 309 """return a bundle containing changesets missing in "dest"
307 310
308 311 The `opts` keyword-arguments are the same as the one accepted by the
309 312 `bundle` command.
310 313
311 314 The bundle is a returned as a single in-memory binary blob.
312 315 """
313 316 ui = repo.ui
314 317 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
315 318 tmpfn = os.path.join(tmpdir, 'bundle')
316 319 btype = ui.config('patchbomb', 'bundletype')
317 320 if btype:
318 321 opts['type'] = btype
319 322 try:
320 323 commands.bundle(ui, repo, tmpfn, dest, **opts)
321 324 return util.readfile(tmpfn)
322 325 finally:
323 326 try:
324 327 os.unlink(tmpfn)
325 328 except OSError:
326 329 pass
327 330 os.rmdir(tmpdir)
328 331
329 332 def _getdescription(repo, defaultbody, sender, **opts):
330 333 """obtain the body of the introduction message and return it
331 334
332 335 This is also used for the body of email with an attached bundle.
333 336
334 337 The body can be obtained either from the command line option or entered by
335 338 the user through the editor.
336 339 """
337 340 ui = repo.ui
338 341 if opts.get('desc'):
339 342 body = open(opts.get('desc')).read()
340 343 else:
341 344 ui.write(_('\nWrite the introductory message for the '
342 345 'patch series.\n\n'))
343 346 body = ui.edit(defaultbody, sender, repopath=repo.path,
344 347 action='patchbombbody')
345 348 # Save series description in case sendmail fails
346 349 msgfile = repo.vfs('last-email.txt', 'wb')
347 350 msgfile.write(body)
348 351 msgfile.close()
349 352 return body
350 353
351 354 def _getbundlemsgs(repo, sender, bundle, **opts):
352 355 """Get the full email for sending a given bundle
353 356
354 357 This function returns a list of "email" tuples (subject, content, None).
355 358 The list is always one message long in that case.
356 359 """
357 360 ui = repo.ui
358 361 _charsets = mail._charsets(ui)
359 362 subj = (opts.get('subject')
360 363 or prompt(ui, 'Subject:', 'A bundle for your repository'))
361 364
362 365 body = _getdescription(repo, '', sender, **opts)
363 366 msg = emailmod.MIMEMultipart.MIMEMultipart()
364 367 if body:
365 368 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
366 369 datapart = emailmod.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
367 370 datapart.set_payload(bundle)
368 371 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
369 372 datapart.add_header('Content-Disposition', 'attachment',
370 373 filename=bundlename)
371 374 emailmod.Encoders.encode_base64(datapart)
372 375 msg.attach(datapart)
373 376 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
374 377 return [(msg, subj, None)]
375 378
376 379 def _makeintro(repo, sender, revs, patches, **opts):
377 380 """make an introduction email, asking the user for content if needed
378 381
379 382 email is returned as (subject, body, cumulative-diffstat)"""
380 383 ui = repo.ui
381 384 _charsets = mail._charsets(ui)
382 385
383 386 # use the last revision which is likely to be a bookmarked head
384 387 prefix = _formatprefix(ui, repo, revs.last(), opts.get('flag'),
385 388 0, len(patches), numbered=True)
386 389 subj = (opts.get('subject') or
387 390 prompt(ui, '(optional) Subject: ', rest=prefix, default=''))
388 391 if not subj:
389 392 return None # skip intro if the user doesn't bother
390 393
391 394 subj = prefix + ' ' + subj
392 395
393 396 body = ''
394 397 if opts.get('diffstat'):
395 398 # generate a cumulative diffstat of the whole patch series
396 399 diffstat = patch.diffstat(sum(patches, []))
397 400 body = '\n' + diffstat
398 401 else:
399 402 diffstat = None
400 403
401 404 body = _getdescription(repo, body, sender, **opts)
402 405 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
403 406 msg['Subject'] = mail.headencode(ui, subj, _charsets,
404 407 opts.get('test'))
405 408 return (msg, subj, diffstat)
406 409
407 410 def _getpatchmsgs(repo, sender, revs, patchnames=None, **opts):
408 411 """return a list of emails from a list of patches
409 412
410 413 This involves introduction message creation if necessary.
411 414
412 415 This function returns a list of "email" tuples (subject, content, None).
413 416 """
414 417 ui = repo.ui
415 418 _charsets = mail._charsets(ui)
416 419 patches = list(_getpatches(repo, revs, **opts))
417 420 msgs = []
418 421
419 422 ui.write(_('this patch series consists of %d patches.\n\n')
420 423 % len(patches))
421 424
422 425 # build the intro message, or skip it if the user declines
423 426 if introwanted(ui, opts, len(patches)):
424 427 msg = _makeintro(repo, sender, revs, patches, **opts)
425 428 if msg:
426 429 msgs.append(msg)
427 430
428 431 # are we going to send more than one message?
429 432 numbered = len(msgs) + len(patches) > 1
430 433
431 434 # now generate the actual patch messages
432 435 name = None
433 436 assert len(revs) == len(patches)
434 437 for i, (r, p) in enumerate(zip(revs, patches)):
435 438 if patchnames:
436 439 name = patchnames[i]
437 440 msg = makepatch(ui, repo, r, p, opts, _charsets, i + 1,
438 441 len(patches), numbered, name)
439 442 msgs.append(msg)
440 443
441 444 return msgs
442 445
443 446 def _getoutgoing(repo, dest, revs):
444 447 '''Return the revisions present locally but not in dest'''
445 448 ui = repo.ui
446 449 url = ui.expandpath(dest or 'default-push', dest or 'default')
447 450 url = hg.parseurl(url)[0]
448 451 ui.status(_('comparing with %s\n') % util.hidepassword(url))
449 452
450 453 revs = [r for r in revs if r >= 0]
451 454 if not revs:
452 455 revs = [len(repo) - 1]
453 456 revs = repo.revs('outgoing(%s) and ::%ld', dest or '', revs)
454 457 if not revs:
455 458 ui.status(_("no changes found\n"))
456 459 return revs
457 460
458 461 emailopts = [
459 462 ('', 'body', None, _('send patches as inline message text (default)')),
460 463 ('a', 'attach', None, _('send patches as attachments')),
461 464 ('i', 'inline', None, _('send patches as inline attachments')),
462 465 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
463 466 ('c', 'cc', [], _('email addresses of copy recipients')),
464 467 ('', 'confirm', None, _('ask for confirmation before sending')),
465 468 ('d', 'diffstat', None, _('add diffstat output to messages')),
466 469 ('', 'date', '', _('use the given date as the sending date')),
467 470 ('', 'desc', '', _('use the given file as the series description')),
468 471 ('f', 'from', '', _('email address of sender')),
469 472 ('n', 'test', None, _('print messages that would be sent')),
470 473 ('m', 'mbox', '', _('write messages to mbox file instead of sending them')),
471 474 ('', 'reply-to', [], _('email addresses replies should be sent to')),
472 475 ('s', 'subject', '', _('subject of first message (intro or single patch)')),
473 476 ('', 'in-reply-to', '', _('message identifier to reply to')),
474 477 ('', 'flag', [], _('flags to add in subject prefixes')),
475 478 ('t', 'to', [], _('email addresses of recipients'))]
476 479
477 480 @command('email',
478 481 [('g', 'git', None, _('use git extended diff format')),
479 482 ('', 'plain', None, _('omit hg patch header')),
480 483 ('o', 'outgoing', None,
481 484 _('send changes not found in the target repository')),
482 485 ('b', 'bundle', None, _('send changes not in target as a binary bundle')),
483 486 ('B', 'bookmark', '', _('send changes only reachable by given bookmark')),
484 487 ('', 'bundlename', 'bundle',
485 488 _('name of the bundle attachment file'), _('NAME')),
486 489 ('r', 'rev', [], _('a revision to send'), _('REV')),
487 490 ('', 'force', None, _('run even when remote repository is unrelated '
488 491 '(with -b/--bundle)')),
489 492 ('', 'base', [], _('a base changeset to specify instead of a destination '
490 493 '(with -b/--bundle)'), _('REV')),
491 494 ('', 'intro', None, _('send an introduction email for a single patch')),
492 495 ] + emailopts + cmdutil.remoteopts,
493 496 _('hg email [OPTION]... [DEST]...'))
494 497 def email(ui, repo, *revs, **opts):
495 498 '''send changesets by email
496 499
497 500 By default, diffs are sent in the format generated by
498 501 :hg:`export`, one per message. The series starts with a "[PATCH 0
499 502 of N]" introduction, which describes the series as a whole.
500 503
501 504 Each patch email has a Subject line of "[PATCH M of N] ...", using
502 505 the first line of the changeset description as the subject text.
503 506 The message contains two or three parts. First, the changeset
504 507 description.
505 508
506 509 With the -d/--diffstat option, if the diffstat program is
507 510 installed, the result of running diffstat on the patch is inserted.
508 511
509 512 Finally, the patch itself, as generated by :hg:`export`.
510 513
511 514 With the -d/--diffstat or --confirm options, you will be presented
512 515 with a final summary of all messages and asked for confirmation before
513 516 the messages are sent.
514 517
515 518 By default the patch is included as text in the email body for
516 519 easy reviewing. Using the -a/--attach option will instead create
517 520 an attachment for the patch. With -i/--inline an inline attachment
518 521 will be created. You can include a patch both as text in the email
519 522 body and as a regular or an inline attachment by combining the
520 523 -a/--attach or -i/--inline with the --body option.
521 524
522 525 With -B/--bookmark changesets reachable by the given bookmark are
523 526 selected.
524 527
525 528 With -o/--outgoing, emails will be generated for patches not found
526 529 in the destination repository (or only those which are ancestors
527 530 of the specified revisions if any are provided)
528 531
529 532 With -b/--bundle, changesets are selected as for --outgoing, but a
530 533 single email containing a binary Mercurial bundle as an attachment
531 534 will be sent. Use the ``patchbomb.bundletype`` config option to
532 535 control the bundle type as with :hg:`bundle --type`.
533 536
534 537 With -m/--mbox, instead of previewing each patchbomb message in a
535 538 pager or sending the messages directly, it will create a UNIX
536 539 mailbox file with the patch emails. This mailbox file can be
537 540 previewed with any mail user agent which supports UNIX mbox
538 541 files.
539 542
540 543 With -n/--test, all steps will run, but mail will not be sent.
541 544 You will be prompted for an email recipient address, a subject and
542 545 an introductory message describing the patches of your patchbomb.
543 546 Then when all is done, patchbomb messages are displayed.
544 547
545 548 In case email sending fails, you will find a backup of your series
546 549 introductory message in ``.hg/last-email.txt``.
547 550
548 551 The default behavior of this command can be customized through
549 552 configuration. (See :hg:`help patchbomb` for details)
550 553
551 554 Examples::
552 555
553 556 hg email -r 3000 # send patch 3000 only
554 557 hg email -r 3000 -r 3001 # send patches 3000 and 3001
555 558 hg email -r 3000:3005 # send patches 3000 through 3005
556 559 hg email 3000 # send patch 3000 (deprecated)
557 560
558 561 hg email -o # send all patches not in default
559 562 hg email -o DEST # send all patches not in DEST
560 563 hg email -o -r 3000 # send all ancestors of 3000 not in default
561 564 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
562 565
563 566 hg email -B feature # send all ancestors of feature bookmark
564 567
565 568 hg email -b # send bundle of all patches not in default
566 569 hg email -b DEST # send bundle of all patches not in DEST
567 570 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
568 571 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
569 572
570 573 hg email -o -m mbox && # generate an mbox file...
571 574 mutt -R -f mbox # ... and view it with mutt
572 575 hg email -o -m mbox && # generate an mbox file ...
573 576 formail -s sendmail \\ # ... and use formail to send from the mbox
574 577 -bm -t < mbox # ... using sendmail
575 578
576 579 Before using this command, you will need to enable email in your
577 580 hgrc. See the [email] section in hgrc(5) for details.
578 581 '''
579 582
580 583 _charsets = mail._charsets(ui)
581 584
582 585 bundle = opts.get('bundle')
583 586 date = opts.get('date')
584 587 mbox = opts.get('mbox')
585 588 outgoing = opts.get('outgoing')
586 589 rev = opts.get('rev')
587 590 bookmark = opts.get('bookmark')
588 591
589 592 if not (opts.get('test') or mbox):
590 593 # really sending
591 594 mail.validateconfig(ui)
592 595
593 596 if not (revs or rev or outgoing or bundle or bookmark):
594 597 raise error.Abort(_('specify at least one changeset with -B, -r or -o'))
595 598
596 599 if outgoing and bundle:
597 600 raise error.Abort(_("--outgoing mode always on with --bundle;"
598 601 " do not re-specify --outgoing"))
599 602 if rev and bookmark:
600 603 raise error.Abort(_("-r and -B are mutually exclusive"))
601 604
602 605 if outgoing or bundle:
603 606 if len(revs) > 1:
604 607 raise error.Abort(_("too many destinations"))
605 608 if revs:
606 609 dest = revs[0]
607 610 else:
608 611 dest = None
609 612 revs = []
610 613
611 614 if rev:
612 615 if revs:
613 616 raise error.Abort(_('use only one form to specify the revision'))
614 617 revs = rev
615 618 elif bookmark:
616 619 if bookmark not in repo._bookmarks:
617 620 raise error.Abort(_("bookmark '%s' not found") % bookmark)
618 621 revs = repair.stripbmrevset(repo, bookmark)
619 622
620 623 revs = scmutil.revrange(repo, revs)
621 624 if outgoing:
622 625 revs = _getoutgoing(repo, dest, revs)
623 626 if bundle:
624 627 opts['revs'] = [str(r) for r in revs]
625 628
626 629 # check if revision exist on the public destination
627 630 publicurl = repo.ui.config('patchbomb', 'publicurl')
628 631 if publicurl:
629 632 repo.ui.debug('checking that revision exist in the public repo')
630 633 try:
631 634 publicpeer = hg.peer(repo, {}, publicurl)
632 635 except error.RepoError:
633 636 repo.ui.write_err(_('unable to access public repo: %s\n')
634 637 % publicurl)
635 638 raise
636 639 if not publicpeer.capable('known'):
637 640 repo.ui.debug('skipping existence checks: public repo too old')
638 641 else:
639 642 out = [repo[r] for r in revs]
640 643 known = publicpeer.known(h.node() for h in out)
641 644 missing = []
642 645 for idx, h in enumerate(out):
643 646 if not known[idx]:
644 647 missing.append(h)
645 648 if missing:
646 649 if 1 < len(missing):
647 650 msg = _('public "%s" is missing %s and %i others')
648 651 msg %= (publicurl, missing[0], len(missing) - 1)
649 652 else:
650 653 msg = _('public url %s is missing %s')
651 654 msg %= (publicurl, missing[0])
652 655 revhint = ' '.join('-r %s' % h
653 656 for h in repo.set('heads(%ld)', missing))
654 657 hint = _("use 'hg push %s %s'") % (publicurl, revhint)
655 658 raise error.Abort(msg, hint=hint)
656 659
657 660 # start
658 661 if date:
659 662 start_time = util.parsedate(date)
660 663 else:
661 664 start_time = util.makedate()
662 665
663 666 def genmsgid(id):
664 667 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
665 668
666 669 # deprecated config: patchbomb.from
667 670 sender = (opts.get('from') or ui.config('email', 'from') or
668 671 ui.config('patchbomb', 'from') or
669 672 prompt(ui, 'From', ui.username()))
670 673
671 674 if bundle:
672 675 bundledata = _getbundle(repo, dest, **opts)
673 676 bundleopts = opts.copy()
674 677 bundleopts.pop('bundle', None) # already processed
675 678 msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
676 679 else:
677 680 msgs = _getpatchmsgs(repo, sender, revs, **opts)
678 681
679 682 showaddrs = []
680 683
681 684 def getaddrs(header, ask=False, default=None):
682 685 configkey = header.lower()
683 686 opt = header.replace('-', '_').lower()
684 687 addrs = opts.get(opt)
685 688 if addrs:
686 689 showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
687 690 return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))
688 691
689 692 # not on the command line: fallback to config and then maybe ask
690 693 addr = (ui.config('email', configkey) or
691 694 ui.config('patchbomb', configkey))
692 695 if not addr:
693 696 specified = (ui.hasconfig('email', configkey) or
694 697 ui.hasconfig('patchbomb', configkey))
695 698 if not specified and ask:
696 699 addr = prompt(ui, header, default=default)
697 700 if addr:
698 701 showaddrs.append('%s: %s' % (header, addr))
699 702 return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
700 703 elif default:
701 704 return mail.addrlistencode(
702 705 ui, [default], _charsets, opts.get('test'))
703 706 return []
704 707
705 708 to = getaddrs('To', ask=True)
706 709 if not to:
707 710 # we can get here in non-interactive mode
708 711 raise error.Abort(_('no recipient addresses provided'))
709 712 cc = getaddrs('Cc', ask=True, default='')
710 713 bcc = getaddrs('Bcc')
711 714 replyto = getaddrs('Reply-To')
712 715
713 716 confirm = ui.configbool('patchbomb', 'confirm')
714 717 confirm |= bool(opts.get('diffstat') or opts.get('confirm'))
715 718
716 719 if confirm:
717 720 ui.write(_('\nFinal summary:\n\n'), label='patchbomb.finalsummary')
718 721 ui.write(('From: %s\n' % sender), label='patchbomb.from')
719 722 for addr in showaddrs:
720 723 ui.write('%s\n' % addr, label='patchbomb.to')
721 724 for m, subj, ds in msgs:
722 725 ui.write(('Subject: %s\n' % subj), label='patchbomb.subject')
723 726 if ds:
724 727 ui.write(ds, label='patchbomb.diffstats')
725 728 ui.write('\n')
726 729 if ui.promptchoice(_('are you sure you want to send (yn)?'
727 730 '$$ &Yes $$ &No')):
728 731 raise error.Abort(_('patchbomb canceled'))
729 732
730 733 ui.write('\n')
731 734
732 735 parent = opts.get('in_reply_to') or None
733 736 # angle brackets may be omitted, they're not semantically part of the msg-id
734 737 if parent is not None:
735 738 if not parent.startswith('<'):
736 739 parent = '<' + parent
737 740 if not parent.endswith('>'):
738 741 parent += '>'
739 742
740 743 sender_addr = emailmod.Utils.parseaddr(sender)[1]
741 744 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
742 745 sendmail = None
743 746 firstpatch = None
744 747 for i, (m, subj, ds) in enumerate(msgs):
745 748 try:
746 749 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
747 750 if not firstpatch:
748 751 firstpatch = m['Message-Id']
749 752 m['X-Mercurial-Series-Id'] = firstpatch
750 753 except TypeError:
751 754 m['Message-Id'] = genmsgid('patchbomb')
752 755 if parent:
753 756 m['In-Reply-To'] = parent
754 757 m['References'] = parent
755 758 if not parent or 'X-Mercurial-Node' not in m:
756 759 parent = m['Message-Id']
757 760
758 761 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
759 762 m['Date'] = emailmod.Utils.formatdate(start_time[0], localtime=True)
760 763
761 764 start_time = (start_time[0] + 1, start_time[1])
762 765 m['From'] = sender
763 766 m['To'] = ', '.join(to)
764 767 if cc:
765 768 m['Cc'] = ', '.join(cc)
766 769 if bcc:
767 770 m['Bcc'] = ', '.join(bcc)
768 771 if replyto:
769 772 m['Reply-To'] = ', '.join(replyto)
770 773 if opts.get('test'):
771 774 ui.status(_('displaying '), subj, ' ...\n')
772 775 ui.pager('email')
773 776 generator = emailmod.Generator.Generator(ui, mangle_from_=False)
774 777 try:
775 778 generator.flatten(m, 0)
776 779 ui.write('\n')
777 780 except IOError as inst:
778 781 if inst.errno != errno.EPIPE:
779 782 raise
780 783 else:
781 784 if not sendmail:
782 785 sendmail = mail.connect(ui, mbox=mbox)
783 786 ui.status(_('sending '), subj, ' ...\n')
784 787 ui.progress(_('sending'), i, item=subj, total=len(msgs),
785 788 unit=_('emails'))
786 789 if not mbox:
787 790 # Exim does not remove the Bcc field
788 791 del m['Bcc']
789 792 fp = stringio()
790 793 generator = emailmod.Generator.Generator(fp, mangle_from_=False)
791 794 generator.flatten(m, 0)
792 795 sendmail(sender_addr, to + bcc + cc, fp.getvalue())
793 796
794 797 ui.progress(_('writing'), None)
795 798 ui.progress(_('sending'), None)
@@ -1,1134 +1,1137
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import functools
11 11 import re
12 12
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 )
17 17
18 18 def loadconfigtable(ui, extname, configtable):
19 19 """update config item known to the ui with the extension ones"""
20 20 for section, items in configtable.items():
21 21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 22 knownkeys = set(knownitems)
23 23 newkeys = set(items)
24 24 for key in sorted(knownkeys & newkeys):
25 25 msg = "extension '%s' overwrite config item '%s.%s'"
26 26 msg %= (extname, section, key)
27 27 ui.develwarn(msg, config='warn-config')
28 28
29 29 knownitems.update(items)
30 30
31 31 class configitem(object):
32 32 """represent a known config item
33 33
34 34 :section: the official config section where to find this item,
35 35 :name: the official name within the section,
36 36 :default: default value for this item,
37 37 :alias: optional list of tuples as alternatives,
38 38 :generic: this is a generic definition, match name using regular expression.
39 39 """
40 40
41 41 def __init__(self, section, name, default=None, alias=(),
42 42 generic=False, priority=0):
43 43 self.section = section
44 44 self.name = name
45 45 self.default = default
46 46 self.alias = list(alias)
47 47 self.generic = generic
48 48 self.priority = priority
49 49 self._re = None
50 50 if generic:
51 51 self._re = re.compile(self.name)
52 52
53 53 class itemregister(dict):
54 54 """A specialized dictionary that can handle wild-card selection"""
55 55
56 56 def __init__(self):
57 57 super(itemregister, self).__init__()
58 58 self._generics = set()
59 59
60 60 def update(self, other):
61 61 super(itemregister, self).update(other)
62 62 self._generics.update(other._generics)
63 63
64 64 def __setitem__(self, key, item):
65 65 super(itemregister, self).__setitem__(key, item)
66 66 if item.generic:
67 67 self._generics.add(item)
68 68
69 69 def get(self, key):
70 70 baseitem = super(itemregister, self).get(key)
71 71 if baseitem is not None and not baseitem.generic:
72 72 return baseitem
73 73
74 74 # search for a matching generic item
75 75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 76 for item in generics:
77 77 # we use 'match' instead of 'search' to make the matching simpler
78 78 # for people unfamiliar with regular expression. Having the match
79 79 # rooted to the start of the string will produce less surprising
80 80 # result for user writing simple regex for sub-attribute.
81 81 #
82 82 # For example using "color\..*" match produces an unsurprising
83 83 # result, while using search could suddenly match apparently
84 84 # unrelated configuration that happens to contains "color."
85 85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 86 # some match to avoid the need to prefix most pattern with "^".
87 87 # The "^" seems more error prone.
88 88 if item._re.match(key):
89 89 return item
90 90
91 91 return None
92 92
93 93 coreitems = {}
94 94
95 95 def _register(configtable, *args, **kwargs):
96 96 item = configitem(*args, **kwargs)
97 97 section = configtable.setdefault(item.section, itemregister())
98 98 if item.name in section:
99 99 msg = "duplicated config item registration for '%s.%s'"
100 100 raise error.ProgrammingError(msg % (item.section, item.name))
101 101 section[item.name] = item
102 102
103 103 # special value for case where the default is derived from other values
104 104 dynamicdefault = object()
105 105
106 106 # Registering actual config items
107 107
108 108 def getitemregister(configtable):
109 109 return functools.partial(_register, configtable)
110 110
111 111 coreconfigitem = getitemregister(coreitems)
112 112
113 113 coreconfigitem('alias', '.*',
114 114 default=None,
115 115 generic=True,
116 116 )
117 117 coreconfigitem('annotate', 'nodates',
118 118 default=False,
119 119 )
120 120 coreconfigitem('annotate', 'showfunc',
121 121 default=False,
122 122 )
123 123 coreconfigitem('annotate', 'unified',
124 124 default=None,
125 125 )
126 126 coreconfigitem('annotate', 'git',
127 127 default=False,
128 128 )
129 129 coreconfigitem('annotate', 'ignorews',
130 130 default=False,
131 131 )
132 132 coreconfigitem('annotate', 'ignorewsamount',
133 133 default=False,
134 134 )
135 135 coreconfigitem('annotate', 'ignoreblanklines',
136 136 default=False,
137 137 )
138 138 coreconfigitem('annotate', 'ignorewseol',
139 139 default=False,
140 140 )
141 141 coreconfigitem('annotate', 'nobinary',
142 142 default=False,
143 143 )
144 144 coreconfigitem('annotate', 'noprefix',
145 145 default=False,
146 146 )
147 147 coreconfigitem('auth', 'cookiefile',
148 148 default=None,
149 149 )
150 150 # bookmarks.pushing: internal hack for discovery
151 151 coreconfigitem('bookmarks', 'pushing',
152 152 default=list,
153 153 )
154 154 # bundle.mainreporoot: internal hack for bundlerepo
155 155 coreconfigitem('bundle', 'mainreporoot',
156 156 default='',
157 157 )
158 158 # bundle.reorder: experimental config
159 159 coreconfigitem('bundle', 'reorder',
160 160 default='auto',
161 161 )
162 162 coreconfigitem('censor', 'policy',
163 163 default='abort',
164 164 )
165 165 coreconfigitem('chgserver', 'idletimeout',
166 166 default=3600,
167 167 )
168 168 coreconfigitem('chgserver', 'skiphash',
169 169 default=False,
170 170 )
171 171 coreconfigitem('cmdserver', 'log',
172 172 default=None,
173 173 )
174 174 coreconfigitem('color', '.*',
175 175 default=None,
176 176 generic=True,
177 177 )
178 178 coreconfigitem('color', 'mode',
179 179 default='auto',
180 180 )
181 181 coreconfigitem('color', 'pagermode',
182 182 default=dynamicdefault,
183 183 )
184 184 coreconfigitem('commands', 'show.aliasprefix',
185 185 default=list,
186 186 )
187 187 coreconfigitem('commands', 'status.relative',
188 188 default=False,
189 189 )
190 190 coreconfigitem('commands', 'status.skipstates',
191 191 default=[],
192 192 )
193 193 coreconfigitem('commands', 'status.verbose',
194 194 default=False,
195 195 )
196 196 coreconfigitem('commands', 'update.check',
197 197 default=None,
198 198 # Deprecated, remove after 4.4 release
199 199 alias=[('experimental', 'updatecheck')]
200 200 )
201 201 coreconfigitem('commands', 'update.requiredest',
202 202 default=False,
203 203 )
204 204 coreconfigitem('committemplate', '.*',
205 205 default=None,
206 206 generic=True,
207 207 )
208 208 coreconfigitem('debug', 'dirstate.delaywrite',
209 209 default=0,
210 210 )
211 211 coreconfigitem('defaults', '.*',
212 212 default=None,
213 213 generic=True,
214 214 )
215 215 coreconfigitem('devel', 'all-warnings',
216 216 default=False,
217 217 )
218 218 coreconfigitem('devel', 'bundle2.debug',
219 219 default=False,
220 220 )
221 221 coreconfigitem('devel', 'cache-vfs',
222 222 default=None,
223 223 )
224 224 coreconfigitem('devel', 'check-locks',
225 225 default=False,
226 226 )
227 227 coreconfigitem('devel', 'check-relroot',
228 228 default=False,
229 229 )
230 230 coreconfigitem('devel', 'default-date',
231 231 default=None,
232 232 )
233 233 coreconfigitem('devel', 'deprec-warn',
234 234 default=False,
235 235 )
236 236 coreconfigitem('devel', 'disableloaddefaultcerts',
237 237 default=False,
238 238 )
239 239 coreconfigitem('devel', 'warn-empty-changegroup',
240 240 default=False,
241 241 )
242 242 coreconfigitem('devel', 'legacy.exchange',
243 243 default=list,
244 244 )
245 245 coreconfigitem('devel', 'servercafile',
246 246 default='',
247 247 )
248 248 coreconfigitem('devel', 'serverexactprotocol',
249 249 default='',
250 250 )
251 251 coreconfigitem('devel', 'serverrequirecert',
252 252 default=False,
253 253 )
254 254 coreconfigitem('devel', 'strip-obsmarkers',
255 255 default=True,
256 256 )
257 257 coreconfigitem('devel', 'warn-config',
258 258 default=None,
259 259 )
260 260 coreconfigitem('devel', 'warn-config-default',
261 261 default=None,
262 262 )
263 263 coreconfigitem('devel', 'user.obsmarker',
264 264 default=None,
265 265 )
266 266 coreconfigitem('devel', 'warn-config-unknown',
267 267 default=None,
268 268 )
269 269 coreconfigitem('diff', 'nodates',
270 270 default=False,
271 271 )
272 272 coreconfigitem('diff', 'showfunc',
273 273 default=False,
274 274 )
275 275 coreconfigitem('diff', 'unified',
276 276 default=None,
277 277 )
278 278 coreconfigitem('diff', 'git',
279 279 default=False,
280 280 )
281 281 coreconfigitem('diff', 'ignorews',
282 282 default=False,
283 283 )
284 284 coreconfigitem('diff', 'ignorewsamount',
285 285 default=False,
286 286 )
287 287 coreconfigitem('diff', 'ignoreblanklines',
288 288 default=False,
289 289 )
290 290 coreconfigitem('diff', 'ignorewseol',
291 291 default=False,
292 292 )
293 293 coreconfigitem('diff', 'nobinary',
294 294 default=False,
295 295 )
296 296 coreconfigitem('diff', 'noprefix',
297 297 default=False,
298 298 )
299 299 coreconfigitem('email', 'bcc',
300 300 default=None,
301 301 )
302 302 coreconfigitem('email', 'cc',
303 303 default=None,
304 304 )
305 305 coreconfigitem('email', 'charsets',
306 306 default=list,
307 307 )
308 308 coreconfigitem('email', 'from',
309 309 default=None,
310 310 )
311 311 coreconfigitem('email', 'method',
312 312 default='smtp',
313 313 )
314 314 coreconfigitem('email', 'reply-to',
315 315 default=None,
316 316 )
317 coreconfigitem('email', 'to',
318 default=None,
319 )
317 320 coreconfigitem('experimental', 'archivemetatemplate',
318 321 default=dynamicdefault,
319 322 )
320 323 coreconfigitem('experimental', 'bundle-phases',
321 324 default=False,
322 325 )
323 326 coreconfigitem('experimental', 'bundle2-advertise',
324 327 default=True,
325 328 )
326 329 coreconfigitem('experimental', 'bundle2-output-capture',
327 330 default=False,
328 331 )
329 332 coreconfigitem('experimental', 'bundle2.pushback',
330 333 default=False,
331 334 )
332 335 coreconfigitem('experimental', 'bundle2lazylocking',
333 336 default=False,
334 337 )
335 338 coreconfigitem('experimental', 'bundlecomplevel',
336 339 default=None,
337 340 )
338 341 coreconfigitem('experimental', 'changegroup3',
339 342 default=False,
340 343 )
341 344 coreconfigitem('experimental', 'clientcompressionengines',
342 345 default=list,
343 346 )
344 347 coreconfigitem('experimental', 'copytrace',
345 348 default='on',
346 349 )
347 350 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
348 351 default=100,
349 352 )
350 353 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
351 354 default=100,
352 355 )
353 356 coreconfigitem('experimental', 'crecordtest',
354 357 default=None,
355 358 )
356 359 coreconfigitem('experimental', 'editortmpinhg',
357 360 default=False,
358 361 )
359 362 coreconfigitem('experimental', 'evolution',
360 363 default=list,
361 364 )
362 365 coreconfigitem('experimental', 'evolution.allowdivergence',
363 366 default=False,
364 367 alias=[('experimental', 'allowdivergence')]
365 368 )
366 369 coreconfigitem('experimental', 'evolution.allowunstable',
367 370 default=None,
368 371 )
369 372 coreconfigitem('experimental', 'evolution.createmarkers',
370 373 default=None,
371 374 )
372 375 coreconfigitem('experimental', 'evolution.effect-flags',
373 376 default=False,
374 377 alias=[('experimental', 'effect-flags')]
375 378 )
376 379 coreconfigitem('experimental', 'evolution.exchange',
377 380 default=None,
378 381 )
379 382 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
380 383 default=False,
381 384 )
382 385 coreconfigitem('experimental', 'evolution.track-operation',
383 386 default=True,
384 387 )
385 388 coreconfigitem('experimental', 'maxdeltachainspan',
386 389 default=-1,
387 390 )
388 391 coreconfigitem('experimental', 'mmapindexthreshold',
389 392 default=None,
390 393 )
391 394 coreconfigitem('experimental', 'nonnormalparanoidcheck',
392 395 default=False,
393 396 )
394 397 coreconfigitem('experimental', 'exportableenviron',
395 398 default=list,
396 399 )
397 400 coreconfigitem('experimental', 'extendedheader.index',
398 401 default=None,
399 402 )
400 403 coreconfigitem('experimental', 'extendedheader.similarity',
401 404 default=False,
402 405 )
403 406 coreconfigitem('experimental', 'format.compression',
404 407 default='zlib',
405 408 )
406 409 coreconfigitem('experimental', 'graphshorten',
407 410 default=False,
408 411 )
409 412 coreconfigitem('experimental', 'graphstyle.parent',
410 413 default=dynamicdefault,
411 414 )
412 415 coreconfigitem('experimental', 'graphstyle.missing',
413 416 default=dynamicdefault,
414 417 )
415 418 coreconfigitem('experimental', 'graphstyle.grandparent',
416 419 default=dynamicdefault,
417 420 )
418 421 coreconfigitem('experimental', 'hook-track-tags',
419 422 default=False,
420 423 )
421 424 coreconfigitem('experimental', 'httppostargs',
422 425 default=False,
423 426 )
424 427 coreconfigitem('experimental', 'manifestv2',
425 428 default=False,
426 429 )
427 430 coreconfigitem('experimental', 'mergedriver',
428 431 default=None,
429 432 )
430 433 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
431 434 default=False,
432 435 )
433 436 coreconfigitem('experimental', 'rebase.multidest',
434 437 default=False,
435 438 )
436 439 coreconfigitem('experimental', 'revertalternateinteractivemode',
437 440 default=True,
438 441 )
439 442 coreconfigitem('experimental', 'revlogv2',
440 443 default=None,
441 444 )
442 445 coreconfigitem('experimental', 'spacemovesdown',
443 446 default=False,
444 447 )
445 448 coreconfigitem('experimental', 'sparse-read',
446 449 default=False,
447 450 )
448 451 coreconfigitem('experimental', 'sparse-read.density-threshold',
449 452 default=0.25,
450 453 )
451 454 coreconfigitem('experimental', 'sparse-read.min-gap-size',
452 455 default='256K',
453 456 )
454 457 coreconfigitem('experimental', 'treemanifest',
455 458 default=False,
456 459 )
457 460 coreconfigitem('extensions', '.*',
458 461 default=None,
459 462 generic=True,
460 463 )
461 464 coreconfigitem('extdata', '.*',
462 465 default=None,
463 466 generic=True,
464 467 )
465 468 coreconfigitem('format', 'aggressivemergedeltas',
466 469 default=False,
467 470 )
468 471 coreconfigitem('format', 'chunkcachesize',
469 472 default=None,
470 473 )
471 474 coreconfigitem('format', 'dotencode',
472 475 default=True,
473 476 )
474 477 coreconfigitem('format', 'generaldelta',
475 478 default=False,
476 479 )
477 480 coreconfigitem('format', 'manifestcachesize',
478 481 default=None,
479 482 )
480 483 coreconfigitem('format', 'maxchainlen',
481 484 default=None,
482 485 )
483 486 coreconfigitem('format', 'obsstore-version',
484 487 default=None,
485 488 )
486 489 coreconfigitem('format', 'usefncache',
487 490 default=True,
488 491 )
489 492 coreconfigitem('format', 'usegeneraldelta',
490 493 default=True,
491 494 )
492 495 coreconfigitem('format', 'usestore',
493 496 default=True,
494 497 )
495 498 coreconfigitem('fsmonitor', 'warn_when_unused',
496 499 default=True,
497 500 )
498 501 coreconfigitem('fsmonitor', 'warn_update_file_count',
499 502 default=50000,
500 503 )
501 504 coreconfigitem('hooks', '.*',
502 505 default=dynamicdefault,
503 506 generic=True,
504 507 )
505 508 coreconfigitem('hgweb-paths', '.*',
506 509 default=list,
507 510 generic=True,
508 511 )
509 512 coreconfigitem('hostfingerprints', '.*',
510 513 default=list,
511 514 generic=True,
512 515 )
513 516 coreconfigitem('hostsecurity', 'ciphers',
514 517 default=None,
515 518 )
516 519 coreconfigitem('hostsecurity', 'disabletls10warning',
517 520 default=False,
518 521 )
519 522 coreconfigitem('hostsecurity', 'minimumprotocol',
520 523 default=dynamicdefault,
521 524 )
522 525 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
523 526 default=dynamicdefault,
524 527 generic=True,
525 528 )
526 529 coreconfigitem('hostsecurity', '.*:ciphers$',
527 530 default=dynamicdefault,
528 531 generic=True,
529 532 )
530 533 coreconfigitem('hostsecurity', '.*:fingerprints$',
531 534 default=list,
532 535 generic=True,
533 536 )
534 537 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
535 538 default=None,
536 539 generic=True,
537 540 )
538 541
539 542 coreconfigitem('http_proxy', 'always',
540 543 default=False,
541 544 )
542 545 coreconfigitem('http_proxy', 'host',
543 546 default=None,
544 547 )
545 548 coreconfigitem('http_proxy', 'no',
546 549 default=list,
547 550 )
548 551 coreconfigitem('http_proxy', 'passwd',
549 552 default=None,
550 553 )
551 554 coreconfigitem('http_proxy', 'user',
552 555 default=None,
553 556 )
554 557 coreconfigitem('logtoprocess', 'commandexception',
555 558 default=None,
556 559 )
557 560 coreconfigitem('logtoprocess', 'commandfinish',
558 561 default=None,
559 562 )
560 563 coreconfigitem('logtoprocess', 'command',
561 564 default=None,
562 565 )
563 566 coreconfigitem('logtoprocess', 'develwarn',
564 567 default=None,
565 568 )
566 569 coreconfigitem('logtoprocess', 'uiblocked',
567 570 default=None,
568 571 )
569 572 coreconfigitem('merge', 'checkunknown',
570 573 default='abort',
571 574 )
572 575 coreconfigitem('merge', 'checkignored',
573 576 default='abort',
574 577 )
575 578 coreconfigitem('merge', 'followcopies',
576 579 default=True,
577 580 )
578 581 coreconfigitem('merge', 'on-failure',
579 582 default='continue',
580 583 )
581 584 coreconfigitem('merge', 'preferancestor',
582 585 default=lambda: ['*'],
583 586 )
584 587 coreconfigitem('merge-tools', '.*',
585 588 default=None,
586 589 generic=True,
587 590 )
588 591 coreconfigitem('merge-tools', br'.*\.args$',
589 592 default="$local $base $other",
590 593 generic=True,
591 594 priority=-1,
592 595 )
593 596 coreconfigitem('merge-tools', br'.*\.binary$',
594 597 default=False,
595 598 generic=True,
596 599 priority=-1,
597 600 )
598 601 coreconfigitem('merge-tools', br'.*\.check$',
599 602 default=list,
600 603 generic=True,
601 604 priority=-1,
602 605 )
603 606 coreconfigitem('merge-tools', br'.*\.checkchanged$',
604 607 default=False,
605 608 generic=True,
606 609 priority=-1,
607 610 )
608 611 coreconfigitem('merge-tools', br'.*\.executable$',
609 612 default=dynamicdefault,
610 613 generic=True,
611 614 priority=-1,
612 615 )
613 616 coreconfigitem('merge-tools', br'.*\.fixeol$',
614 617 default=False,
615 618 generic=True,
616 619 priority=-1,
617 620 )
618 621 coreconfigitem('merge-tools', br'.*\.gui$',
619 622 default=False,
620 623 generic=True,
621 624 priority=-1,
622 625 )
623 626 coreconfigitem('merge-tools', br'.*\.priority$',
624 627 default=0,
625 628 generic=True,
626 629 priority=-1,
627 630 )
628 631 coreconfigitem('merge-tools', br'.*\.premerge$',
629 632 default=dynamicdefault,
630 633 generic=True,
631 634 priority=-1,
632 635 )
633 636 coreconfigitem('merge-tools', br'.*\.symlink$',
634 637 default=False,
635 638 generic=True,
636 639 priority=-1,
637 640 )
638 641 coreconfigitem('pager', 'attend-.*',
639 642 default=dynamicdefault,
640 643 generic=True,
641 644 )
642 645 coreconfigitem('pager', 'ignore',
643 646 default=list,
644 647 )
645 648 coreconfigitem('pager', 'pager',
646 649 default=dynamicdefault,
647 650 )
648 651 coreconfigitem('patch', 'eol',
649 652 default='strict',
650 653 )
651 654 coreconfigitem('patch', 'fuzz',
652 655 default=2,
653 656 )
654 657 coreconfigitem('paths', 'default',
655 658 default=None,
656 659 )
657 660 coreconfigitem('paths', 'default-push',
658 661 default=None,
659 662 )
660 663 coreconfigitem('paths', '.*',
661 664 default=None,
662 665 generic=True,
663 666 )
664 667 coreconfigitem('phases', 'checksubrepos',
665 668 default='follow',
666 669 )
667 670 coreconfigitem('phases', 'new-commit',
668 671 default='draft',
669 672 )
670 673 coreconfigitem('phases', 'publish',
671 674 default=True,
672 675 )
673 676 coreconfigitem('profiling', 'enabled',
674 677 default=False,
675 678 )
676 679 coreconfigitem('profiling', 'format',
677 680 default='text',
678 681 )
679 682 coreconfigitem('profiling', 'freq',
680 683 default=1000,
681 684 )
682 685 coreconfigitem('profiling', 'limit',
683 686 default=30,
684 687 )
685 688 coreconfigitem('profiling', 'nested',
686 689 default=0,
687 690 )
688 691 coreconfigitem('profiling', 'output',
689 692 default=None,
690 693 )
691 694 coreconfigitem('profiling', 'showmax',
692 695 default=0.999,
693 696 )
694 697 coreconfigitem('profiling', 'showmin',
695 698 default=dynamicdefault,
696 699 )
697 700 coreconfigitem('profiling', 'sort',
698 701 default='inlinetime',
699 702 )
700 703 coreconfigitem('profiling', 'statformat',
701 704 default='hotpath',
702 705 )
703 706 coreconfigitem('profiling', 'type',
704 707 default='stat',
705 708 )
706 709 coreconfigitem('progress', 'assume-tty',
707 710 default=False,
708 711 )
709 712 coreconfigitem('progress', 'changedelay',
710 713 default=1,
711 714 )
712 715 coreconfigitem('progress', 'clear-complete',
713 716 default=True,
714 717 )
715 718 coreconfigitem('progress', 'debug',
716 719 default=False,
717 720 )
718 721 coreconfigitem('progress', 'delay',
719 722 default=3,
720 723 )
721 724 coreconfigitem('progress', 'disable',
722 725 default=False,
723 726 )
724 727 coreconfigitem('progress', 'estimateinterval',
725 728 default=60.0,
726 729 )
727 730 coreconfigitem('progress', 'format',
728 731 default=lambda: ['topic', 'bar', 'number', 'estimate'],
729 732 )
730 733 coreconfigitem('progress', 'refresh',
731 734 default=0.1,
732 735 )
733 736 coreconfigitem('progress', 'width',
734 737 default=dynamicdefault,
735 738 )
736 739 coreconfigitem('push', 'pushvars.server',
737 740 default=False,
738 741 )
739 742 coreconfigitem('server', 'bundle1',
740 743 default=True,
741 744 )
742 745 coreconfigitem('server', 'bundle1gd',
743 746 default=None,
744 747 )
745 748 coreconfigitem('server', 'bundle1.pull',
746 749 default=None,
747 750 )
748 751 coreconfigitem('server', 'bundle1gd.pull',
749 752 default=None,
750 753 )
751 754 coreconfigitem('server', 'bundle1.push',
752 755 default=None,
753 756 )
754 757 coreconfigitem('server', 'bundle1gd.push',
755 758 default=None,
756 759 )
757 760 coreconfigitem('server', 'compressionengines',
758 761 default=list,
759 762 )
760 763 coreconfigitem('server', 'concurrent-push-mode',
761 764 default='strict',
762 765 )
763 766 coreconfigitem('server', 'disablefullbundle',
764 767 default=False,
765 768 )
766 769 coreconfigitem('server', 'maxhttpheaderlen',
767 770 default=1024,
768 771 )
769 772 coreconfigitem('server', 'preferuncompressed',
770 773 default=False,
771 774 )
772 775 coreconfigitem('server', 'uncompressed',
773 776 default=True,
774 777 )
775 778 coreconfigitem('server', 'uncompressedallowsecret',
776 779 default=False,
777 780 )
778 781 coreconfigitem('server', 'validate',
779 782 default=False,
780 783 )
781 784 coreconfigitem('server', 'zliblevel',
782 785 default=-1,
783 786 )
784 787 coreconfigitem('smtp', 'host',
785 788 default=None,
786 789 )
787 790 coreconfigitem('smtp', 'local_hostname',
788 791 default=None,
789 792 )
790 793 coreconfigitem('smtp', 'password',
791 794 default=None,
792 795 )
793 796 coreconfigitem('smtp', 'port',
794 797 default=dynamicdefault,
795 798 )
796 799 coreconfigitem('smtp', 'tls',
797 800 default='none',
798 801 )
799 802 coreconfigitem('smtp', 'username',
800 803 default=None,
801 804 )
802 805 coreconfigitem('sparse', 'missingwarning',
803 806 default=True,
804 807 )
805 808 coreconfigitem('templates', '.*',
806 809 default=None,
807 810 generic=True,
808 811 )
809 812 coreconfigitem('trusted', 'groups',
810 813 default=list,
811 814 )
812 815 coreconfigitem('trusted', 'users',
813 816 default=list,
814 817 )
815 818 coreconfigitem('ui', '_usedassubrepo',
816 819 default=False,
817 820 )
818 821 coreconfigitem('ui', 'allowemptycommit',
819 822 default=False,
820 823 )
821 824 coreconfigitem('ui', 'archivemeta',
822 825 default=True,
823 826 )
824 827 coreconfigitem('ui', 'askusername',
825 828 default=False,
826 829 )
827 830 coreconfigitem('ui', 'clonebundlefallback',
828 831 default=False,
829 832 )
830 833 coreconfigitem('ui', 'clonebundleprefers',
831 834 default=list,
832 835 )
833 836 coreconfigitem('ui', 'clonebundles',
834 837 default=True,
835 838 )
836 839 coreconfigitem('ui', 'color',
837 840 default='auto',
838 841 )
839 842 coreconfigitem('ui', 'commitsubrepos',
840 843 default=False,
841 844 )
842 845 coreconfigitem('ui', 'debug',
843 846 default=False,
844 847 )
845 848 coreconfigitem('ui', 'debugger',
846 849 default=None,
847 850 )
848 851 coreconfigitem('ui', 'fallbackencoding',
849 852 default=None,
850 853 )
851 854 coreconfigitem('ui', 'forcecwd',
852 855 default=None,
853 856 )
854 857 coreconfigitem('ui', 'forcemerge',
855 858 default=None,
856 859 )
857 860 coreconfigitem('ui', 'formatdebug',
858 861 default=False,
859 862 )
860 863 coreconfigitem('ui', 'formatjson',
861 864 default=False,
862 865 )
863 866 coreconfigitem('ui', 'formatted',
864 867 default=None,
865 868 )
866 869 coreconfigitem('ui', 'graphnodetemplate',
867 870 default=None,
868 871 )
869 872 coreconfigitem('ui', 'http2debuglevel',
870 873 default=None,
871 874 )
872 875 coreconfigitem('ui', 'interactive',
873 876 default=None,
874 877 )
875 878 coreconfigitem('ui', 'interface',
876 879 default=None,
877 880 )
878 881 coreconfigitem('ui', 'interface.chunkselector',
879 882 default=None,
880 883 )
881 884 coreconfigitem('ui', 'logblockedtimes',
882 885 default=False,
883 886 )
884 887 coreconfigitem('ui', 'logtemplate',
885 888 default=None,
886 889 )
887 890 coreconfigitem('ui', 'merge',
888 891 default=None,
889 892 )
890 893 coreconfigitem('ui', 'mergemarkers',
891 894 default='basic',
892 895 )
893 896 coreconfigitem('ui', 'mergemarkertemplate',
894 897 default=('{node|short} '
895 898 '{ifeq(tags, "tip", "", '
896 899 'ifeq(tags, "", "", "{tags} "))}'
897 900 '{if(bookmarks, "{bookmarks} ")}'
898 901 '{ifeq(branch, "default", "", "{branch} ")}'
899 902 '- {author|user}: {desc|firstline}')
900 903 )
901 904 coreconfigitem('ui', 'nontty',
902 905 default=False,
903 906 )
904 907 coreconfigitem('ui', 'origbackuppath',
905 908 default=None,
906 909 )
907 910 coreconfigitem('ui', 'paginate',
908 911 default=True,
909 912 )
910 913 coreconfigitem('ui', 'patch',
911 914 default=None,
912 915 )
913 916 coreconfigitem('ui', 'portablefilenames',
914 917 default='warn',
915 918 )
916 919 coreconfigitem('ui', 'promptecho',
917 920 default=False,
918 921 )
919 922 coreconfigitem('ui', 'quiet',
920 923 default=False,
921 924 )
922 925 coreconfigitem('ui', 'quietbookmarkmove',
923 926 default=False,
924 927 )
925 928 coreconfigitem('ui', 'remotecmd',
926 929 default='hg',
927 930 )
928 931 coreconfigitem('ui', 'report_untrusted',
929 932 default=True,
930 933 )
931 934 coreconfigitem('ui', 'rollback',
932 935 default=True,
933 936 )
934 937 coreconfigitem('ui', 'slash',
935 938 default=False,
936 939 )
937 940 coreconfigitem('ui', 'ssh',
938 941 default='ssh',
939 942 )
940 943 coreconfigitem('ui', 'statuscopies',
941 944 default=False,
942 945 )
943 946 coreconfigitem('ui', 'strict',
944 947 default=False,
945 948 )
946 949 coreconfigitem('ui', 'style',
947 950 default='',
948 951 )
949 952 coreconfigitem('ui', 'supportcontact',
950 953 default=None,
951 954 )
952 955 coreconfigitem('ui', 'textwidth',
953 956 default=78,
954 957 )
955 958 coreconfigitem('ui', 'timeout',
956 959 default='600',
957 960 )
958 961 coreconfigitem('ui', 'traceback',
959 962 default=False,
960 963 )
961 964 coreconfigitem('ui', 'tweakdefaults',
962 965 default=False,
963 966 )
964 967 coreconfigitem('ui', 'usehttp2',
965 968 default=False,
966 969 )
967 970 coreconfigitem('ui', 'username',
968 971 alias=[('ui', 'user')]
969 972 )
970 973 coreconfigitem('ui', 'verbose',
971 974 default=False,
972 975 )
973 976 coreconfigitem('verify', 'skipflags',
974 977 default=None,
975 978 )
976 979 coreconfigitem('web', 'allowbz2',
977 980 default=False,
978 981 )
979 982 coreconfigitem('web', 'allowgz',
980 983 default=False,
981 984 )
982 985 coreconfigitem('web', 'allowpull',
983 986 default=True,
984 987 )
985 988 coreconfigitem('web', 'allow_push',
986 989 default=list,
987 990 )
988 991 coreconfigitem('web', 'allowzip',
989 992 default=False,
990 993 )
991 994 coreconfigitem('web', 'archivesubrepos',
992 995 default=False,
993 996 )
994 997 coreconfigitem('web', 'cache',
995 998 default=True,
996 999 )
997 1000 coreconfigitem('web', 'contact',
998 1001 default=None,
999 1002 )
1000 1003 coreconfigitem('web', 'deny_push',
1001 1004 default=list,
1002 1005 )
1003 1006 coreconfigitem('web', 'guessmime',
1004 1007 default=False,
1005 1008 )
1006 1009 coreconfigitem('web', 'hidden',
1007 1010 default=False,
1008 1011 )
1009 1012 coreconfigitem('web', 'labels',
1010 1013 default=list,
1011 1014 )
1012 1015 coreconfigitem('web', 'logoimg',
1013 1016 default='hglogo.png',
1014 1017 )
1015 1018 coreconfigitem('web', 'logourl',
1016 1019 default='https://mercurial-scm.org/',
1017 1020 )
1018 1021 coreconfigitem('web', 'accesslog',
1019 1022 default='-',
1020 1023 )
1021 1024 coreconfigitem('web', 'address',
1022 1025 default='',
1023 1026 )
1024 1027 coreconfigitem('web', 'allow_archive',
1025 1028 default=list,
1026 1029 )
1027 1030 coreconfigitem('web', 'allow_read',
1028 1031 default=list,
1029 1032 )
1030 1033 coreconfigitem('web', 'baseurl',
1031 1034 default=None,
1032 1035 )
1033 1036 coreconfigitem('web', 'cacerts',
1034 1037 default=None,
1035 1038 )
1036 1039 coreconfigitem('web', 'certificate',
1037 1040 default=None,
1038 1041 )
1039 1042 coreconfigitem('web', 'collapse',
1040 1043 default=False,
1041 1044 )
1042 1045 coreconfigitem('web', 'csp',
1043 1046 default=None,
1044 1047 )
1045 1048 coreconfigitem('web', 'deny_read',
1046 1049 default=list,
1047 1050 )
1048 1051 coreconfigitem('web', 'descend',
1049 1052 default=True,
1050 1053 )
1051 1054 coreconfigitem('web', 'description',
1052 1055 default="",
1053 1056 )
1054 1057 coreconfigitem('web', 'encoding',
1055 1058 default=lambda: encoding.encoding,
1056 1059 )
1057 1060 coreconfigitem('web', 'errorlog',
1058 1061 default='-',
1059 1062 )
1060 1063 coreconfigitem('web', 'ipv6',
1061 1064 default=False,
1062 1065 )
1063 1066 coreconfigitem('web', 'maxchanges',
1064 1067 default=10,
1065 1068 )
1066 1069 coreconfigitem('web', 'maxfiles',
1067 1070 default=10,
1068 1071 )
1069 1072 coreconfigitem('web', 'maxshortchanges',
1070 1073 default=60,
1071 1074 )
1072 1075 coreconfigitem('web', 'motd',
1073 1076 default='',
1074 1077 )
1075 1078 coreconfigitem('web', 'name',
1076 1079 default=dynamicdefault,
1077 1080 )
1078 1081 coreconfigitem('web', 'port',
1079 1082 default=8000,
1080 1083 )
1081 1084 coreconfigitem('web', 'prefix',
1082 1085 default='',
1083 1086 )
1084 1087 coreconfigitem('web', 'push_ssl',
1085 1088 default=True,
1086 1089 )
1087 1090 coreconfigitem('web', 'refreshinterval',
1088 1091 default=20,
1089 1092 )
1090 1093 coreconfigitem('web', 'staticurl',
1091 1094 default=None,
1092 1095 )
1093 1096 coreconfigitem('web', 'stripes',
1094 1097 default=1,
1095 1098 )
1096 1099 coreconfigitem('web', 'style',
1097 1100 default='paper',
1098 1101 )
1099 1102 coreconfigitem('web', 'templates',
1100 1103 default=None,
1101 1104 )
1102 1105 coreconfigitem('web', 'view',
1103 1106 default='served',
1104 1107 )
1105 1108 coreconfigitem('worker', 'backgroundclose',
1106 1109 default=dynamicdefault,
1107 1110 )
1108 1111 # Windows defaults to a limit of 512 open files. A buffer of 128
1109 1112 # should give us enough headway.
1110 1113 coreconfigitem('worker', 'backgroundclosemaxqueue',
1111 1114 default=384,
1112 1115 )
1113 1116 coreconfigitem('worker', 'backgroundcloseminfilecount',
1114 1117 default=2048,
1115 1118 )
1116 1119 coreconfigitem('worker', 'backgroundclosethreadcount',
1117 1120 default=4,
1118 1121 )
1119 1122 coreconfigitem('worker', 'numcpus',
1120 1123 default=None,
1121 1124 )
1122 1125
1123 1126 # Rebase related configuration moved to core because other extension are doing
1124 1127 # strange things. For example, shelve import the extensions to reuse some bit
1125 1128 # without formally loading it.
1126 1129 coreconfigitem('commands', 'rebase.requiredest',
1127 1130 default=False,
1128 1131 )
1129 1132 coreconfigitem('experimental', 'rebaseskipobsolete',
1130 1133 default=True,
1131 1134 )
1132 1135 coreconfigitem('rebase', 'singletransaction',
1133 1136 default=False,
1134 1137 )
General Comments 0
You need to be logged in to leave comments. Login now