Patchwork mail: encode long unicode lines in emails properly (issue5687)

login
register
mail settings
Submitter Ippolitov Igor
Date Sept. 21, 2017, 2:59 p.m.
Message ID <474d0bfc980903266886.1506005992@iippolitov-laptop>
Download mbox | patch
Permalink /patch/24090/
State Superseded
Headers show

Comments

Ippolitov Igor - Sept. 21, 2017, 2:59 p.m.
# HG changeset patch
# User Igor Ippolitov <iippolitov@gmail.com>
# Date 1505982498 0
#      Thu Sep 21 08:28:18 2017 +0000
# Node ID 474d0bfc9809032668864cea16026f9b53d24a6d
# Parent  05131c963767faaac6a66b2c658659bfbb4db29b
mail: encode long unicode lines in emails properly (issue5687)

3e544c074459 introduced a bug: emails Content-Transfer-Encoding
is silently replaced with 'quoted-printable' while any other
encoding could be used by underlying code. The problem is revealed
when a long unicode line is encoded.

The patch implements proper check which works for any text and
encoding.
test-notify.t changed so that it would fail on unpatched code
test-patchbomb.t changed due to email headers order change

The patch won't work for python 3.6 as it lacks email.set_charset()
Yuya Nishihara - Sept. 22, 2017, 12:58 p.m.
On Thu, 21 Sep 2017 17:59:52 +0300, Ippolitov Igor wrote:
> # HG changeset patch
> # User Igor Ippolitov <iippolitov@gmail.com>
> # Date 1505982498 0
> #      Thu Sep 21 08:28:18 2017 +0000
> # Node ID 474d0bfc9809032668864cea16026f9b53d24a6d
> # Parent  05131c963767faaac6a66b2c658659bfbb4db29b
> mail: encode long unicode lines in emails properly (issue5687)
> 
> 3e544c074459 introduced a bug: emails Content-Transfer-Encoding
> is silently replaced with 'quoted-printable' while any other
> encoding could be used by underlying code. The problem is revealed
> when a long unicode line is encoded.
> 
> The patch implements proper check which works for any text and
> encoding.
> test-notify.t changed so that it would fail on unpatched code
> test-patchbomb.t changed due to email headers order change
> 
> The patch won't work for python 3.6 as it lacks email.set_charset()
> 
> diff -r 05131c963767 -r 474d0bfc9809 mercurial/mail.py
> --- a/mercurial/mail.py	Wed Sep 20 09:35:45 2017 -0700
> +++ b/mercurial/mail.py	Thu Sep 21 08:28:18 2017 +0000
> @@ -217,16 +217,22 @@
>      Quoted-printable transfer encoding will be used if necessary.
>      '''
>      enc = None
> +    cs = email.charset.Charset(charset)
> +    msg = email.MIMEText.MIMEText('', subtype)
> +    del msg['Content-Transfer-Encoding']
>      for line in body.splitlines():
>          if len(line) > 950:
>              body = quopri.encodestring(body)
> -            enc = "quoted-printable"
> +            cs.body_encoding = email.charset.QP

Perhaps body_encoding should be preserved if it's set to BASE64. IIUC, BASE64
is preferred for utf-8 content. So the easiest hack would be to just set
charset='iso-8859-1' if it was 'us-ascii' and long line detected.

> +            enc = True
>              break
> +    if enc:
> +        msg.set_charset(cs)
> +        msg.set_payload(body)
> +    else:
> +        msg.set_payload(body)
> +        msg.set_charset(cs)

This if/else seems unnecessary if body is not encoded beforehand.

> -    msg = email.MIMEText.MIMEText(body, subtype, charset)
> -    if enc:
> -        del msg['Content-Transfer-Encoding']
> -        msg['Content-Transfer-Encoding'] = enc
>      return msg
Ippolitov Igor - Sept. 22, 2017, 1:29 p.m.
My strong believe is that the software should fully rely on pythong 'email'
module to handle encondings and eveything. Just reuse the code (possibly
fixing it).
So from my point of view, the whole function should consist of just two
latter lines (at least, no manual quopri calls).

> msg.set_payload()
> msg.set_charset()

But in this case I would prefer doing this in a 'robust' way, because I
know nothing about the initial issue.
Nor do I know anything about internal 'email' machinery.

If this is acceptable, I can rewrite this patch to fully rely on pythons
email module and compose a test to make sure the initial issue about long
lines is solved.


2017-09-22 15:58 GMT+03:00 Yuya Nishihara <yuya@tcha.org>:

> On Thu, 21 Sep 2017 17:59:52 +0300, Ippolitov Igor wrote:
> > # HG changeset patch
> > # User Igor Ippolitov <iippolitov@gmail.com>
> > # Date 1505982498 0
> > #      Thu Sep 21 08:28:18 2017 +0000
> > # Node ID 474d0bfc9809032668864cea16026f9b53d24a6d
> > # Parent  05131c963767faaac6a66b2c658659bfbb4db29b
> > mail: encode long unicode lines in emails properly (issue5687)
> >
> > 3e544c074459 introduced a bug: emails Content-Transfer-Encoding
> > is silently replaced with 'quoted-printable' while any other
> > encoding could be used by underlying code. The problem is revealed
> > when a long unicode line is encoded.
> >
> > The patch implements proper check which works for any text and
> > encoding.
> > test-notify.t changed so that it would fail on unpatched code
> > test-patchbomb.t changed due to email headers order change
> >
> > The patch won't work for python 3.6 as it lacks email.set_charset()
> >
> > diff -r 05131c963767 -r 474d0bfc9809 mercurial/mail.py
> > --- a/mercurial/mail.py       Wed Sep 20 09:35:45 2017 -0700
> > +++ b/mercurial/mail.py       Thu Sep 21 08:28:18 2017 +0000
> > @@ -217,16 +217,22 @@
> >      Quoted-printable transfer encoding will be used if necessary.
> >      '''
> >      enc = None
> > +    cs = email.charset.Charset(charset)
> > +    msg = email.MIMEText.MIMEText('', subtype)
> > +    del msg['Content-Transfer-Encoding']
> >      for line in body.splitlines():
> >          if len(line) > 950:
> >              body = quopri.encodestring(body)
> > -            enc = "quoted-printable"
> > +            cs.body_encoding = email.charset.QP
>
> Perhaps body_encoding should be preserved if it's set to BASE64. IIUC,
> BASE64
> is preferred for utf-8 content. So the easiest hack would be to just set
> charset='iso-8859-1' if it was 'us-ascii' and long line detected.
>
> > +            enc = True
> >              break
> > +    if enc:
> > +        msg.set_charset(cs)
> > +        msg.set_payload(body)
> > +    else:
> > +        msg.set_payload(body)
> > +        msg.set_charset(cs)
>
> This if/else seems unnecessary if body is not encoded beforehand.
>
> > -    msg = email.MIMEText.MIMEText(body, subtype, charset)
> > -    if enc:
> > -        del msg['Content-Transfer-Encoding']
> > -        msg['Content-Transfer-Encoding'] = enc
> >      return msg
>
Ippolitov Igor - Sept. 25, 2017, 7:06 a.m.
Hello, Yuya.

