20 |
20 |
21 Each message refers to the first in the series using the In-Reply-To |
21 Each message refers to the first in the series using the In-Reply-To |
22 and References headers, so they will show up as a sequence in threaded |
22 and References headers, so they will show up as a sequence in threaded |
23 mail and news readers, and in mail archives. |
23 mail and news readers, and in mail archives. |
24 |
24 |
25 With the -d/--diffstat option, you will be prompted for each changeset |
25 With the -d/--diffstat option, you will be presented with a final |
26 with a diffstat summary and the changeset summary, so you can be sure |
26 summary of all messages and asked for confirmation before the messages |
27 you are sending the right changes. |
27 are sent. |
28 |
28 |
29 To configure other defaults, add a section like this to your hgrc |
29 To configure other defaults, add a section like this to your hgrc |
30 file:: |
30 file:: |
31 |
31 |
32 [email] |
32 [email] |
92 return r |
92 return r |
93 if default is not None: |
93 if default is not None: |
94 return default |
94 return default |
95 ui.warn(_('Please enter a valid value.\n')) |
95 ui.warn(_('Please enter a valid value.\n')) |
96 |
96 |
97 def cdiffstat(ui, summary, patchlines): |
|
98 s = patch.diffstat(patchlines) |
|
99 if summary: |
|
100 ui.write(summary, '\n') |
|
101 ui.write(s, '\n') |
|
102 if ui.promptchoice(_('does the diffstat above look okay (yn)?'), |
|
103 (_('&Yes'), _('&No'))): |
|
104 raise util.Abort(_('diffstat rejected')) |
|
105 return s |
|
106 |
|
107 def introneeded(opts, number): |
97 def introneeded(opts, number): |
108 '''is an introductory message required?''' |
98 '''is an introductory message required?''' |
109 return number > 1 or opts.get('intro') or opts.get('desc') |
99 return number > 1 or opts.get('intro') or opts.get('desc') |
110 |
100 |
111 def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, |
101 def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, |
138 if patchlines: |
128 if patchlines: |
139 patchlines.pop(0) |
129 patchlines.pop(0) |
140 while patchlines and not patchlines[0].strip(): |
130 while patchlines and not patchlines[0].strip(): |
141 patchlines.pop(0) |
131 patchlines.pop(0) |
142 |
132 |
|
133 ds = patch.diffstat(patchlines) |
143 if opts.get('diffstat'): |
134 if opts.get('diffstat'): |
144 body += cdiffstat(ui, '\n'.join(desc), patchlines) + '\n\n' |
135 body += ds + '\n\n' |
145 |
136 |
146 if opts.get('attach') or opts.get('inline'): |
137 if opts.get('attach') or opts.get('inline'): |
147 msg = email.MIMEMultipart.MIMEMultipart() |
138 msg = email.MIMEMultipart.MIMEMultipart() |
148 if body: |
139 if body: |
149 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) |
140 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) |
179 else: |
170 else: |
180 tlen = len(str(total)) |
171 tlen = len(str(total)) |
181 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj) |
172 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj) |
182 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
173 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
183 msg['X-Mercurial-Node'] = node |
174 msg['X-Mercurial-Node'] = node |
184 return msg, subj |
175 return msg, subj, ds |
185 |
176 |
186 def patchbomb(ui, repo, *revs, **opts): |
177 def patchbomb(ui, repo, *revs, **opts): |
187 '''send changesets by email |
178 '''send changesets by email |
188 |
179 |
189 By default, diffs are sent in the format generated by |
180 By default, diffs are sent in the format generated by |
349 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches)) |
340 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches)) |
350 subj += ' ' + (opts.get('subject') or |
341 subj += ' ' + (opts.get('subject') or |
351 prompt(ui, 'Subject: ', rest=subj)) |
342 prompt(ui, 'Subject: ', rest=subj)) |
352 |
343 |
353 body = '' |
344 body = '' |
354 if opts.get('diffstat'): |
345 ds = patch.diffstat(jumbo) |
355 d = cdiffstat(ui, _('Final summary:\n'), jumbo) |
346 if ds and opts.get('diffstat'): |
356 if d: |
347 body = '\n' + ds |
357 body = '\n' + d |
|
358 |
348 |
359 body = getdescription(body, sender) |
349 body = getdescription(body, sender) |
360 msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) |
350 msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) |
361 msg['Subject'] = mail.headencode(ui, subj, _charsets, |
351 msg['Subject'] = mail.headencode(ui, subj, _charsets, |
362 opts.get('test')) |
352 opts.get('test')) |
363 |
353 |
364 msgs.insert(0, (msg, subj)) |
354 msgs.insert(0, (msg, subj, ds)) |
365 return msgs |
355 return msgs |
366 |
356 |
367 def getbundlemsgs(bundle): |
357 def getbundlemsgs(bundle): |
368 subj = (opts.get('subject') |
358 subj = (opts.get('subject') |
369 or prompt(ui, 'Subject:', 'A bundle for your repository')) |
359 or prompt(ui, 'Subject:', 'A bundle for your repository')) |
378 datapart.add_header('Content-Disposition', 'attachment', |
368 datapart.add_header('Content-Disposition', 'attachment', |
379 filename=bundlename) |
369 filename=bundlename) |
380 email.Encoders.encode_base64(datapart) |
370 email.Encoders.encode_base64(datapart) |
381 msg.attach(datapart) |
371 msg.attach(datapart) |
382 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
372 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
383 return [(msg, subj)] |
373 return [(msg, subj, None)] |
384 |
374 |
385 sender = (opts.get('from') or ui.config('email', 'from') or |
375 sender = (opts.get('from') or ui.config('email', 'from') or |
386 ui.config('patchbomb', 'from') or |
376 ui.config('patchbomb', 'from') or |
387 prompt(ui, 'From', ui.username())) |
377 prompt(ui, 'From', ui.username())) |
388 |
378 |
391 elif bundle: |
381 elif bundle: |
392 msgs = getbundlemsgs(getbundle(dest)) |
382 msgs = getbundlemsgs(getbundle(dest)) |
393 else: |
383 else: |
394 msgs = getpatchmsgs(list(getpatches(revs))) |
384 msgs = getpatchmsgs(list(getpatches(revs))) |
395 |
385 |
|
386 showaddrs = [] |
|
387 |
396 def getaddrs(opt, prpt=None, default=None): |
388 def getaddrs(opt, prpt=None, default=None): |
397 addrs = opts.get(opt.replace('-', '_')) |
389 addrs = opts.get(opt.replace('-', '_')) |
|
390 if opt != 'reply-to': |
|
391 showaddr = '%s:' % opt.capitalize() |
|
392 else: |
|
393 showaddr = 'Reply-To:' |
|
394 |
398 if addrs: |
395 if addrs: |
|
396 showaddrs.append('%s %s' % (showaddr, ', '.join(addrs))) |
399 return mail.addrlistencode(ui, addrs, _charsets, |
397 return mail.addrlistencode(ui, addrs, _charsets, |
400 opts.get('test')) |
398 opts.get('test')) |
401 |
399 |
402 addrs = ui.config('email', opt) or ui.config('patchbomb', opt) or '' |
400 addrs = ui.config('email', opt) or ui.config('patchbomb', opt) or '' |
403 if not addrs and prpt: |
401 if not addrs and prpt: |
404 addrs = prompt(ui, prpt, default) |
402 addrs = prompt(ui, prpt, default) |
|
403 |
|
404 if addrs: |
|
405 showaddr = '%s %s' % (showaddr, addrs) |
|
406 showaddrs.append(showaddr) |
405 |
407 |
406 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test')) |
408 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test')) |
407 |
409 |
408 to = getaddrs('to', 'To') |
410 to = getaddrs('to', 'To') |
409 cc = getaddrs('cc', 'Cc', '') |
411 cc = getaddrs('cc', 'Cc', '') |
410 bcc = getaddrs('bcc') |
412 bcc = getaddrs('bcc') |
411 replyto = getaddrs('reply-to') |
413 replyto = getaddrs('reply-to') |
|
414 |
|
415 if opts.get('diffstat'): |
|
416 ui.write(_('\nFinal summary:\n\n')) |
|
417 ui.write('From: %s\n' % sender) |
|
418 for addr in showaddrs: |
|
419 ui.write('%s\n' % addr) |
|
420 for m, subj, ds in msgs: |
|
421 ui.write('Subject: %s\n' % subj) |
|
422 if ds: |
|
423 ui.write(ds) |
|
424 ui.write('\n') |
|
425 if ui.promptchoice(_('are you sure you want to send (yn)?'), |
|
426 (_('&Yes'), _('&No'))): |
|
427 raise util.Abort(_('patchbomb canceled')) |
412 |
428 |
413 ui.write('\n') |
429 ui.write('\n') |
414 |
430 |
415 parent = opts.get('in_reply_to') or None |
431 parent = opts.get('in_reply_to') or None |
416 # angle brackets may be omitted, they're not semantically part of the msg-id |
432 # angle brackets may be omitted, they're not semantically part of the msg-id |
423 first = True |
439 first = True |
424 |
440 |
425 sender_addr = email.Utils.parseaddr(sender)[1] |
441 sender_addr = email.Utils.parseaddr(sender)[1] |
426 sender = mail.addressencode(ui, sender, _charsets, opts.get('test')) |
442 sender = mail.addressencode(ui, sender, _charsets, opts.get('test')) |
427 sendmail = None |
443 sendmail = None |
428 for m, subj in msgs: |
444 for m, subj, ds in msgs: |
429 try: |
445 try: |
430 m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) |
446 m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) |
431 except TypeError: |
447 except TypeError: |
432 m['Message-Id'] = genmsgid('patchbomb') |
448 m['Message-Id'] = genmsgid('patchbomb') |
433 if parent: |
449 if parent: |