319 subj = desc[0].strip().rstrip(b'. ') |
319 subj = desc[0].strip().rstrip(b'. ') |
320 if not numbered: |
320 if not numbered: |
321 subj = b' '.join([prefix, opts.get(b'subject') or subj]) |
321 subj = b' '.join([prefix, opts.get(b'subject') or subj]) |
322 else: |
322 else: |
323 subj = b' '.join([prefix, subj]) |
323 subj = b' '.join([prefix, subj]) |
324 msg[b'Subject'] = mail.headencode(ui, subj, _charsets, opts.get(b'test')) |
324 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get(b'test')) |
325 msg[b'X-Mercurial-Node'] = node |
325 msg['X-Mercurial-Node'] = pycompat.sysstr(node) |
326 msg[b'X-Mercurial-Series-Index'] = b'%i' % idx |
326 msg['X-Mercurial-Series-Index'] = '%i' % idx |
327 msg[b'X-Mercurial-Series-Total'] = b'%i' % total |
327 msg['X-Mercurial-Series-Total'] = '%i' % total |
328 return msg, subj, ds |
328 return msg, subj, ds |
329 |
329 |
330 |
330 |
331 def _getpatches(repo, revs, **opts): |
331 def _getpatches(repo, revs, **opts): |
332 """return a list of patches for a list of revisions |
332 """return a list of patches for a list of revisions |
419 'attachment', |
419 'attachment', |
420 filename=encoding.strfromlocal(bundlename), |
420 filename=encoding.strfromlocal(bundlename), |
421 ) |
421 ) |
422 emailencoders.encode_base64(datapart) |
422 emailencoders.encode_base64(datapart) |
423 msg.attach(datapart) |
423 msg.attach(datapart) |
424 msg[b'Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
424 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
425 return [(msg, subj, None)] |
425 return [(msg, subj, None)] |
426 |
426 |
427 |
427 |
428 def _makeintro(repo, sender, revs, patches, **opts): |
428 def _makeintro(repo, sender, revs, patches, **opts): |
429 """make an introduction email, asking the user for content if needed |
429 """make an introduction email, asking the user for content if needed |
452 else: |
452 else: |
453 diffstat = None |
453 diffstat = None |
454 |
454 |
455 body = _getdescription(repo, body, sender, **opts) |
455 body = _getdescription(repo, body, sender, **opts) |
456 msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) |
456 msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) |
457 msg[b'Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
457 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) |
458 return (msg, subj, diffstat) |
458 return (msg, subj, diffstat) |
459 |
459 |
460 |
460 |
461 def _getpatchmsgs(repo, sender, revs, patchnames=None, **opts): |
461 def _getpatchmsgs(repo, sender, revs, patchnames=None, **opts): |
462 """return a list of emails from a list of patches |
462 """return a list of emails from a list of patches |
520 ui.status(_(b"no changes found\n")) |
520 ui.status(_(b"no changes found\n")) |
521 return revs |
521 return revs |
522 |
522 |
523 |
523 |
524 def _msgid(node, timestamp): |
524 def _msgid(node, timestamp): |
525 hostname = encoding.strtolocal(socket.getfqdn()) |
525 try: |
526 hostname = encoding.environ.get(b'HGHOSTNAME', hostname) |
526 hostname = encoding.strfromlocal(encoding.environ[b'HGHOSTNAME']) |
527 return b'<%s.%d@%s>' % (node, timestamp, hostname) |
527 except KeyError: |
|
528 hostname = socket.getfqdn() |
|
529 return '<%s.%d@%s>' % (node, timestamp, hostname) |
528 |
530 |
529 |
531 |
530 emailopts = [ |
532 emailopts = [ |
531 (b'', b'body', None, _(b'send patches as inline message text (default)')), |
533 (b'', b'body', None, _(b'send patches as inline message text (default)')), |
532 (b'a', b'attach', None, _(b'send patches as attachments')), |
534 (b'a', b'attach', None, _(b'send patches as attachments')), |
910 ui.write(b'\n') |
912 ui.write(b'\n') |
911 |
913 |
912 parent = opts.get(b'in_reply_to') or None |
914 parent = opts.get(b'in_reply_to') or None |
913 # angle brackets may be omitted, they're not semantically part of the msg-id |
915 # angle brackets may be omitted, they're not semantically part of the msg-id |
914 if parent is not None: |
916 if parent is not None: |
915 if not parent.startswith(b'<'): |
917 parent = encoding.strfromlocal(parent) |
916 parent = b'<' + parent |
918 if not parent.startswith('<'): |
917 if not parent.endswith(b'>'): |
919 parent = '<' + parent |
918 parent += b'>' |
920 if not parent.endswith('>'): |
|
921 parent += '>' |
919 |
922 |
920 sender_addr = eutil.parseaddr(encoding.strfromlocal(sender))[1] |
923 sender_addr = eutil.parseaddr(encoding.strfromlocal(sender))[1] |
921 sender = mail.addressencode(ui, sender, _charsets, opts.get(b'test')) |
924 sender = mail.addressencode(ui, sender, _charsets, opts.get(b'test')) |
922 sendmail = None |
925 sendmail = None |
923 firstpatch = None |
926 firstpatch = None |
924 progress = ui.makeprogress( |
927 progress = ui.makeprogress( |
925 _(b'sending'), unit=_(b'emails'), total=len(msgs) |
928 _(b'sending'), unit=_(b'emails'), total=len(msgs) |
926 ) |
929 ) |
927 for i, (m, subj, ds) in enumerate(msgs): |
930 for i, (m, subj, ds) in enumerate(msgs): |
928 try: |
931 try: |
929 m[b'Message-Id'] = genmsgid(m[b'X-Mercurial-Node']) |
932 m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) |
930 if not firstpatch: |
933 if not firstpatch: |
931 firstpatch = m[b'Message-Id'] |
934 firstpatch = m['Message-Id'] |
932 m[b'X-Mercurial-Series-Id'] = firstpatch |
935 m['X-Mercurial-Series-Id'] = firstpatch |
933 except TypeError: |
936 except TypeError: |
934 m[b'Message-Id'] = genmsgid(b'patchbomb') |
937 m['Message-Id'] = genmsgid('patchbomb') |
935 if parent: |
938 if parent: |
936 m[b'In-Reply-To'] = parent |
939 m['In-Reply-To'] = parent |
937 m[b'References'] = parent |
940 m['References'] = parent |
938 if not parent or b'X-Mercurial-Node' not in m: |
941 if not parent or 'X-Mercurial-Node' not in m: |
939 parent = m[b'Message-Id'] |
942 parent = m['Message-Id'] |
940 |
943 |
941 m[b'User-Agent'] = b'Mercurial-patchbomb/%s' % util.version() |
944 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version().decode() |
942 m[b'Date'] = eutil.formatdate(start_time[0], localtime=True) |
945 m['Date'] = eutil.formatdate(start_time[0], localtime=True) |
943 |
946 |
944 start_time = (start_time[0] + 1, start_time[1]) |
947 start_time = (start_time[0] + 1, start_time[1]) |
945 m[b'From'] = sender |
948 m['From'] = sender |
946 m[b'To'] = ', '.join(to) |
949 m['To'] = ', '.join(to) |
947 if cc: |
950 if cc: |
948 m[b'Cc'] = ', '.join(cc) |
951 m['Cc'] = ', '.join(cc) |
949 if bcc: |
952 if bcc: |
950 m[b'Bcc'] = ', '.join(bcc) |
953 m['Bcc'] = ', '.join(bcc) |
951 if replyto: |
954 if replyto: |
952 m[b'Reply-To'] = ', '.join(replyto) |
955 m['Reply-To'] = ', '.join(replyto) |
953 # Fix up all headers to be native strings. |
|
954 # TODO(durin42): this should probably be cleaned up above in the future. |
|
955 if pycompat.ispy3: |
|
956 for hdr, val in list(m.items()): |
|
957 change = False |
|
958 if isinstance(hdr, bytes): |
|
959 del m[hdr] |
|
960 hdr = pycompat.strurl(hdr) |
|
961 change = True |
|
962 if isinstance(val, bytes): |
|
963 # header value should be ASCII since it's encoded by |
|
964 # mail.headencode(), but -n/--test disables it and raw |
|
965 # value of platform encoding is stored. |
|
966 val = encoding.strfromlocal(val) |
|
967 if not change: |
|
968 # prevent duplicate headers |
|
969 del m[hdr] |
|
970 change = True |
|
971 if change: |
|
972 m[hdr] = val |
|
973 if opts.get(b'test'): |
956 if opts.get(b'test'): |
974 ui.status(_(b'displaying '), subj, b' ...\n') |
957 ui.status(_(b'displaying '), subj, b' ...\n') |
975 ui.pager(b'email') |
958 ui.pager(b'email') |
976 generator = mail.Generator(ui, mangle_from_=False) |
959 generator = mail.Generator(ui, mangle_from_=False) |
977 try: |
960 try: |
985 sendmail = mail.connect(ui, mbox=mbox) |
968 sendmail = mail.connect(ui, mbox=mbox) |
986 ui.status(_(b'sending '), subj, b' ...\n') |
969 ui.status(_(b'sending '), subj, b' ...\n') |
987 progress.update(i, item=subj) |
970 progress.update(i, item=subj) |
988 if not mbox: |
971 if not mbox: |
989 # Exim does not remove the Bcc field |
972 # Exim does not remove the Bcc field |
990 del m[b'Bcc'] |
973 del m['Bcc'] |
991 fp = stringio() |
974 fp = stringio() |
992 generator = mail.Generator(fp, mangle_from_=False) |
975 generator = mail.Generator(fp, mangle_from_=False) |
993 generator.flatten(m, 0) |
976 generator.flatten(m, 0) |
994 alldests = to + bcc + cc |
977 alldests = to + bcc + cc |
995 sendmail(sender_addr, alldests, fp.getvalue()) |
978 sendmail(sender_addr, alldests, fp.getvalue()) |