I dug a little more and finally realized, that the patch will work nicely
in python 3.6.
The compat3.2 we discussed in IRC is not a module, but API which is in the
class
This api includes set_payload and set_charset methods. So nothing will be
broken

Additionally, I tested that initial issue with long lies is not solved, so
you have to
perform some additional checks encoding the body (so there is no 'simple'
solution)

In the end, I belive, the patch submitted is the best solution for now:
It solves all issues and does minimal changes.

2017-09-22 16:29 GMT+03:00 Igor Ippolitov <iippolitov@gmail.com>:

> My strong believe is that the software should fully rely on pythong
> 'email' module to handle encondings and eveything. Just reuse the code
> (possibly fixing it).
> So from my point of view, the whole function should consist of just two
> latter lines (at least, no manual quopri calls).
>
> > msg.set_payload()
> > msg.set_charset()
>
> But in this case I would prefer doing this in a 'robust' way, because I
> know nothing about the initial issue.
> Nor do I know anything about internal 'email' machinery.
>
> If this is acceptable, I can rewrite this patch to fully rely on pythons
> email module and compose a test to make sure the initial issue about long
> lines is solved.
>
>
> 2017-09-22 15:58 GMT+03:00 Yuya Nishihara <yuya@tcha.org>:
>
>> On Thu, 21 Sep 2017 17:59:52 +0300, Ippolitov Igor wrote:
>> > # HG changeset patch
>> > # User Igor Ippolitov <iippolitov@gmail.com>
>> > # Date 1505982498 0
>> > #      Thu Sep 21 08:28:18 2017 +0000
>> > # Node ID 474d0bfc9809032668864cea16026f9b53d24a6d
>> > # Parent  05131c963767faaac6a66b2c658659bfbb4db29b
>> > mail: encode long unicode lines in emails properly (issue5687)
>> >
>> > 3e544c074459 introduced a bug: emails Content-Transfer-Encoding
>> > is silently replaced with 'quoted-printable' while any other
>> > encoding could be used by underlying code. The problem is revealed
>> > when a long unicode line is encoded.
>> >
>> > The patch implements proper check which works for any text and
>> > encoding.
>> > test-notify.t changed so that it would fail on unpatched code
>> > test-patchbomb.t changed due to email headers order change
>> >
>> > The patch won't work for python 3.6 as it lacks email.set_charset()
>> >
>> > diff -r 05131c963767 -r 474d0bfc9809 mercurial/mail.py
>> > --- a/mercurial/mail.py       Wed Sep 20 09:35:45 2017 -0700
>> > +++ b/mercurial/mail.py       Thu Sep 21 08:28:18 2017 +0000
>> > @@ -217,16 +217,22 @@
>> >      Quoted-printable transfer encoding will be used if necessary.
>> >      '''
>> >      enc = None
>> > +    cs = email.charset.Charset(charset)
>> > +    msg = email.MIMEText.MIMEText('', subtype)
>> > +    del msg['Content-Transfer-Encoding']
>> >      for line in body.splitlines():
>> >          if len(line) > 950:
>> >              body = quopri.encodestring(body)
>> > -            enc = "quoted-printable"
>> > +            cs.body_encoding = email.charset.QP
>>
>> Perhaps body_encoding should be preserved if it's set to BASE64. IIUC,
>> BASE64
>> is preferred for utf-8 content. So the easiest hack would be to just set
>> charset='iso-8859-1' if it was 'us-ascii' and long line detected.
>>
>> > +            enc = True
>> >              break
>> > +    if enc:
>> > +        msg.set_charset(cs)
>> > +        msg.set_payload(body)
>> > +    else:
>> > +        msg.set_payload(body)
>> > +        msg.set_charset(cs)
>>
>> This if/else seems unnecessary if body is not encoded beforehand.
>>
>> > -    msg = email.MIMEText.MIMEText(body, subtype, charset)
>> > -    if enc:
>> > -        del msg['Content-Transfer-Encoding']
>> > -        msg['Content-Transfer-Encoding'] = enc
>> >      return msg
>>
>
>
Yuya Nishihara - Sept. 25, 2017, 1:38 p.m.
On Mon, 25 Sep 2017 10:06:44 +0300, Igor Ippolitov wrote:
> I dug a little more and finally realized, that the patch will work nicely
> in python 3.6.
> The compat3.2 we discussed in IRC is not a module, but API which is in the
> class
> This api includes set_payload and set_charset methods. So nothing will be
> broken
> 
> Additionally, I tested that initial issue with long lies is not solved, so
> you have to
> perform some additional checks encoding the body (so there is no 'simple'
> solution)
> 
> In the end, I belive, the patch submitted is the best solution for now:
> It solves all issues and does minimal changes.

Okay, thanks for investigating that.

> >> >          if len(line) > 950:
> >> >              body = quopri.encodestring(body)
> >> > -            enc = "quoted-printable"
> >> > +            cs.body_encoding = email.charset.QP
> >>
> >> Perhaps body_encoding should be preserved if it's set to BASE64. IIUC,
> >> BASE64
> >> is preferred for utf-8 content.

Any thought on this? Do you think QP is preferred for UTF-8 over the default
BASE64 encoding?

> >> > +            enc = True
> >> >              break
> >> > +    if enc:
> >> > +        msg.set_charset(cs)
> >> > +        msg.set_payload(body)
> >> > +    else:
> >> > +        msg.set_payload(body)
> >> > +        msg.set_charset(cs)
> >>
> >> This if/else seems unnecessary if body is not encoded beforehand.

Can you fix this? IIUC, we can just call msg.set_payload(body, charset) if
body isn't encoded in QP manually.
Ippolitov Igor - Sept. 25, 2017, 1:50 p.m.
There is a nice small research from Django which states that QP is
preferred for UTF8 texts: https://code.djangoproject.com/ticket/22561
Base64 is preferred for binary data.

Concerning the "if enc: ... else:" part, I thought you don't have to encode
long lines on your own. But this is still an issue.
So this part should be as it is.
'set_charset' call sets correct headers for a letter (not only
'Content-Transfer-Encoding')
This call will change payload if no CTE header is set. So for preencoded
payload it is called before payload is set. If it is called after - the
payload will be double encoded (like it is now for long unicode lines).

So I don't see any reasons to change the patch for now.


2017-09-25 16:38 GMT+03:00 Yuya Nishihara <yuya@tcha.org>:

> On Mon, 25 Sep 2017 10:06:44 +0300, Igor Ippolitov wrote:
> > I dug a little more and finally realized, that the patch will work nicely
> > in python 3.6.
> > The compat3.2 we discussed in IRC is not a module, but API which is in
> the
> > class
> > This api includes set_payload and set_charset methods. So nothing will be
> > broken
> >
> > Additionally, I tested that initial issue with long lies is not solved,
> so
> > you have to
> > perform some additional checks encoding the body (so there is no 'simple'
> > solution)
> >
> > In the end, I belive, the patch submitted is the best solution for now:
> > It solves all issues and does minimal changes.
>
> Okay, thanks for investigating that.
>
> > >> >          if len(line) > 950:
> > >> >              body = quopri.encodestring(body)
> > >> > -            enc = "quoted-printable"
> > >> > +            cs.body_encoding = email.charset.QP
> > >>
> > >> Perhaps body_encoding should be preserved if it's set to BASE64. IIUC,
> > >> BASE64
> > >> is preferred for utf-8 content.
>
> Any thought on this? Do you think QP is preferred for UTF-8 over the
> default
> BASE64 encoding?
>
> > >> > +            enc = True
> > >> >              break
> > >> > +    if enc:
> > >> > +        msg.set_charset(cs)
> > >> > +        msg.set_payload(body)
> > >> > +    else:
> > >> > +        msg.set_payload(body)
> > >> > +        msg.set_charset(cs)
> > >>
> > >> This if/else seems unnecessary if body is not encoded beforehand.
>
> Can you fix this? IIUC, we can just call msg.set_payload(body, charset) if
> body isn't encoded in QP manually.
>
Yuya Nishihara - Sept. 25, 2017, 3:13 p.m.
On Mon, 25 Sep 2017 16:50:33 +0300, Igor Ippolitov wrote:
> There is a nice small research from Django which states that QP is
> preferred for UTF8 texts: https://code.djangoproject.com/ticket/22561
> Base64 is preferred for binary data.

Okay, then we might want to use QP no matter if there is a long line.

> Concerning the "if enc: ... else:" part, I thought you don't have to encode
> long lines on your own. But this is still an issue.

Yeah, there is still a long lines issue, but we've enforce the QP encoding
in that case. My question is why can't we rely on the email module to encode
the payload according to the specified charset?

  if long:
      # don't: body = quopri.encodestring(body)
      cs.body_encoding = QP
  else:
      # use the default body_encoding: None, QP or BASE64
  msg.set_payload(body, cs)

> So this part should be as it is.
> 'set_charset' call sets correct headers for a letter (not only
> 'Content-Transfer-Encoding')
> This call will change payload if no CTE header is set. So for preencoded
> payload it is called before payload is set. If it is called after - the
> payload will be double encoded (like it is now for long unicode lines).
Ippolitov Igor - Sept. 25, 2017, 3:39 p.m.
I gave it a thought and seems you are right. The function can be written
down in a better (and shorter) way^

>def mimetextqp(body, subtype, charset):
>    '''Return MIME message.
>    Quoted-printable transfer encoding will be used if necessary.
>    '''
>    cs = email.charset.Charset(charset)
>    msg = email.Message.Message()
>    msg.set_type('text/'+subtype)
>
>    for line in body.splitlines():
>        if len(line) > 950:
>            cs.body_encoding = email.charset.QP
>            break
>
>    msg.set_payload(body,cs)
>
>    return msg

All tests are passed successfully with this change.
Should I submit this as a separate patch?


2017-09-25 18:13 GMT+03:00 Yuya Nishihara <yuya@tcha.org>:

> On Mon, 25 Sep 2017 16:50:33 +0300, Igor Ippolitov wrote:
> > There is a nice small research from Django which states that QP is
> > preferred for UTF8 texts: https://code.djangoproject.com/ticket/22561
> > Base64 is preferred for binary data.
>
> Okay, then we might want to use QP no matter if there is a long line.
>
> > Concerning the "if enc: ... else:" part, I thought you don't have to
> encode
> > long lines on your own. But this is still an issue.
>
> Yeah, there is still a long lines issue, but we've enforce the QP encoding
> in that case. My question is why can't we rely on the email module to
> encode
> the payload according to the specified charset?
>
>   if long:
>       # don't: body = quopri.encodestring(body)
>       cs.body_encoding = QP
>   else:
>       # use the default body_encoding: None, QP or BASE64
>   msg.set_payload(body, cs)
>
> > So this part should be as it is.
> > 'set_charset' call sets correct headers for a letter (not only
> > 'Content-Transfer-Encoding')
> > This call will change payload if no CTE header is set. So for preencoded
> > payload it is called before payload is set. If it is called after - the
> > payload will be double encoded (like it is now for long unicode lines).
>
Yuya Nishihara - Sept. 26, 2017, 12:58 p.m.
On Mon, 25 Sep 2017 15:12:16 -0500, Kevin Bullock wrote:
> > On Sep 25, 2017, at 10:39, Igor Ippolitov <iippolitov@gmail.com> wrote:
> > I gave it a thought and seems you are right. The function can be written down in a better (and shorter) way^
> > 
> > >def mimetextqp(body, subtype, charset):
> > >    '''Return MIME message.
> > >    Quoted-printable transfer encoding will be used if necessary.
> > >    '''
> > >    cs = email.charset.Charset(charset)
> > >    msg = email.Message.Message()
> > >    msg.set_type('text/'+subtype)
> > >
> > >    for line in body.splitlines():
> > >        if len(line) > 950:
> > >            cs.body_encoding = email.charset.QP
> > >            break
> > >
> > >    msg.set_payload(body,cs)
> > >
> > >    return msg
> > 
> > All tests are passed successfully with this change.
> > Should I submit this as a separate patch?

Nice.

> It sounds like it would make more sense to fold this into your existing patch and send a V2.

Yeah, please send a V2.

Patch

diff -r 05131c963767 -r 474d0bfc9809 mercurial/mail.py
--- a/mercurial/mail.py	Wed Sep 20 09:35:45 2017 -0700
+++ b/mercurial/mail.py	Thu Sep 21 08:28:18 2017 +0000
@@ -217,16 +217,22 @@ 
     Quoted-printable transfer encoding will be used if necessary.
     '''
     enc = None
