[PATCH 2 of 2 STABLE] py3: use native strings when forming email headers in patchbomb

Denis Laxalde denis at laxalde.org
Fri Oct 25 09:02:11 EDT 2019


# HG changeset patch
# User Denis Laxalde <denis at laxalde.org>
# Date 1572008488 -7200
#      Fri Oct 25 15:01:28 2019 +0200
# Branch stable
# Node ID 6058175493d062dbaec2cb0e897e49da4596d147
# Parent  17fad3f1b4742d75db23e17a04c85223bcc239a2
py3: use native strings when forming email headers in patchbomb

We use raw strings for headers' keys and native strings for values. This
fixes "hg email" with a changeset with a message containing non-ascii
characters. This also removes the "if pycompat.ispy3:" TODO.

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -321,10 +321,12 @@ def makepatch(
         subj = b' '.join([prefix, opts.get(b'subject') or subj])
     else:
         subj = b' '.join([prefix, subj])
-    msg[b'Subject'] = mail.headencode(ui, subj, _charsets, opts.get(b'test'))
-    msg[b'X-Mercurial-Node'] = node
-    msg[b'X-Mercurial-Series-Index'] = b'%i' % idx
-    msg[b'X-Mercurial-Series-Total'] = b'%i' % total
+    msg[r'Subject'] = encoding.strfromlocal(
+        mail.headencode(ui, subj, _charsets, opts.get(b'test'))
+    )
+    msg[r'X-Mercurial-Node'] = pycompat.sysstr(node)
+    msg[r'X-Mercurial-Series-Index'] = '%i' % idx
+    msg[r'X-Mercurial-Series-Total'] = '%i' % total
     return msg, subj, ds
 
 
@@ -421,7 +423,9 @@ def _getbundlemsgs(repo, sender, bundle,
     )
     emailencoders.encode_base64(datapart)
     msg.attach(datapart)
-    msg[b'Subject'] = mail.headencode(ui, subj, _charsets, opts.get(r'test'))
+    msg[r'Subject'] = encoding.strfromlocal(
+        mail.headencode(ui, subj, _charsets, opts.get(r'test'))
+    )
     return [(msg, subj, None)]
 
 
@@ -454,7 +458,9 @@ def _makeintro(repo, sender, revs, patch
 
     body = _getdescription(repo, body, sender, **opts)
     msg = mail.mimeencode(ui, body, _charsets, opts.get(r'test'))
-    msg[b'Subject'] = mail.headencode(ui, subj, _charsets, opts.get(r'test'))
+    msg[r'Subject'] = encoding.strfromlocal(
+        mail.headencode(ui, subj, _charsets, opts.get(r'test'))
+    )
     return (msg, subj, diffstat)
 
 
@@ -523,8 +529,10 @@ def _getoutgoing(repo, dest, revs):
 
 def _msgid(node, timestamp):
     hostname = encoding.strtolocal(socket.getfqdn())
-    hostname = encoding.environ.get(b'HGHOSTNAME', hostname)
-    return b'<%s.%d@%s>' % (node, timestamp, hostname)
+    hostname = encoding.strfromlocal(
+        encoding.environ.get(b'HGHOSTNAME', hostname)
+    )
+    return '<%s.%d@%s>' % (node, timestamp, hostname)
 
 
 emailopts = [
@@ -854,7 +862,7 @@ def email(ui, repo, *revs, **opts):
 
     showaddrs = []
 
-    def getaddrs(header, ask=False, default=None):
+    def _getaddrs(header, ask=False, default=None):
         configkey = header.lower()
         opt = header.replace(b'-', b'_').lower()
         addrs = opts.get(opt)
@@ -881,6 +889,12 @@ def email(ui, repo, *revs, **opts):
             )
         return []
 
+    def getaddrs(header, ask=False, default=None):
+        return [
+            encoding.strfromlocal(a)
+            for a in _getaddrs(header, ask=ask, default=default)
+        ]
+
     to = getaddrs(b'To', ask=True)
     if not to:
         # we can get here in non-interactive mode
@@ -912,10 +926,11 @@ def email(ui, repo, *revs, **opts):
     parent = opts.get(b'in_reply_to') or None
     # angle brackets may be omitted, they're not semantically part of the msg-id
     if parent is not None:
-        if not parent.startswith(b'<'):
-            parent = b'<' + parent
-        if not parent.endswith(b'>'):
-            parent += b'>'
+        parent = encoding.strfromlocal(parent)
+        if not parent.startswith('<'):
+            parent = '<' + parent
+        if not parent.endswith('>'):
+            parent += '>'
 
     sender_addr = eutil.parseaddr(encoding.strfromlocal(sender))[1]
     sender = mail.addressencode(ui, sender, _charsets, opts.get(b'test'))
@@ -926,47 +941,32 @@ def email(ui, repo, *revs, **opts):
     )
     for i, (m, subj, ds) in enumerate(msgs):
         try:
-            m[b'Message-Id'] = genmsgid(m[b'X-Mercurial-Node'])
+            m[r'Message-Id'] = genmsgid(m[r'X-Mercurial-Node'])
             if not firstpatch:
-                firstpatch = m[b'Message-Id']
-            m[b'X-Mercurial-Series-Id'] = firstpatch
+                firstpatch = m[r'Message-Id']
+            m[r'X-Mercurial-Series-Id'] = firstpatch
         except TypeError:
-            m[b'Message-Id'] = genmsgid(b'patchbomb')
+            m[r'Message-Id'] = genmsgid('patchbomb')
         if parent:
-            m[b'In-Reply-To'] = parent
-            m[b'References'] = parent
-        if not parent or b'X-Mercurial-Node' not in m:
-            parent = m[b'Message-Id']
+            m[r'In-Reply-To'] = parent
+            m[r'References'] = parent
+        if not parent or r'X-Mercurial-Node' not in m:
+            parent = m[r'Message-Id']
 
-        m[b'User-Agent'] = b'Mercurial-patchbomb/%s' % util.version()
-        m[b'Date'] = eutil.formatdate(start_time[0], localtime=True)
+        m[r'User-Agent'] = encoding.strfromlocal(
+            b'Mercurial-patchbomb/%s' % util.version()
+        )
+        m[r'Date'] = eutil.formatdate(start_time[0], localtime=True)
 
         start_time = (start_time[0] + 1, start_time[1])
-        m[b'From'] = sender
-        m[b'To'] = b', '.join(to)
+        m[r'From'] = encoding.strfromlocal(sender)
+        m[r'To'] = ', '.join(to)
         if cc:
-            m[b'Cc'] = b', '.join(cc)
+            m[r'Cc'] = ', '.join(cc)
         if bcc:
-            m[b'Bcc'] = b', '.join(bcc)
+            m[r'Bcc'] = ', '.join(bcc)
         if replyto:
-            m[b'Reply-To'] = b', '.join(replyto)
-        # Fix up all headers to be native strings.
-        # TODO(durin42): this should probably be cleaned up above in the future.
-        if pycompat.ispy3:
-            for hdr, val in list(m.items()):
-                change = False
-                if isinstance(hdr, bytes):
-                    del m[hdr]
-                    hdr = pycompat.strurl(hdr)
-                    change = True
-                if isinstance(val, bytes):
-                    val = pycompat.strurl(val)
-                    if not change:
-                        # prevent duplicate headers
-                        del m[hdr]
-                    change = True
-                if change:
-                    m[hdr] = val
+            m[r'Reply-To'] = ', '.join(replyto)
         if opts.get(b'test'):
             ui.status(_(b'displaying '), subj, b' ...\n')
             ui.pager(b'email')
@@ -984,12 +984,11 @@ def email(ui, repo, *revs, **opts):
             progress.update(i, item=subj)
             if not mbox:
                 # Exim does not remove the Bcc field
-                del m[b'Bcc']
+                del m[r'Bcc']
             fp = stringio()
             generator = mail.Generator(fp, mangle_from_=False)
             generator.flatten(m, 0)
             alldests = to + bcc + cc
-            alldests = [encoding.strfromlocal(d) for d in alldests]
             sendmail(sender_addr, alldests, fp.getvalue())
 
     progress.complete()


More information about the Mercurial-devel mailing list