##// END OF EJS Templates
patchbomb: rename email function...
timeless -
r27150:88aaddb1 default
parent child Browse files
Show More
@@ -1,710 +1,710
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 You can control the default inclusion of an introduction message with the
48 48 ``patchbomb.intro`` configuration option. The configuration is always
49 49 overwritten by command line flags like --intro and --desc::
50 50
51 51 [patchbomb]
52 52 intro=auto # include introduction message if more than 1 patch (default)
53 53 intro=never # never include an introduction message
54 54 intro=always # always include an introduction message
55 55
56 56 You can set patchbomb to always ask for confirmation by setting
57 57 ``patchbomb.confirm`` to true.
58 58 '''
59 59
60 60 import os, errno, socket, tempfile, cStringIO
61 import email
61 import email as emailmod
62 62
63 63 from mercurial import cmdutil, commands, hg, mail, patch, util, error
64 64 from mercurial import scmutil
65 65 from mercurial.i18n import _
66 66 from mercurial.node import bin
67 67
68 68 cmdtable = {}
69 69 command = cmdutil.command(cmdtable)
70 70 # Note for extension authors: ONLY specify testedwith = 'internal' for
71 71 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
72 72 # be specifying the version(s) of Mercurial they are tested with, or
73 73 # leave the attribute unspecified.
74 74 testedwith = 'internal'
75 75
76 76 def _addpullheader(seq, ctx):
77 77 """Add a header pointing to a public URL where the changeset is available
78 78 """
79 79 repo = ctx.repo()
80 80 # experimental config: patchbomb.publicurl
81 81 # waiting for some logic that check that the changeset are available on the
82 82 # destination before patchbombing anything.
83 83 pullurl = repo.ui.config('patchbomb', 'publicurl')
84 84 if pullurl is not None:
85 85 return ('Available At %s\n'
86 86 '# hg pull %s -r %s' % (pullurl, pullurl, ctx))
87 87 return None
88 88
89 89 def uisetup(ui):
90 90 cmdutil.extraexport.append('pullurl')
91 91 cmdutil.extraexportmap['pullurl'] = _addpullheader
92 92
93 93
94 94 def prompt(ui, prompt, default=None, rest=':'):
95 95 if default:
96 96 prompt += ' [%s]' % default
97 97 return ui.prompt(prompt + rest, default)
98 98
99 99 def introwanted(ui, opts, number):
100 100 '''is an introductory message apparently wanted?'''
101 101 introconfig = ui.config('patchbomb', 'intro', 'auto')
102 102 if opts.get('intro') or opts.get('desc'):
103 103 intro = True
104 104 elif introconfig == 'always':
105 105 intro = True
106 106 elif introconfig == 'never':
107 107 intro = False
108 108 elif introconfig == 'auto':
109 109 intro = 1 < number
110 110 else:
111 111 ui.write_err(_('warning: invalid patchbomb.intro value "%s"\n')
112 112 % introconfig)
113 113 ui.write_err(_('(should be one of always, never, auto)\n'))
114 114 intro = 1 < number
115 115 return intro
116 116
117 117 def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered,
118 118 patchname=None):
119 119
120 120 desc = []
121 121 node = None
122 122 body = ''
123 123
124 124 for line in patchlines:
125 125 if line.startswith('#'):
126 126 if line.startswith('# Node ID'):
127 127 node = line.split()[-1]
128 128 continue
129 129 if line.startswith('diff -r') or line.startswith('diff --git'):
130 130 break
131 131 desc.append(line)
132 132
133 133 if not patchname and not node:
134 134 raise ValueError
135 135
136 136 if opts.get('attach') and not opts.get('body'):
137 137 body = ('\n'.join(desc[1:]).strip() or
138 138 'Patch subject is complete summary.')
139 139 body += '\n\n\n'
140 140
141 141 if opts.get('plain'):
142 142 while patchlines and patchlines[0].startswith('# '):
143 143 patchlines.pop(0)
144 144 if patchlines:
145 145 patchlines.pop(0)
146 146 while patchlines and not patchlines[0].strip():
147 147 patchlines.pop(0)
148 148
149 149 ds = patch.diffstat(patchlines, git=opts.get('git'))
150 150 if opts.get('diffstat'):
151 151 body += ds + '\n\n'
152 152
153 153 addattachment = opts.get('attach') or opts.get('inline')
154 154 if not addattachment or opts.get('body'):
155 155 body += '\n'.join(patchlines)
156 156
157 157 if addattachment:
158 msg = email.MIMEMultipart.MIMEMultipart()
158 msg = emailmod.MIMEMultipart.MIMEMultipart()
159 159 if body:
160 160 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
161 161 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch',
162 162 opts.get('test'))
163 163 binnode = bin(node)
164 164 # if node is mq patch, it will have the patch file's name as a tag
165 165 if not patchname:
166 166 patchtags = [t for t in repo.nodetags(binnode)
167 167 if t.endswith('.patch') or t.endswith('.diff')]
168 168 if patchtags:
169 169 patchname = patchtags[0]
170 170 elif total > 1:
171 171 patchname = cmdutil.makefilename(repo, '%b-%n.patch',
172 172 binnode, seqno=idx,
173 173 total=total)
174 174 else:
175 175 patchname = cmdutil.makefilename(repo, '%b.patch', binnode)
176 176 disposition = 'inline'
177 177 if opts.get('attach'):
178 178 disposition = 'attachment'
179 179 p['Content-Disposition'] = disposition + '; filename=' + patchname
180 180 msg.attach(p)
181 181 else:
182 182 msg = mail.mimetextpatch(body, display=opts.get('test'))
183 183
184 184 flag = ' '.join(opts.get('flag'))
185 185 if flag:
186 186 flag = ' ' + flag
187 187
188 188 subj = desc[0].strip().rstrip('. ')
189 189 if not numbered:
190 190 subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
191 191 else:
192 192 tlen = len(str(total))
193 193 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
194 194 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
195 195 msg['X-Mercurial-Node'] = node
196 196 msg['X-Mercurial-Series-Index'] = '%i' % idx
197 197 msg['X-Mercurial-Series-Total'] = '%i' % total
198 198 return msg, subj, ds
199 199
200 200 def _getpatches(repo, revs, **opts):
201 201 """return a list of patches for a list of revisions
202 202
203 203 Each patch in the list is itself a list of lines.
204 204 """
205 205 ui = repo.ui
206 206 prev = repo['.'].rev()
207 207 for r in revs:
208 208 if r == prev and (repo[None].files() or repo[None].deleted()):
209 209 ui.warn(_('warning: working directory has '
210 210 'uncommitted changes\n'))
211 211 output = cStringIO.StringIO()
212 212 cmdutil.export(repo, [r], fp=output,
213 213 opts=patch.difffeatureopts(ui, opts, git=True))
214 214 yield output.getvalue().split('\n')
215 215 def _getbundle(repo, dest, **opts):
216 216 """return a bundle containing changesets missing in "dest"
217 217
218 218 The `opts` keyword-arguments are the same as the one accepted by the
219 219 `bundle` command.
220 220
221 221 The bundle is a returned as a single in-memory binary blob.
222 222 """
223 223 ui = repo.ui
224 224 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
225 225 tmpfn = os.path.join(tmpdir, 'bundle')
226 226 btype = ui.config('patchbomb', 'bundletype')
227 227 if btype:
228 228 opts['type'] = btype
229 229 try:
230 230 commands.bundle(ui, repo, tmpfn, dest, **opts)
231 231 fp = open(tmpfn, 'rb')
232 232 data = fp.read()
233 233 fp.close()
234 234 return data
235 235 finally:
236 236 try:
237 237 os.unlink(tmpfn)
238 238 except OSError:
239 239 pass
240 240 os.rmdir(tmpdir)
241 241
242 242 def _getdescription(repo, defaultbody, sender, **opts):
243 243 """obtain the body of the introduction message and return it
244 244
245 245 This is also used for the body of email with an attached bundle.
246 246
247 247 The body can be obtained either from the command line option or entered by
248 248 the user through the editor.
249 249 """
250 250 ui = repo.ui
251 251 if opts.get('desc'):
252 252 body = open(opts.get('desc')).read()
253 253 else:
254 254 ui.write(_('\nWrite the introductory message for the '
255 255 'patch series.\n\n'))
256 256 body = ui.edit(defaultbody, sender)
257 257 # Save series description in case sendmail fails
258 258 msgfile = repo.vfs('last-email.txt', 'wb')
259 259 msgfile.write(body)
260 260 msgfile.close()
261 261 return body
262 262
263 263 def _getbundlemsgs(repo, sender, bundle, **opts):
264 264 """Get the full email for sending a given bundle
265 265
266 266 This function returns a list of "email" tuples (subject, content, None).
267 267 The list is always one message long in that case.
268 268 """
269 269 ui = repo.ui
270 270 _charsets = mail._charsets(ui)
271 271 subj = (opts.get('subject')
272 272 or prompt(ui, 'Subject:', 'A bundle for your repository'))
273 273
274 274 body = _getdescription(repo, '', sender, **opts)
275 msg = email.MIMEMultipart.MIMEMultipart()
275 msg = emailmod.MIMEMultipart.MIMEMultipart()
276 276 if body:
277 277 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
278 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
278 datapart = emailmod.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
279 279 datapart.set_payload(bundle)
280 280 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
281 281 datapart.add_header('Content-Disposition', 'attachment',
282 282 filename=bundlename)
283 email.Encoders.encode_base64(datapart)
283 emailmod.Encoders.encode_base64(datapart)
284 284 msg.attach(datapart)
285 285 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
286 286 return [(msg, subj, None)]
287 287
288 288 def _makeintro(repo, sender, patches, **opts):
289 289 """make an introduction email, asking the user for content if needed
290 290
291 291 email is returned as (subject, body, cumulative-diffstat)"""
292 292 ui = repo.ui
293 293 _charsets = mail._charsets(ui)
294 294 tlen = len(str(len(patches)))
295 295
296 296 flag = opts.get('flag') or ''
297 297 if flag:
298 298 flag = ' ' + ' '.join(flag)
299 299 prefix = '[PATCH %0*d of %d%s]' % (tlen, 0, len(patches), flag)
300 300
301 301 subj = (opts.get('subject') or
302 302 prompt(ui, '(optional) Subject: ', rest=prefix, default=''))
303 303 if not subj:
304 304 return None # skip intro if the user doesn't bother
305 305
306 306 subj = prefix + ' ' + subj
307 307
308 308 body = ''
309 309 if opts.get('diffstat'):
310 310 # generate a cumulative diffstat of the whole patch series
311 311 diffstat = patch.diffstat(sum(patches, []))
312 312 body = '\n' + diffstat
313 313 else:
314 314 diffstat = None
315 315
316 316 body = _getdescription(repo, body, sender, **opts)
317 317 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
318 318 msg['Subject'] = mail.headencode(ui, subj, _charsets,
319 319 opts.get('test'))
320 320 return (msg, subj, diffstat)
321 321
322 322 def _getpatchmsgs(repo, sender, patches, patchnames=None, **opts):
323 323 """return a list of emails from a list of patches
324 324
325 325 This involves introduction message creation if necessary.
326 326
327 327 This function returns a list of "email" tuples (subject, content, None).
328 328 """
329 329 ui = repo.ui
330 330 _charsets = mail._charsets(ui)
331 331 msgs = []
332 332
333 333 ui.write(_('this patch series consists of %d patches.\n\n')
334 334 % len(patches))
335 335
336 336 # build the intro message, or skip it if the user declines
337 337 if introwanted(ui, opts, len(patches)):
338 338 msg = _makeintro(repo, sender, patches, **opts)
339 339 if msg:
340 340 msgs.append(msg)
341 341
342 342 # are we going to send more than one message?
343 343 numbered = len(msgs) + len(patches) > 1
344 344
345 345 # now generate the actual patch messages
346 346 name = None
347 347 for i, p in enumerate(patches):
348 348 if patchnames:
349 349 name = patchnames[i]
350 350 msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
351 351 len(patches), numbered, name)
352 352 msgs.append(msg)
353 353
354 354 return msgs
355 355
356 356 def _getoutgoing(repo, dest, revs):
357 357 '''Return the revisions present locally but not in dest'''
358 358 ui = repo.ui
359 359 url = ui.expandpath(dest or 'default-push', dest or 'default')
360 360 url = hg.parseurl(url)[0]
361 361 ui.status(_('comparing with %s\n') % util.hidepassword(url))
362 362
363 363 revs = [r for r in revs if r >= 0]
364 364 if not revs:
365 365 revs = [len(repo) - 1]
366 366 revs = repo.revs('outgoing(%s) and ::%ld', dest or '', revs)
367 367 if not revs:
368 368 ui.status(_("no changes found\n"))
369 369 return revs
370 370
371 371 emailopts = [
372 372 ('', 'body', None, _('send patches as inline message text (default)')),
373 373 ('a', 'attach', None, _('send patches as attachments')),
374 374 ('i', 'inline', None, _('send patches as inline attachments')),
375 375 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
376 376 ('c', 'cc', [], _('email addresses of copy recipients')),
377 377 ('', 'confirm', None, _('ask for confirmation before sending')),
378 378 ('d', 'diffstat', None, _('add diffstat output to messages')),
379 379 ('', 'date', '', _('use the given date as the sending date')),
380 380 ('', 'desc', '', _('use the given file as the series description')),
381 381 ('f', 'from', '', _('email address of sender')),
382 382 ('n', 'test', None, _('print messages that would be sent')),
383 383 ('m', 'mbox', '', _('write messages to mbox file instead of sending them')),
384 384 ('', 'reply-to', [], _('email addresses replies should be sent to')),
385 385 ('s', 'subject', '', _('subject of first message (intro or single patch)')),
386 386 ('', 'in-reply-to', '', _('message identifier to reply to')),
387 387 ('', 'flag', [], _('flags to add in subject prefixes')),
388 388 ('t', 'to', [], _('email addresses of recipients'))]
389 389
390 390 @command('email',
391 391 [('g', 'git', None, _('use git extended diff format')),
392 392 ('', 'plain', None, _('omit hg patch header')),
393 393 ('o', 'outgoing', None,
394 394 _('send changes not found in the target repository')),
395 395 ('b', 'bundle', None, _('send changes not in target as a binary bundle')),
396 396 ('', 'bundlename', 'bundle',
397 397 _('name of the bundle attachment file'), _('NAME')),
398 398 ('r', 'rev', [], _('a revision to send'), _('REV')),
399 399 ('', 'force', None, _('run even when remote repository is unrelated '
400 400 '(with -b/--bundle)')),
401 401 ('', 'base', [], _('a base changeset to specify instead of a destination '
402 402 '(with -b/--bundle)'), _('REV')),
403 403 ('', 'intro', None, _('send an introduction email for a single patch')),
404 404 ] + emailopts + commands.remoteopts,
405 405 _('hg email [OPTION]... [DEST]...'))
406 def patchbomb(ui, repo, *revs, **opts):
406 def email(ui, repo, *revs, **opts):
407 407 '''send changesets by email
408 408
409 409 By default, diffs are sent in the format generated by
410 410 :hg:`export`, one per message. The series starts with a "[PATCH 0
411 411 of N]" introduction, which describes the series as a whole.
412 412
413 413 Each patch email has a Subject line of "[PATCH M of N] ...", using
414 414 the first line of the changeset description as the subject text.
415 415 The message contains two or three parts. First, the changeset
416 416 description.
417 417
418 418 With the -d/--diffstat option, if the diffstat program is
419 419 installed, the result of running diffstat on the patch is inserted.
420 420
421 421 Finally, the patch itself, as generated by :hg:`export`.
422 422
423 423 With the -d/--diffstat or --confirm options, you will be presented
424 424 with a final summary of all messages and asked for confirmation before
425 425 the messages are sent.
426 426
427 427 By default the patch is included as text in the email body for
428 428 easy reviewing. Using the -a/--attach option will instead create
429 429 an attachment for the patch. With -i/--inline an inline attachment
430 430 will be created. You can include a patch both as text in the email
431 431 body and as a regular or an inline attachment by combining the
432 432 -a/--attach or -i/--inline with the --body option.
433 433
434 434 With -o/--outgoing, emails will be generated for patches not found
435 435 in the destination repository (or only those which are ancestors
436 436 of the specified revisions if any are provided)
437 437
438 438 With -b/--bundle, changesets are selected as for --outgoing, but a
439 439 single email containing a binary Mercurial bundle as an attachment
440 440 will be sent. Use the ``patchbomb.bundletype`` config option to
441 441 control the bundle type as with :hg:`bundle --type`.
442 442
443 443 With -m/--mbox, instead of previewing each patchbomb message in a
444 444 pager or sending the messages directly, it will create a UNIX
445 445 mailbox file with the patch emails. This mailbox file can be
446 446 previewed with any mail user agent which supports UNIX mbox
447 447 files.
448 448
449 449 With -n/--test, all steps will run, but mail will not be sent.
450 450 You will be prompted for an email recipient address, a subject and
451 451 an introductory message describing the patches of your patchbomb.
452 452 Then when all is done, patchbomb messages are displayed. If the
453 453 PAGER environment variable is set, your pager will be fired up once
454 454 for each patchbomb message, so you can verify everything is alright.
455 455
456 456 In case email sending fails, you will find a backup of your series
457 457 introductory message in ``.hg/last-email.txt``.
458 458
459 459 The default behavior of this command can be customized through
460 460 configuration. (See :hg:`help patchbomb` for details)
461 461
462 462 Examples::
463 463
464 464 hg email -r 3000 # send patch 3000 only
465 465 hg email -r 3000 -r 3001 # send patches 3000 and 3001
466 466 hg email -r 3000:3005 # send patches 3000 through 3005
467 467 hg email 3000 # send patch 3000 (deprecated)
468 468
469 469 hg email -o # send all patches not in default
470 470 hg email -o DEST # send all patches not in DEST
471 471 hg email -o -r 3000 # send all ancestors of 3000 not in default
472 472 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
473 473
474 474 hg email -b # send bundle of all patches not in default
475 475 hg email -b DEST # send bundle of all patches not in DEST
476 476 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
477 477 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
478 478
479 479 hg email -o -m mbox && # generate an mbox file...
480 480 mutt -R -f mbox # ... and view it with mutt
481 481 hg email -o -m mbox && # generate an mbox file ...
482 482 formail -s sendmail \\ # ... and use formail to send from the mbox
483 483 -bm -t < mbox # ... using sendmail
484 484
485 485 Before using this command, you will need to enable email in your
486 486 hgrc. See the [email] section in hgrc(5) for details.
487 487 '''
488 488
489 489 _charsets = mail._charsets(ui)
490 490
491 491 bundle = opts.get('bundle')
492 492 date = opts.get('date')
493 493 mbox = opts.get('mbox')
494 494 outgoing = opts.get('outgoing')
495 495 rev = opts.get('rev')
496 496 # internal option used by pbranches
497 497 patches = opts.get('patches')
498 498
499 499 if not (opts.get('test') or mbox):
500 500 # really sending
501 501 mail.validateconfig(ui)
502 502
503 503 if not (revs or rev or outgoing or bundle or patches):
504 504 raise error.Abort(_('specify at least one changeset with -r or -o'))
505 505
506 506 if outgoing and bundle:
507 507 raise error.Abort(_("--outgoing mode always on with --bundle;"
508 508 " do not re-specify --outgoing"))
509 509
510 510 if outgoing or bundle:
511 511 if len(revs) > 1:
512 512 raise error.Abort(_("too many destinations"))
513 513 if revs:
514 514 dest = revs[0]
515 515 else:
516 516 dest = None
517 517 revs = []
518 518
519 519 if rev:
520 520 if revs:
521 521 raise error.Abort(_('use only one form to specify the revision'))
522 522 revs = rev
523 523
524 524 revs = scmutil.revrange(repo, revs)
525 525 if outgoing:
526 526 revs = _getoutgoing(repo, dest, revs)
527 527 if bundle:
528 528 opts['revs'] = [str(r) for r in revs]
529 529
530 530 # check if revision exist on the public destination
531 531 publicurl = repo.ui.config('patchbomb', 'publicurl')
532 532 if publicurl is not None:
533 533 repo.ui.debug('checking that revision exist in the public repo')
534 534 try:
535 535 publicpeer = hg.peer(repo, {}, publicurl)
536 536 except error.RepoError:
537 537 repo.ui.write_err(_('unable to access public repo: %s\n')
538 538 % publicurl)
539 539 raise
540 540 if not publicpeer.capable('known'):
541 541 repo.ui.debug('skipping existence checks: public repo too old')
542 542 else:
543 543 out = [repo[r] for r in revs]
544 544 known = publicpeer.known(h.node() for h in out)
545 545 missing = []
546 546 for idx, h in enumerate(out):
547 547 if not known[idx]:
548 548 missing.append(h)
549 549 if missing:
550 550 if 1 < len(missing):
551 551 msg = _('public "%s" is missing %s and %i others')
552 552 msg %= (publicurl, missing[0], len(missing) - 1)
553 553 else:
554 554 msg = _('public url %s is missing %s')
555 555 msg %= (publicurl, missing[0])
556 556 revhint = ''.join('-r %s' % h
557 557 for h in repo.set('heads(%ld)', missing))
558 558 hint = _('use "hg push %s %s"') % (publicurl, revhint)
559 559 raise error.Abort(msg, hint=hint)
560 560
561 561 # start
562 562 if date:
563 563 start_time = util.parsedate(date)
564 564 else:
565 565 start_time = util.makedate()
566 566
567 567 def genmsgid(id):
568 568 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
569 569
570 570 # deprecated config: patchbomb.from
571 571 sender = (opts.get('from') or ui.config('email', 'from') or
572 572 ui.config('patchbomb', 'from') or
573 573 prompt(ui, 'From', ui.username()))
574 574
575 575 if patches:
576 576 msgs = _getpatchmsgs(repo, sender, patches, opts.get('patchnames'),
577 577 **opts)
578 578 elif bundle:
579 579 bundledata = _getbundle(repo, dest, **opts)
580 580 bundleopts = opts.copy()
581 581 bundleopts.pop('bundle', None) # already processed
582 582 msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
583 583 else:
584 584 _patches = list(_getpatches(repo, revs, **opts))
585 585 msgs = _getpatchmsgs(repo, sender, _patches, **opts)
586 586
587 587 showaddrs = []
588 588
589 589 def getaddrs(header, ask=False, default=None):
590 590 configkey = header.lower()
591 591 opt = header.replace('-', '_').lower()
592 592 addrs = opts.get(opt)
593 593 if addrs:
594 594 showaddrs.append('%s: %s' % (header, ', '.join(addrs)))
595 595 return mail.addrlistencode(ui, addrs, _charsets, opts.get('test'))
596 596
597 597 # not on the command line: fallback to config and then maybe ask
598 598 addr = (ui.config('email', configkey) or
599 599 ui.config('patchbomb', configkey) or
600 600 '')
601 601 if not addr and ask:
602 602 addr = prompt(ui, header, default=default)
603 603 if addr:
604 604 showaddrs.append('%s: %s' % (header, addr))
605 605 return mail.addrlistencode(ui, [addr], _charsets, opts.get('test'))
606 606 else:
607 607 return default
608 608
609 609 to = getaddrs('To', ask=True)
610 610 if not to:
611 611 # we can get here in non-interactive mode
612 612 raise error.Abort(_('no recipient addresses provided'))
613 613 cc = getaddrs('Cc', ask=True, default='') or []
614 614 bcc = getaddrs('Bcc') or []
615 615 replyto = getaddrs('Reply-To')
616 616
617 617 confirm = ui.configbool('patchbomb', 'confirm')
618 618 confirm |= bool(opts.get('diffstat') or opts.get('confirm'))
619 619
620 620 if confirm:
621 621 ui.write(_('\nFinal summary:\n\n'), label='patchbomb.finalsummary')
622 622 ui.write(('From: %s\n' % sender), label='patchbomb.from')
623 623 for addr in showaddrs:
624 624 ui.write('%s\n' % addr, label='patchbomb.to')
625 625 for m, subj, ds in msgs:
626 626 ui.write(('Subject: %s\n' % subj), label='patchbomb.subject')
627 627 if ds:
628 628 ui.write(ds, label='patchbomb.diffstats')
629 629 ui.write('\n')
630 630 if ui.promptchoice(_('are you sure you want to send (yn)?'
631 631 '$$ &Yes $$ &No')):
632 632 raise error.Abort(_('patchbomb canceled'))
633 633
634 634 ui.write('\n')
635 635
636 636 parent = opts.get('in_reply_to') or None
637 637 # angle brackets may be omitted, they're not semantically part of the msg-id
638 638 if parent is not None:
639 639 if not parent.startswith('<'):
640 640 parent = '<' + parent
641 641 if not parent.endswith('>'):
642 642 parent += '>'
643 643
644 sender_addr = email.Utils.parseaddr(sender)[1]
644 sender_addr = emailmod.Utils.parseaddr(sender)[1]
645 645 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
646 646 sendmail = None
647 647 firstpatch = None
648 648 for i, (m, subj, ds) in enumerate(msgs):
649 649 try:
650 650 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
651 651 if not firstpatch:
652 652 firstpatch = m['Message-Id']
653 653 m['X-Mercurial-Series-Id'] = firstpatch
654 654 except TypeError:
655 655 m['Message-Id'] = genmsgid('patchbomb')
656 656 if parent:
657 657 m['In-Reply-To'] = parent
658 658 m['References'] = parent
659 659 if not parent or 'X-Mercurial-Node' not in m:
660 660 parent = m['Message-Id']
661 661
662 662 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
663 m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)
663 m['Date'] = emailmod.Utils.formatdate(start_time[0], localtime=True)
664 664
665 665 start_time = (start_time[0] + 1, start_time[1])
666 666 m['From'] = sender
667 667 m['To'] = ', '.join(to)
668 668 if cc:
669 669 m['Cc'] = ', '.join(cc)
670 670 if bcc:
671 671 m['Bcc'] = ', '.join(bcc)
672 672 if replyto:
673 673 m['Reply-To'] = ', '.join(replyto)
674 674 if opts.get('test'):
675 675 ui.status(_('displaying '), subj, ' ...\n')
676 676 ui.flush()
677 677 if 'PAGER' in os.environ and not ui.plain():
678 678 fp = util.popen(os.environ['PAGER'], 'w')
679 679 else:
680 680 fp = ui
681 generator = email.Generator.Generator(fp, mangle_from_=False)
681 generator = emailmod.Generator.Generator(fp, mangle_from_=False)
682 682 try:
683 683 generator.flatten(m, 0)
684 684 fp.write('\n')
685 685 except IOError as inst:
686 686 if inst.errno != errno.EPIPE:
687 687 raise
688 688 if fp is not ui:
689 689 fp.close()
690 690 else:
691 691 if not sendmail:
692 692 verifycert = ui.config('smtp', 'verifycert', 'strict')
693 693 if opts.get('insecure'):
694 694 ui.setconfig('smtp', 'verifycert', 'loose', 'patchbomb')
695 695 try:
696 696 sendmail = mail.connect(ui, mbox=mbox)
697 697 finally:
698 698 ui.setconfig('smtp', 'verifycert', verifycert, 'patchbomb')
699 699 ui.status(_('sending '), subj, ' ...\n')
700 700 ui.progress(_('sending'), i, item=subj, total=len(msgs))
701 701 if not mbox:
702 702 # Exim does not remove the Bcc field
703 703 del m['Bcc']
704 704 fp = cStringIO.StringIO()
705 generator = email.Generator.Generator(fp, mangle_from_=False)
705 generator = emailmod.Generator.Generator(fp, mangle_from_=False)
706 706 generator.flatten(m, 0)
707 707 sendmail(sender_addr, to + bcc + cc, fp.getvalue())
708 708
709 709 ui.progress(_('writing'), None)
710 710 ui.progress(_('sending'), None)
General Comments 0
You need to be logged in to leave comments. Login now