##// END OF EJS Templates
patchbomb: use cmdutil.command decorator
Adrian Buehlmann -
r14309:37e80214 default
parent child Browse files
Show More
@@ -1,562 +1,550
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
48 48 import os, errno, socket, tempfile, cStringIO, time
49 49 import email.MIMEMultipart, email.MIMEBase
50 50 import email.Utils, email.Encoders, email.Generator
51 51 from mercurial import cmdutil, commands, hg, mail, patch, util, discovery
52 52 from mercurial.i18n import _
53 53 from mercurial.node import bin
54 54
55 cmdtable = {}
56 command = cmdutil.command(cmdtable)
57
55 58 def prompt(ui, prompt, default=None, rest=':'):
56 59 if not ui.interactive() and default is None:
57 60 raise util.Abort(_("%s Please enter a valid value" % (prompt + rest)))
58 61 if default:
59 62 prompt += ' [%s]' % default
60 63 prompt += rest
61 64 while True:
62 65 r = ui.prompt(prompt, default=default)
63 66 if r:
64 67 return r
65 68 if default is not None:
66 69 return default
67 70 ui.warn(_('Please enter a valid value.\n'))
68 71
69 72 def introneeded(opts, number):
70 73 '''is an introductory message required?'''
71 74 return number > 1 or opts.get('intro') or opts.get('desc')
72 75
73 76 def makepatch(ui, repo, patchlines, opts, _charsets, idx, total,
74 77 patchname=None):
75 78
76 79 desc = []
77 80 node = None
78 81 body = ''
79 82
80 83 for line in patchlines:
81 84 if line.startswith('#'):
82 85 if line.startswith('# Node ID'):
83 86 node = line.split()[-1]
84 87 continue
85 88 if line.startswith('diff -r') or line.startswith('diff --git'):
86 89 break
87 90 desc.append(line)
88 91
89 92 if not patchname and not node:
90 93 raise ValueError
91 94
92 95 if opts.get('attach'):
93 96 body = ('\n'.join(desc[1:]).strip() or
94 97 'Patch subject is complete summary.')
95 98 body += '\n\n\n'
96 99
97 100 if opts.get('plain'):
98 101 while patchlines and patchlines[0].startswith('# '):
99 102 patchlines.pop(0)
100 103 if patchlines:
101 104 patchlines.pop(0)
102 105 while patchlines and not patchlines[0].strip():
103 106 patchlines.pop(0)
104 107
105 108 ds = patch.diffstat(patchlines)
106 109 if opts.get('diffstat'):
107 110 body += ds + '\n\n'
108 111
109 112 if opts.get('attach') or opts.get('inline'):
110 113 msg = email.MIMEMultipart.MIMEMultipart()
111 114 if body:
112 115 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
113 116 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch', opts.get('test'))
114 117 binnode = bin(node)
115 118 # if node is mq patch, it will have the patch file's name as a tag
116 119 if not patchname:
117 120 patchtags = [t for t in repo.nodetags(binnode)
118 121 if t.endswith('.patch') or t.endswith('.diff')]
119 122 if patchtags:
120 123 patchname = patchtags[0]
121 124 elif total > 1:
122 125 patchname = cmdutil.makefilename(repo, '%b-%n.patch',
123 126 binnode, seqno=idx, total=total)
124 127 else:
125 128 patchname = cmdutil.makefilename(repo, '%b.patch', binnode)
126 129 disposition = 'inline'
127 130 if opts.get('attach'):
128 131 disposition = 'attachment'
129 132 p['Content-Disposition'] = disposition + '; filename=' + patchname
130 133 msg.attach(p)
131 134 else:
132 135 body += '\n'.join(patchlines)
133 136 msg = mail.mimetextpatch(body, display=opts.get('test'))
134 137
135 138 flag = ' '.join(opts.get('flag'))
136 139 if flag:
137 140 flag = ' ' + flag
138 141
139 142 subj = desc[0].strip().rstrip('. ')
140 143 if not introneeded(opts, total):
141 144 subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
142 145 else:
143 146 tlen = len(str(total))
144 147 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
145 148 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
146 149 msg['X-Mercurial-Node'] = node
147 150 return msg, subj, ds
148 151
152 emailopts = [
153 ('a', 'attach', None, _('send patches as attachments')),
154 ('i', 'inline', None, _('send patches as inline attachments')),
155 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
156 ('c', 'cc', [], _('email addresses of copy recipients')),
157 ('', 'confirm', None, _('ask for confirmation before sending')),
158 ('d', 'diffstat', None, _('add diffstat output to messages')),
159 ('', 'date', '', _('use the given date as the sending date')),
160 ('', 'desc', '', _('use the given file as the series description')),
161 ('f', 'from', '', _('email address of sender')),
162 ('n', 'test', None, _('print messages that would be sent')),
163 ('m', 'mbox', '', _('write messages to mbox file instead of sending them')),
164 ('', 'reply-to', [], _('email addresses replies should be sent to')),
165 ('s', 'subject', '', _('subject of first message (intro or single patch)')),
166 ('', 'in-reply-to', '', _('message identifier to reply to')),
167 ('', 'flag', [], _('flags to add in subject prefixes')),
168 ('t', 'to', [], _('email addresses of recipients'))]
169
170 @command('email',
171 [('g', 'git', None, _('use git extended diff format')),
172 ('', 'plain', None, _('omit hg patch header')),
173 ('o', 'outgoing', None,
174 _('send changes not found in the target repository')),
175 ('b', 'bundle', None, _('send changes not in target as a binary bundle')),
176 ('', 'bundlename', 'bundle',
177 _('name of the bundle attachment file'), _('NAME')),
178 ('r', 'rev', [], _('a revision to send'), _('REV')),
179 ('', 'force', None, _('run even when remote repository is unrelated '
180 '(with -b/--bundle)')),
181 ('', 'base', [], _('a base changeset to specify instead of a destination '
182 '(with -b/--bundle)'), _('REV')),
183 ('', 'intro', None, _('send an introduction email for a single patch')),
184 ] + emailopts + commands.remoteopts,
185 _('hg email [OPTION]... [DEST]...'))
149 186 def patchbomb(ui, repo, *revs, **opts):
150 187 '''send changesets by email
151 188
152 189 By default, diffs are sent in the format generated by
153 190 :hg:`export`, one per message. The series starts with a "[PATCH 0
154 191 of N]" introduction, which describes the series as a whole.
155 192
156 193 Each patch email has a Subject line of "[PATCH M of N] ...", using
157 194 the first line of the changeset description as the subject text.
158 195 The message contains two or three parts. First, the changeset
159 196 description.
160 197
161 198 With the -d/--diffstat option, if the diffstat program is
162 199 installed, the result of running diffstat on the patch is inserted.
163 200
164 201 Finally, the patch itself, as generated by :hg:`export`.
165 202
166 203 With the -d/--diffstat or -c/--confirm options, you will be presented
167 204 with a final summary of all messages and asked for confirmation before
168 205 the messages are sent.
169 206
170 207 By default the patch is included as text in the email body for
171 208 easy reviewing. Using the -a/--attach option will instead create
172 209 an attachment for the patch. With -i/--inline an inline attachment
173 210 will be created.
174 211
175 212 With -o/--outgoing, emails will be generated for patches not found
176 213 in the destination repository (or only those which are ancestors
177 214 of the specified revisions if any are provided)
178 215
179 216 With -b/--bundle, changesets are selected as for --outgoing, but a
180 217 single email containing a binary Mercurial bundle as an attachment
181 218 will be sent.
182 219
183 220 With -m/--mbox, instead of previewing each patchbomb message in a
184 221 pager or sending the messages directly, it will create a UNIX
185 222 mailbox file with the patch emails. This mailbox file can be
186 223 previewed with any mail user agent which supports UNIX mbox
187 224 files.
188 225
189 226 With -n/--test, all steps will run, but mail will not be sent.
190 227 You will be prompted for an email recipient address, a subject and
191 228 an introductory message describing the patches of your patchbomb.
192 229 Then when all is done, patchbomb messages are displayed. If the
193 230 PAGER environment variable is set, your pager will be fired up once
194 231 for each patchbomb message, so you can verify everything is alright.
195 232
196 233 In case email sending fails, you will find a backup of your series
197 234 introductory message in ``.hg/last-email.txt``.
198 235
199 236 Examples::
200 237
201 238 hg email -r 3000 # send patch 3000 only
202 239 hg email -r 3000 -r 3001 # send patches 3000 and 3001
203 240 hg email -r 3000:3005 # send patches 3000 through 3005
204 241 hg email 3000 # send patch 3000 (deprecated)
205 242
206 243 hg email -o # send all patches not in default
207 244 hg email -o DEST # send all patches not in DEST
208 245 hg email -o -r 3000 # send all ancestors of 3000 not in default
209 246 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
210 247
211 248 hg email -b # send bundle of all patches not in default
212 249 hg email -b DEST # send bundle of all patches not in DEST
213 250 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
214 251 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
215 252
216 253 hg email -o -m mbox && # generate an mbox file...
217 254 mutt -R -f mbox # ... and view it with mutt
218 255 hg email -o -m mbox && # generate an mbox file ...
219 256 formail -s sendmail \\ # ... and use formail to send from the mbox
220 257 -bm -t < mbox # ... using sendmail
221 258
222 259 Before using this command, you will need to enable email in your
223 260 hgrc. See the [email] section in hgrc(5) for details.
224 261 '''
225 262
226 263 _charsets = mail._charsets(ui)
227 264
228 265 bundle = opts.get('bundle')
229 266 date = opts.get('date')
230 267 mbox = opts.get('mbox')
231 268 outgoing = opts.get('outgoing')
232 269 rev = opts.get('rev')
233 270 # internal option used by pbranches
234 271 patches = opts.get('patches')
235 272
236 273 def getoutgoing(dest, revs):
237 274 '''Return the revisions present locally but not in dest'''
238 275 dest = ui.expandpath(dest or 'default-push', dest or 'default')
239 276 dest, branches = hg.parseurl(dest)
240 277 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
241 278 other = hg.repository(hg.remoteui(repo, opts), dest)
242 279 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
243 280 common, _anyinc, _heads = discovery.findcommonincoming(repo, other)
244 281 nodes = revs and map(repo.lookup, revs) or revs
245 282 o = repo.changelog.findmissing(common, heads=nodes)
246 283 if not o:
247 284 ui.status(_("no changes found\n"))
248 285 return []
249 286 return [str(repo.changelog.rev(r)) for r in o]
250 287
251 288 def getpatches(revs):
252 289 for r in cmdutil.revrange(repo, revs):
253 290 output = cStringIO.StringIO()
254 291 cmdutil.export(repo, [r], fp=output,
255 292 opts=patch.diffopts(ui, opts))
256 293 yield output.getvalue().split('\n')
257 294
258 295 def getbundle(dest):
259 296 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
260 297 tmpfn = os.path.join(tmpdir, 'bundle')
261 298 try:
262 299 commands.bundle(ui, repo, tmpfn, dest, **opts)
263 300 fp = open(tmpfn, 'rb')
264 301 data = fp.read()
265 302 fp.close()
266 303 return data
267 304 finally:
268 305 try:
269 306 os.unlink(tmpfn)
270 307 except:
271 308 pass
272 309 os.rmdir(tmpdir)
273 310
274 311 if not (opts.get('test') or mbox):
275 312 # really sending
276 313 mail.validateconfig(ui)
277 314
278 315 if not (revs or rev or outgoing or bundle or patches):
279 316 raise util.Abort(_('specify at least one changeset with -r or -o'))
280 317
281 318 if outgoing and bundle:
282 319 raise util.Abort(_("--outgoing mode always on with --bundle;"
283 320 " do not re-specify --outgoing"))
284 321
285 322 if outgoing or bundle:
286 323 if len(revs) > 1:
287 324 raise util.Abort(_("too many destinations"))
288 325 dest = revs and revs[0] or None
289 326 revs = []
290 327
291 328 if rev:
292 329 if revs:
293 330 raise util.Abort(_('use only one form to specify the revision'))
294 331 revs = rev
295 332
296 333 if outgoing:
297 334 revs = getoutgoing(dest, rev)
298 335 if bundle:
299 336 opts['revs'] = revs
300 337
301 338 # start
302 339 if date:
303 340 start_time = util.parsedate(date)
304 341 else:
305 342 start_time = util.makedate()
306 343
307 344 def genmsgid(id):
308 345 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
309 346
310 347 def getdescription(body, sender):
311 348 if opts.get('desc'):
312 349 body = open(opts.get('desc')).read()
313 350 else:
314 351 ui.write(_('\nWrite the introductory message for the '
315 352 'patch series.\n\n'))
316 353 body = ui.edit(body, sender)
317 354 # Save serie description in case sendmail fails
318 355 msgfile = repo.opener('last-email.txt', 'wb')
319 356 msgfile.write(body)
320 357 msgfile.close()
321 358 return body
322 359
323 360 def getpatchmsgs(patches, patchnames=None):
324 361 jumbo = []
325 362 msgs = []
326 363
327 364 ui.write(_('This patch series consists of %d patches.\n\n')
328 365 % len(patches))
329 366
330 367 name = None
331 368 for i, p in enumerate(patches):
332 369 jumbo.extend(p)
333 370 if patchnames:
334 371 name = patchnames[i]
335 372 msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
336 373 len(patches), name)
337 374 msgs.append(msg)
338 375
339 376 if introneeded(opts, len(patches)):
340 377 tlen = len(str(len(patches)))
341 378
342 379 flag = ' '.join(opts.get('flag'))
343 380 if flag:
344 381 subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag)
345 382 else:
346 383 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
347 384 subj += ' ' + (opts.get('subject') or
348 385 prompt(ui, 'Subject: ', rest=subj))
349 386
350 387 body = ''
351 388 ds = patch.diffstat(jumbo)
352 389 if ds and opts.get('diffstat'):
353 390 body = '\n' + ds
354 391
355 392 body = getdescription(body, sender)
356 393 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
357 394 msg['Subject'] = mail.headencode(ui, subj, _charsets,
358 395 opts.get('test'))
359 396
360 397 msgs.insert(0, (msg, subj, ds))
361 398 return msgs
362 399
363 400 def getbundlemsgs(bundle):
364 401 subj = (opts.get('subject')
365 402 or prompt(ui, 'Subject:', 'A bundle for your repository'))
366 403
367 404 body = getdescription('', sender)
368 405 msg = email.MIMEMultipart.MIMEMultipart()
369 406 if body:
370 407 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
371 408 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
372 409 datapart.set_payload(bundle)
373 410 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
374 411 datapart.add_header('Content-Disposition', 'attachment',
375 412 filename=bundlename)
376 413 email.Encoders.encode_base64(datapart)
377 414 msg.attach(datapart)
378 415 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
379 416 return [(msg, subj, None)]
380 417
381 418 sender = (opts.get('from') or ui.config('email', 'from') or
382 419 ui.config('patchbomb', 'from') or
383 420 prompt(ui, 'From', ui.username()))
384 421
385 422 if patches:
386 423 msgs = getpatchmsgs(patches, opts.get('patchnames'))
387 424 elif bundle:
388 425 msgs = getbundlemsgs(getbundle(dest))
389 426 else:
390 427 msgs = getpatchmsgs(list(getpatches(revs)))
391 428
392 429 showaddrs = []
393 430
394 431 def getaddrs(opt, prpt=None, default=None):
395 432 addrs = opts.get(opt.replace('-', '_'))
396 433 if opt != 'reply-to':
397 434 showaddr = '%s:' % opt.capitalize()
398 435 else:
399 436 showaddr = 'Reply-To:'
400 437
401 438 if addrs:
402 439 showaddrs.append('%s %s' % (showaddr, ', '.join(addrs)))
403 440 return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))
404 441
405 442 addrs = ui.config('email', opt) or ui.config('patchbomb', opt) or ''
406 443 if not addrs and prpt:
407 444 addrs = prompt(ui, prpt, default)
408 445
409 446 if addrs:
410 447 showaddrs.append('%s %s' % (showaddr, addrs))
411 448 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))
412 449
413 450 to = getaddrs('to', 'To')
414 451 cc = getaddrs('cc', 'Cc', '')
415 452 bcc = getaddrs('bcc')
416 453 replyto = getaddrs('reply-to')
417 454
418 455 if opts.get('diffstat') or opts.get('confirm'):
419 456 ui.write(_('\nFinal summary:\n\n'))
420 457 ui.write('From: %s\n' % sender)
421 458 for addr in showaddrs:
422 459 ui.write('%s\n' % addr)
423 460 for m, subj, ds in msgs:
424 461 ui.write('Subject: %s\n' % subj)
425 462 if ds:
426 463 ui.write(ds)
427 464 ui.write('\n')
428 465 if ui.promptchoice(_('are you sure you want to send (yn)?'),
429 466 (_('&Yes'), _('&No'))):
430 467 raise util.Abort(_('patchbomb canceled'))
431 468
432 469 ui.write('\n')
433 470
434 471 parent = opts.get('in_reply_to') or None
435 472 # angle brackets may be omitted, they're not semantically part of the msg-id
436 473 if parent is not None:
437 474 if not parent.startswith('<'):
438 475 parent = '<' + parent
439 476 if not parent.endswith('>'):
440 477 parent += '>'
441 478
442 479 first = True
443 480
444 481 sender_addr = email.Utils.parseaddr(sender)[1]
445 482 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
446 483 sendmail = None
447 484 for i, (m, subj, ds) in enumerate(msgs):
448 485 try:
449 486 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
450 487 except TypeError:
451 488 m['Message-Id'] = genmsgid('patchbomb')
452 489 if parent:
453 490 m['In-Reply-To'] = parent
454 491 m['References'] = parent
455 492 if first:
456 493 parent = m['Message-Id']
457 494 first = False
458 495
459 496 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
460 497 m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)
461 498
462 499 start_time = (start_time[0] + 1, start_time[1])
463 500 m['From'] = sender
464 501 m['To'] = ', '.join(to)
465 502 if cc:
466 503 m['Cc'] = ', '.join(cc)
467 504 if bcc:
468 505 m['Bcc'] = ', '.join(bcc)
469 506 if replyto:
470 507 m['Reply-To'] = ', '.join(replyto)
471 508 if opts.get('test'):
472 509 ui.status(_('Displaying '), subj, ' ...\n')
473 510 ui.flush()
474 511 if 'PAGER' in os.environ and not ui.plain():
475 512 fp = util.popen(os.environ['PAGER'], 'w')
476 513 else:
477 514 fp = ui
478 515 generator = email.Generator.Generator(fp, mangle_from_=False)
479 516 try:
480 517 generator.flatten(m, 0)
481 518 fp.write('\n')
482 519 except IOError, inst:
483 520 if inst.errno != errno.EPIPE:
484 521 raise
485 522 if fp is not ui:
486 523 fp.close()
487 524 elif mbox:
488 525 ui.status(_('Writing '), subj, ' ...\n')
489 526 ui.progress(_('writing'), i, item=subj, total=len(msgs))
490 527 fp = open(mbox, 'In-Reply-To' in m and 'ab+' or 'wb+')
491 528 generator = email.Generator.Generator(fp, mangle_from_=True)
492 529 # Should be time.asctime(), but Windows prints 2-characters day
493 530 # of month instead of one. Make them print the same thing.
494 531 date = time.strftime('%a %b %d %H:%M:%S %Y',
495 532 time.localtime(start_time[0]))
496 533 fp.write('From %s %s\n' % (sender_addr, date))
497 534 generator.flatten(m, 0)
498 535 fp.write('\n\n')
499 536 fp.close()
500 537 else:
501 538 if not sendmail:
502 539 sendmail = mail.connect(ui)
503 540 ui.status(_('Sending '), subj, ' ...\n')
504 541 ui.progress(_('sending'), i, item=subj, total=len(msgs))
505 542 # Exim does not remove the Bcc field
506 543 del m['Bcc']
507 544 fp = cStringIO.StringIO()
508 545 generator = email.Generator.Generator(fp, mangle_from_=False)
509 546 generator.flatten(m, 0)
510 547 sendmail(sender, to + bcc + cc, fp.getvalue())
511 548
512 549 ui.progress(_('writing'), None)
513 550 ui.progress(_('sending'), None)
514
515 emailopts = [
516 ('a', 'attach', None, _('send patches as attachments')),
517 ('i', 'inline', None, _('send patches as inline attachments')),
518 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
519 ('c', 'cc', [], _('email addresses of copy recipients')),
520 ('', 'confirm', None, _('ask for confirmation before sending')),
521 ('d', 'diffstat', None, _('add diffstat output to messages')),
522 ('', 'date', '', _('use the given date as the sending date')),
523 ('', 'desc', '', _('use the given file as the series description')),
524 ('f', 'from', '', _('email address of sender')),
525 ('n', 'test', None, _('print messages that would be sent')),
526 ('m', 'mbox', '',
527 _('write messages to mbox file instead of sending them')),
528 ('', 'reply-to', [], _('email addresses replies should be sent to')),
529 ('s', 'subject', '',
530 _('subject of first message (intro or single patch)')),
531 ('', 'in-reply-to', '',
532 _('message identifier to reply to')),
533 ('', 'flag', [], _('flags to add in subject prefixes')),
534 ('t', 'to', [], _('email addresses of recipients')),
535 ]
536
537
538 cmdtable = {
539 "email":
540 (patchbomb,
541 [('g', 'git', None, _('use git extended diff format')),
542 ('', 'plain', None, _('omit hg patch header')),
543 ('o', 'outgoing', None,
544 _('send changes not found in the target repository')),
545 ('b', 'bundle', None,
546 _('send changes not in target as a binary bundle')),
547 ('', 'bundlename', 'bundle',
548 _('name of the bundle attachment file'), _('NAME')),
549 ('r', 'rev', [],
550 _('a revision to send'), _('REV')),
551 ('', 'force', None,
552 _('run even when remote repository is unrelated '
553 '(with -b/--bundle)')),
554 ('', 'base', [],
555 _('a base changeset to specify instead of a destination '
556 '(with -b/--bundle)'),
557 _('REV')),
558 ('', 'intro', None,
559 _('send an introduction email for a single patch')),
560 ] + emailopts + commands.remoteopts,
561 _('hg email [OPTION]... [DEST]...'))
562 }
General Comments 0
You need to be logged in to leave comments. Login now