+    cs = email.charset.Charset(charset)
+    msg = email.MIMEText.MIMEText('', subtype)
+    del msg['Content-Transfer-Encoding']
     for line in body.splitlines():
         if len(line) > 950:
             body = quopri.encodestring(body)
-            enc = "quoted-printable"
+            cs.body_encoding = email.charset.QP
+            enc = True
             break
+    if enc:
+        msg.set_charset(cs)
+        msg.set_payload(body)
+    else:
+        msg.set_payload(body)
+        msg.set_charset(cs)
 
-    msg = email.MIMEText.MIMEText(body, subtype, charset)
-    if enc:
-        del msg['Content-Transfer-Encoding']
-        msg['Content-Transfer-Encoding'] = enc
     return msg
 
 def _charsets(ui):
diff -r 05131c963767 -r 474d0bfc9809 tests/test-notify.t
--- a/tests/test-notify.t	Wed Sep 20 09:35:45 2017 -0700
+++ b/tests/test-notify.t	Thu Sep 21 08:28:18 2017 +0000
@@ -416,7 +416,7 @@ 
   > test = False
   > mbox = mbox
   > EOF
-  $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\n")'
+  $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\xd1\x84" + "\n")'
   $ hg --cwd a commit -A -m "long line"
   $ hg --traceback --cwd b pull ../a
   pulling from ../a
