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