56 You can set patchbomb to always ask for confirmation by setting |
56 You can set patchbomb to always ask for confirmation by setting |
57 ``patchbomb.confirm`` to true. |
57 ``patchbomb.confirm`` to true. |
58 ''' |
58 ''' |
59 |
59 |
60 import os, errno, socket, tempfile, cStringIO |
60 import os, errno, socket, tempfile, cStringIO |
61 import email |
61 import email as emailmod |
62 |
62 |
63 from mercurial import cmdutil, commands, hg, mail, patch, util, error |
63 from mercurial import cmdutil, commands, hg, mail, patch, util, error |
64 from mercurial import scmutil |
64 from mercurial import scmutil |
65 from mercurial.i18n import _ |
65 from mercurial.i18n import _ |
66 from mercurial.node import bin |
66 from mercurial.node import bin |
153 addattachment = opts.get('attach') or opts.get('inline') |
153 addattachment = opts.get('attach') or opts.get('inline') |
154 if not addattachment or opts.get('body'): |
154 if not addattachment or opts.get('body'): |
155 body += '\n'.join(patchlines) |
155 body += '\n'.join(patchlines) |
156 |
156 |
157 if addattachment: |
157 if addattachment: |
158 msg = email.MIMEMultipart.MIMEMultipart() |
158 msg = emailmod.MIMEMultipart.MIMEMultipart() |
159 if body: |
159 if body: |
160 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) |
160 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) |
161 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch', |
161 p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch', |
162 opts.get('test')) |
162 opts.get('test')) |
163 binnode = bin(node) |
163 binnode = bin(node) |
270 _charsets = mail._charsets(ui) |
270 _charsets = mail._charsets(ui) |
271 subj = (opts.get('subject') |
271 subj = (opts.get('subject') |
272 or prompt(ui, 'Subject:', 'A bundle for your repository')) |
272 or prompt(ui, 'Subject:', 'A bundle for your repository')) |
273 |
273 |
274 body = _getdescription(repo, '', sender, **opts) |
274 body = _getdescription(repo, '', sender, **opts) |
275 msg = email.MIMEMultipart.MIMEMultipart() |
275 msg = emailmod.MIMEMultipart.MIMEMultipart() |
276 if body: |
276 if body: |
277 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) |
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 datapart.set_payload(bundle) |
279 datapart.set_payload(bundle) |
280 bundlename = '%s.hg' % opts.get('bundlename', 'bundle') |
280 bundlename = '%s.hg' % opts.get('bundlename', 'bundle') |
281 datapart.add_header('Content-Disposition', 'attachment', |
281 datapart.add_header('Content-Disposition', 'attachment', |
282 filename=bundlename) |
282 filename=bundlename) |
283 email.Encoders.encode_base64(datapart) |
283 emailmod.Encoders.encode_base64(datapart) |
284 msg.attach(datapart) |
284 msg.attach(datapart) |
285 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
285 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
286 return [(msg, subj, None)] |
286 return [(msg, subj, None)] |
287 |
287 |
288 def _makeintro(repo, sender, patches, **opts): |
288 def _makeintro(repo, sender, patches, **opts): |
401 ('', 'base', [], _('a base changeset to specify instead of a destination ' |
401 ('', 'base', [], _('a base changeset to specify instead of a destination ' |
402 '(with -b/--bundle)'), _('REV')), |
402 '(with -b/--bundle)'), _('REV')), |
403 ('', 'intro', None, _('send an introduction email for a single patch')), |
403 ('', 'intro', None, _('send an introduction email for a single patch')), |
404 ] + emailopts + commands.remoteopts, |
404 ] + emailopts + commands.remoteopts, |
405 _('hg email [OPTION]... [DEST]...')) |
405 _('hg email [OPTION]... [DEST]...')) |
406 def patchbomb(ui, repo, *revs, **opts): |
406 def email(ui, repo, *revs, **opts): |
407 '''send changesets by email |
407 '''send changesets by email |
408 |
408 |
409 By default, diffs are sent in the format generated by |
409 By default, diffs are sent in the format generated by |
410 :hg:`export`, one per message. The series starts with a "[PATCH 0 |
410 :hg:`export`, one per message. The series starts with a "[PATCH 0 |
411 of N]" introduction, which describes the series as a whole. |
411 of N]" introduction, which describes the series as a whole. |
639 if not parent.startswith('<'): |
639 if not parent.startswith('<'): |
640 parent = '<' + parent |
640 parent = '<' + parent |
641 if not parent.endswith('>'): |
641 if not parent.endswith('>'): |
642 parent += '>' |
642 parent += '>' |
643 |
643 |
644 sender_addr = email.Utils.parseaddr(sender)[1] |
644 sender_addr = emailmod.Utils.parseaddr(sender)[1] |
645 sender = mail.addressencode(ui, sender, _charsets, opts.get('test')) |
645 sender = mail.addressencode(ui, sender, _charsets, opts.get('test')) |
646 sendmail = None |
646 sendmail = None |
647 firstpatch = None |
647 firstpatch = None |
648 for i, (m, subj, ds) in enumerate(msgs): |
648 for i, (m, subj, ds) in enumerate(msgs): |
649 try: |
649 try: |
658 m['References'] = parent |
658 m['References'] = parent |
659 if not parent or 'X-Mercurial-Node' not in m: |
659 if not parent or 'X-Mercurial-Node' not in m: |
660 parent = m['Message-Id'] |
660 parent = m['Message-Id'] |
661 |
661 |
662 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version() |
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 start_time = (start_time[0] + 1, start_time[1]) |
665 start_time = (start_time[0] + 1, start_time[1]) |
666 m['From'] = sender |
666 m['From'] = sender |
667 m['To'] = ', '.join(to) |
667 m['To'] = ', '.join(to) |
668 if cc: |
668 if cc: |
676 ui.flush() |
676 ui.flush() |
677 if 'PAGER' in os.environ and not ui.plain(): |
677 if 'PAGER' in os.environ and not ui.plain(): |
678 fp = util.popen(os.environ['PAGER'], 'w') |
678 fp = util.popen(os.environ['PAGER'], 'w') |
679 else: |
679 else: |
680 fp = ui |
680 fp = ui |
681 generator = email.Generator.Generator(fp, mangle_from_=False) |
681 generator = emailmod.Generator.Generator(fp, mangle_from_=False) |
682 try: |
682 try: |
683 generator.flatten(m, 0) |
683 generator.flatten(m, 0) |
684 fp.write('\n') |
684 fp.write('\n') |
685 except IOError as inst: |
685 except IOError as inst: |
686 if inst.errno != errno.EPIPE: |
686 if inst.errno != errno.EPIPE: |
700 ui.progress(_('sending'), i, item=subj, total=len(msgs)) |
700 ui.progress(_('sending'), i, item=subj, total=len(msgs)) |
701 if not mbox: |
701 if not mbox: |
702 # Exim does not remove the Bcc field |
702 # Exim does not remove the Bcc field |
703 del m['Bcc'] |
703 del m['Bcc'] |
704 fp = cStringIO.StringIO() |
704 fp = cStringIO.StringIO() |
705 generator = email.Generator.Generator(fp, mangle_from_=False) |
705 generator = emailmod.Generator.Generator(fp, mangle_from_=False) |
706 generator.flatten(m, 0) |
706 generator.flatten(m, 0) |
707 sendmail(sender_addr, to + bcc + cc, fp.getvalue()) |
707 sendmail(sender_addr, to + bcc + cc, fp.getvalue()) |
708 |
708 |
709 ui.progress(_('writing'), None) |
709 ui.progress(_('writing'), None) |
710 ui.progress(_('sending'), None) |
710 ui.progress(_('sending'), None) |