@@ -429,25 +429,25 @@ 
   (run 'hg update' to get a working copy)
   $ $PYTHON $TESTTMP/filter.py < b/mbox
   From test@test.com ... ... .. ..:..:.. .... (re)
-  Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
+  Content-Type: text/plain; charset="*" (glob)
   Content-Transfer-Encoding: quoted-printable
   X-Test: foo
   Date: * (glob)
   Subject: long line
   From: test@test.com
-  X-Hg-Notification: changeset e0be44cf638b
-  Message-Id: <hg.e0be44cf638b.*.*@*> (glob)
+  X-Hg-Notification: changeset a323cae54f6e
+  Message-Id: <hg.a323cae54f6e.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset e0be44cf638b in b
+  changeset a323cae54f6e in b
   description: long line
   diffstat:
    a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (8 lines):
   
-  diff -r 7ea05ad269dc -r e0be44cf638b a
+  diff -r 7ea05ad269dc -r a323cae54f6e a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
   @@ -1,3 +1,4 @@ a a a
@@ -464,7 +464,7 @@ 
   ononononononononononononononononononononononononononononononononononononono=
   nononononononononononononononononononononononononononononononononononononon=
   ononononononononononononononononononononononononononononononononononononono=
-  nonononononononononononono
+  nonononononononononononono=D1=84
   
  revset selection: send to address that matches branch and repo
 
