comparison hgext/notify.py @ 42904:d26a6706b070

notify: add option for deterministic message-id generation Copied from email by durin42: > Pierre-Yves asked offline why I asked a new option for this and why it > is not the default. Message-Id is supposed to be unique world-wide and > while it is desirable to have a deterministic mechanism for them for > creating follow-up emails, it needs organisational control for ensuring > the uniqueness. Differential Revision: https://phab.mercurial-scm.org/D6824
author Joerg Sonnenberger <joerg@bec.de>
date Sat, 07 Sep 2019 12:49:33 +0200
parents 43f9b8c0574b
children 2372284d9457
comparison
equal deleted inserted replaced
42903:66048f6b5d0d 42904:d26a6706b070
80 turn them into relative paths. For example, ``notify.strip=3`` will change 80 turn them into relative paths. For example, ``notify.strip=3`` will change
81 ``/long/path/repository`` into ``repository``. Default: 0. 81 ``/long/path/repository`` into ``repository``. Default: 0.
82 82
83 notify.domain 83 notify.domain
84 Default email domain for sender or recipients with no explicit domain. 84 Default email domain for sender or recipients with no explicit domain.
85 It is also used for the domain part of the ``Message-Id`` when using
86 ``notify.messageidseed``.
87
88 notify.messageidseed
89 Create deterministic ``Message-Id`` headers for the mails based on the seed
90 and the revision identifier of the first commit in the changeset.
85 91
86 notify.style 92 notify.style
87 Style file to use when formatting emails. 93 Style file to use when formatting emails.
88 94
89 notify.template 95 notify.template
142 from __future__ import absolute_import 148 from __future__ import absolute_import
143 149
144 import email.errors as emailerrors 150 import email.errors as emailerrors
145 import email.parser as emailparser 151 import email.parser as emailparser
146 import fnmatch 152 import fnmatch
153 import hashlib
147 import socket 154 import socket
148 import time 155 import time
149 156
150 from mercurial.i18n import _ 157 from mercurial.i18n import _
151 from mercurial import ( 158 from mercurial import (
179 ) 186 )
180 configitem('notify', 'diffstat', 187 configitem('notify', 'diffstat',
181 default=True, 188 default=True,
182 ) 189 )
183 configitem('notify', 'domain', 190 configitem('notify', 'domain',
191 default=None,
192 )
193 configitem('notify', 'messageidseed',
184 default=None, 194 default=None,
185 ) 195 )
186 configitem('notify', 'fromauthor', 196 configitem('notify', 'fromauthor',
187 default=None, 197 default=None,
188 ) 198 )
266 self.test = self.ui.configbool('notify', 'test') 276 self.test = self.ui.configbool('notify', 'test')
267 self.charsets = mail._charsets(self.ui) 277 self.charsets = mail._charsets(self.ui)
268 self.subs = self.subscribers() 278 self.subs = self.subscribers()
269 self.merge = self.ui.configbool('notify', 'merge') 279 self.merge = self.ui.configbool('notify', 'merge')
270 self.showfunc = self.ui.configbool('notify', 'showfunc') 280 self.showfunc = self.ui.configbool('notify', 'showfunc')
281 self.messageidseed = self.ui.config('notify', 'messageidseed')
271 if self.showfunc is None: 282 if self.showfunc is None:
272 self.showfunc = self.ui.configbool('diff', 'showfunc') 283 self.showfunc = self.ui.configbool('diff', 'showfunc')
273 284
274 mapfile = None 285 mapfile = None
275 template = (self.ui.config('notify', hooktype) or 286 template = (self.ui.config('notify', hooktype) or
410 msg[r'From'] = encoding.strfromlocal( 421 msg[r'From'] = encoding.strfromlocal(
411 mail.addressencode(self.ui, sender, self.charsets, self.test)) 422 mail.addressencode(self.ui, sender, self.charsets, self.test))
412 423
413 msg[r'X-Hg-Notification'] = r'changeset %s' % ctx 424 msg[r'X-Hg-Notification'] = r'changeset %s' % ctx
414 if not msg[r'Message-Id']: 425 if not msg[r'Message-Id']:
415 msg[r'Message-Id'] = encoding.strfromlocal( 426 msg[r'Message-Id'] = messageid(ctx, self.domain, self.messageidseed)
416 '<hg.%s.%d.%d@%s>' % (ctx, int(time.time()),
417 hash(self.repo.root),
418 encoding.strtolocal(socket.getfqdn())))
419 msg[r'To'] = encoding.strfromlocal(', '.join(sorted(subs))) 427 msg[r'To'] = encoding.strfromlocal(', '.join(sorted(subs)))
420 428
421 msgtext = encoding.strtolocal(msg.as_string()) 429 msgtext = encoding.strtolocal(msg.as_string())
422 if self.test: 430 if self.test:
423 self.ui.write(msgtext) 431 self.ui.write(msgtext)
515 if author and fromauthor: 523 if author and fromauthor:
516 data = '\n'.join(['From: %s' % author, data]) 524 data = '\n'.join(['From: %s' % author, data])
517 525
518 if count: 526 if count:
519 n.send(ctx, count, data) 527 n.send(ctx, count, data)
528
529 def messageid(ctx, domain, messageidseed):
530 if domain and messageidseed:
531 host = domain
532 else:
533 host = encoding.strtolocal(socket.getfqdn())
534 if messageidseed:
535 messagehash = hashlib.sha512(ctx.hex() + messageidseed)
536 messageid = '<hg.%s@%s>' % (messagehash.hexdigest()[:64], host)
537 else:
538 messageid = '<hg.%s.%d.%d@%s>' % (ctx, int(time.time()),
539 hash(ctx.repo().root), host)
540 return encoding.strfromlocal(messageid)