@@ -501,11 +501,11 @@ 
   Date: * (glob)
   Subject: test
   From: test@test.com
-  X-Hg-Notification: changeset fbbcbc516f2f
-  Message-Id: <hg.fbbcbc516f2f.*.*@*> (glob)
+  X-Hg-Notification: changeset b7cf10b2bdec
+  Message-Id: <hg.b7cf10b2bdec.*.*@*> (glob)
   To: baz@test.com, foo@bar, notify@example.com
   
-  changeset fbbcbc516f2f in b
+  changeset b7cf10b2bdec in b
   description: test
   (run 'hg update' to get a working copy)
 
@@ -530,11 +530,11 @@ 
   Date: * (glob)
   Subject: test
   From: test@test.com
-  X-Hg-Notification: changeset 38b42fa092de
-  Message-Id: <hg.38b42fa092de.*.*@*> (glob)
+  X-Hg-Notification: changeset 5a07df312a79
+  Message-Id: <hg.5a07df312a79.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset 38b42fa092de in b
+  changeset 5a07df312a79 in b
   description: test
   (run 'hg heads' to see heads)
 
@@ -551,12 +551,12 @@ 
   Date: * (glob)
   Subject: changeset in b: default template
   From: test@test.com
-  X-Hg-Notification: changeset 3548c9e294b6
-  Message-Id: <hg.3548c9e294b6.*.*@*> (glob)
+  X-Hg-Notification: changeset f5e8ec95bf59
+  Message-Id: <hg.f5e8ec95bf59.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset 3548c9e294b6 in $TESTTMP/b (glob)
-  details: http://test/b?cmd=changeset;node=3548c9e294b6
+  changeset f5e8ec95bf59 in $TESTTMP/b (glob)
+  details: http://test/b?cmd=changeset;node=f5e8ec95bf59
   description: default template
 
 with style:
@@ -580,11 +580,11 @@ 
   Date: * (glob)
   Subject: with style
   From: test@test.com
-  X-Hg-Notification: changeset e917dbd961d3
-  Message-Id: <hg.e917dbd961d3.*.*@*> (glob)
+  X-Hg-Notification: changeset 9e2c3a8e9c43
+  Message-Id: <hg.9e2c3a8e9c43.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset e917dbd961d3
+  changeset 9e2c3a8e9c43
 
 with template (overrides style):
 
@@ -601,10 +601,10 @@ 
   MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
-  Subject: a09743fd3edd: with template
+  Subject: e2cbf5bf18a7: with template
   From: test@test.com
-  X-Hg-Notification: changeset a09743fd3edd
-  Message-Id: <hg.a09743fd3edd.*.*@*> (glob)
+  X-Hg-Notification: changeset e2cbf5bf18a7
+  Message-Id: <hg.e2cbf5bf18a7.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
   with template
diff -r 05131c963767 -r 474d0bfc9809 tests/test-patchbomb.t
--- a/tests/test-patchbomb.t	Wed Sep 20 09:35:45 2017 -0700
+++ b/tests/test-patchbomb.t	Thu Sep 21 08:28:18 2017 +0000
@@ -460,8 +460,8 @@ 
 
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="utf-8"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: base64
   Subject: [PATCH] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -638,8 +638,8 @@ 
   sending [PATCH] isolatin 8-bit encoding ...
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="iso-8859-1"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] isolatin 8-bit encoding
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720