hgext/notify.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43117 8ff1ecfadcd1
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
   171 
   171 
   172 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
   172 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
   173 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
   173 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
   174 # be specifying the version(s) of Mercurial they are tested with, or
   174 # be specifying the version(s) of Mercurial they are tested with, or
   175 # leave the attribute unspecified.
   175 # leave the attribute unspecified.
   176 testedwith = 'ships-with-hg-core'
   176 testedwith = b'ships-with-hg-core'
   177 
   177 
   178 configtable = {}
   178 configtable = {}
   179 configitem = registrar.configitem(configtable)
   179 configitem = registrar.configitem(configtable)
   180 
   180 
   181 configitem(
   181 configitem(
   182     'notify', 'changegroup', default=None,
   182     b'notify', b'changegroup', default=None,
   183 )
   183 )
   184 configitem(
   184 configitem(
   185     'notify', 'config', default=None,
   185     b'notify', b'config', default=None,
   186 )
   186 )
   187 configitem(
   187 configitem(
   188     'notify', 'diffstat', default=True,
   188     b'notify', b'diffstat', default=True,
   189 )
   189 )
   190 configitem(
   190 configitem(
   191     'notify', 'domain', default=None,
   191     b'notify', b'domain', default=None,
   192 )
   192 )
   193 configitem(
   193 configitem(
   194     'notify', 'messageidseed', default=None,
   194     b'notify', b'messageidseed', default=None,
   195 )
   195 )
   196 configitem(
   196 configitem(
   197     'notify', 'fromauthor', default=None,
   197     b'notify', b'fromauthor', default=None,
   198 )
   198 )
   199 configitem(
   199 configitem(
   200     'notify', 'incoming', default=None,
   200     b'notify', b'incoming', default=None,
   201 )
   201 )
   202 configitem(
   202 configitem(
   203     'notify', 'maxdiff', default=300,
   203     b'notify', b'maxdiff', default=300,
   204 )
   204 )
   205 configitem(
   205 configitem(
   206     'notify', 'maxdiffstat', default=-1,
   206     b'notify', b'maxdiffstat', default=-1,
   207 )
   207 )
   208 configitem(
   208 configitem(
   209     'notify', 'maxsubject', default=67,
   209     b'notify', b'maxsubject', default=67,
   210 )
   210 )
   211 configitem(
   211 configitem(
   212     'notify', 'mbox', default=None,
   212     b'notify', b'mbox', default=None,
   213 )
   213 )
   214 configitem(
   214 configitem(
   215     'notify', 'merge', default=True,
   215     b'notify', b'merge', default=True,
   216 )
   216 )
   217 configitem(
   217 configitem(
   218     'notify', 'outgoing', default=None,
   218     b'notify', b'outgoing', default=None,
   219 )
   219 )
   220 configitem(
   220 configitem(
   221     'notify', 'sources', default='serve',
   221     b'notify', b'sources', default=b'serve',
   222 )
   222 )
   223 configitem(
   223 configitem(
   224     'notify', 'showfunc', default=None,
   224     b'notify', b'showfunc', default=None,
   225 )
   225 )
   226 configitem(
   226 configitem(
   227     'notify', 'strip', default=0,
   227     b'notify', b'strip', default=0,
   228 )
   228 )
   229 configitem(
   229 configitem(
   230     'notify', 'style', default=None,
   230     b'notify', b'style', default=None,
   231 )
   231 )
   232 configitem(
   232 configitem(
   233     'notify', 'template', default=None,
   233     b'notify', b'template', default=None,
   234 )
   234 )
   235 configitem(
   235 configitem(
   236     'notify', 'test', default=True,
   236     b'notify', b'test', default=True,
   237 )
   237 )
   238 
   238 
   239 # template for single changeset can include email headers.
   239 # template for single changeset can include email headers.
   240 single_template = b'''
   240 single_template = b'''
   241 Subject: changeset in {webroot}: {desc|firstline|strip}
   241 Subject: changeset in {webroot}: {desc|firstline|strip}
   255 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
   255 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
   256 summary: {desc|firstline}
   256 summary: {desc|firstline}
   257 '''
   257 '''
   258 
   258 
   259 deftemplates = {
   259 deftemplates = {
   260     'changegroup': multiple_template,
   260     b'changegroup': multiple_template,
   261 }
   261 }
   262 
   262 
   263 
   263 
   264 class notifier(object):
   264 class notifier(object):
   265     '''email notification class.'''
   265     '''email notification class.'''
   266 
   266 
   267     def __init__(self, ui, repo, hooktype):
   267     def __init__(self, ui, repo, hooktype):
   268         self.ui = ui
   268         self.ui = ui
   269         cfg = self.ui.config('notify', 'config')
   269         cfg = self.ui.config(b'notify', b'config')
   270         if cfg:
   270         if cfg:
   271             self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
   271             self.ui.readconfig(cfg, sections=[b'usersubs', b'reposubs'])
   272         self.repo = repo
   272         self.repo = repo
   273         self.stripcount = int(self.ui.config('notify', 'strip'))
   273         self.stripcount = int(self.ui.config(b'notify', b'strip'))
   274         self.root = self.strip(self.repo.root)
   274         self.root = self.strip(self.repo.root)
   275         self.domain = self.ui.config('notify', 'domain')
   275         self.domain = self.ui.config(b'notify', b'domain')
   276         self.mbox = self.ui.config('notify', 'mbox')
   276         self.mbox = self.ui.config(b'notify', b'mbox')
   277         self.test = self.ui.configbool('notify', 'test')
   277         self.test = self.ui.configbool(b'notify', b'test')
   278         self.charsets = mail._charsets(self.ui)
   278         self.charsets = mail._charsets(self.ui)
   279         self.subs = self.subscribers()
   279         self.subs = self.subscribers()
   280         self.merge = self.ui.configbool('notify', 'merge')
   280         self.merge = self.ui.configbool(b'notify', b'merge')
   281         self.showfunc = self.ui.configbool('notify', 'showfunc')
   281         self.showfunc = self.ui.configbool(b'notify', b'showfunc')
   282         self.messageidseed = self.ui.config('notify', 'messageidseed')
   282         self.messageidseed = self.ui.config(b'notify', b'messageidseed')
   283         if self.showfunc is None:
   283         if self.showfunc is None:
   284             self.showfunc = self.ui.configbool('diff', 'showfunc')
   284             self.showfunc = self.ui.configbool(b'diff', b'showfunc')
   285 
   285 
   286         mapfile = None
   286         mapfile = None
   287         template = self.ui.config('notify', hooktype) or self.ui.config(
   287         template = self.ui.config(b'notify', hooktype) or self.ui.config(
   288             'notify', 'template'
   288             b'notify', b'template'
   289         )
   289         )
   290         if not template:
   290         if not template:
   291             mapfile = self.ui.config('notify', 'style')
   291             mapfile = self.ui.config(b'notify', b'style')
   292         if not mapfile and not template:
   292         if not mapfile and not template:
   293             template = deftemplates.get(hooktype) or single_template
   293             template = deftemplates.get(hooktype) or single_template
   294         spec = logcmdutil.templatespec(template, mapfile)
   294         spec = logcmdutil.templatespec(template, mapfile)
   295         self.t = logcmdutil.changesettemplater(self.ui, self.repo, spec)
   295         self.t = logcmdutil.changesettemplater(self.ui, self.repo, spec)
   296 
   296 
   298         '''strip leading slashes from local path, turn into web-safe path.'''
   298         '''strip leading slashes from local path, turn into web-safe path.'''
   299 
   299 
   300         path = util.pconvert(path)
   300         path = util.pconvert(path)
   301         count = self.stripcount
   301         count = self.stripcount
   302         while count > 0:
   302         while count > 0:
   303             c = path.find('/')
   303             c = path.find(b'/')
   304             if c == -1:
   304             if c == -1:
   305                 break
   305                 break
   306             path = path[c + 1 :]
   306             path = path[c + 1 :]
   307             count -= 1
   307             count -= 1
   308         return path
   308         return path
   310     def fixmail(self, addr):
   310     def fixmail(self, addr):
   311         '''try to clean up email addresses.'''
   311         '''try to clean up email addresses.'''
   312 
   312 
   313         addr = stringutil.email(addr.strip())
   313         addr = stringutil.email(addr.strip())
   314         if self.domain:
   314         if self.domain:
   315             a = addr.find('@localhost')
   315             a = addr.find(b'@localhost')
   316             if a != -1:
   316             if a != -1:
   317                 addr = addr[:a]
   317                 addr = addr[:a]
   318             if '@' not in addr:
   318             if b'@' not in addr:
   319                 return addr + '@' + self.domain
   319                 return addr + b'@' + self.domain
   320         return addr
   320         return addr
   321 
   321 
   322     def subscribers(self):
   322     def subscribers(self):
   323         '''return list of email addresses of subscribers to this repo.'''
   323         '''return list of email addresses of subscribers to this repo.'''
   324         subs = set()
   324         subs = set()
   325         for user, pats in self.ui.configitems('usersubs'):
   325         for user, pats in self.ui.configitems(b'usersubs'):
   326             for pat in pats.split(','):
   326             for pat in pats.split(b','):
   327                 if '#' in pat:
   327                 if b'#' in pat:
   328                     pat, revs = pat.split('#', 1)
   328                     pat, revs = pat.split(b'#', 1)
   329                 else:
   329                 else:
   330                     revs = None
   330                     revs = None
   331                 if fnmatch.fnmatch(self.repo.root, pat.strip()):
   331                 if fnmatch.fnmatch(self.repo.root, pat.strip()):
   332                     subs.add((self.fixmail(user), revs))
   332                     subs.add((self.fixmail(user), revs))
   333         for pat, users in self.ui.configitems('reposubs'):
   333         for pat, users in self.ui.configitems(b'reposubs'):
   334             if '#' in pat:
   334             if b'#' in pat:
   335                 pat, revs = pat.split('#', 1)
   335                 pat, revs = pat.split(b'#', 1)
   336             else:
   336             else:
   337                 revs = None
   337                 revs = None
   338             if fnmatch.fnmatch(self.repo.root, pat):
   338             if fnmatch.fnmatch(self.repo.root, pat):
   339                 for user in users.split(','):
   339                 for user in users.split(b','):
   340                     subs.add((self.fixmail(user), revs))
   340                     subs.add((self.fixmail(user), revs))
   341         return [
   341         return [
   342             (mail.addressencode(self.ui, s, self.charsets, self.test), r)
   342             (mail.addressencode(self.ui, s, self.charsets, self.test), r)
   343             for s, r in sorted(subs)
   343             for s, r in sorted(subs)
   344         ]
   344         ]
   348         if not self.merge and len(ctx.parents()) > 1:
   348         if not self.merge and len(ctx.parents()) > 1:
   349             return False
   349             return False
   350         self.t.show(
   350         self.t.show(
   351             ctx,
   351             ctx,
   352             changes=ctx.changeset(),
   352             changes=ctx.changeset(),
   353             baseurl=self.ui.config('web', 'baseurl'),
   353             baseurl=self.ui.config(b'web', b'baseurl'),
   354             root=self.repo.root,
   354             root=self.repo.root,
   355             webroot=self.root,
   355             webroot=self.root,
   356             **props
   356             **props
   357         )
   357         )
   358         return True
   358         return True
   359 
   359 
   360     def skipsource(self, source):
   360     def skipsource(self, source):
   361         '''true if incoming changes from this source should be skipped.'''
   361         '''true if incoming changes from this source should be skipped.'''
   362         ok_sources = self.ui.config('notify', 'sources').split()
   362         ok_sources = self.ui.config(b'notify', b'sources').split()
   363         return source not in ok_sources
   363         return source not in ok_sources
   364 
   364 
   365     def send(self, ctx, count, data):
   365     def send(self, ctx, count, data):
   366         '''send message.'''
   366         '''send message.'''
   367 
   367 
   369         subs = set()
   369         subs = set()
   370         for sub, spec in self.subs:
   370         for sub, spec in self.subs:
   371             if spec is None:
   371             if spec is None:
   372                 subs.add(sub)
   372                 subs.add(sub)
   373                 continue
   373                 continue
   374             revs = self.repo.revs('%r and %d:', spec, ctx.rev())
   374             revs = self.repo.revs(b'%r and %d:', spec, ctx.rev())
   375             if len(revs):
   375             if len(revs):
   376                 subs.add(sub)
   376                 subs.add(sub)
   377                 continue
   377                 continue
   378         if len(subs) == 0:
   378         if len(subs) == 0:
   379             self.ui.debug(
   379             self.ui.debug(
   380                 'notify: no subscribers to selected repo ' 'and revset\n'
   380                 b'notify: no subscribers to selected repo ' b'and revset\n'
   381             )
   381             )
   382             return
   382             return
   383 
   383 
   384         p = emailparser.Parser()
   384         p = emailparser.Parser()
   385         try:
   385         try:
   406             # reinstate custom headers
   406             # reinstate custom headers
   407             for k, v in headers:
   407             for k, v in headers:
   408                 msg[k] = v
   408                 msg[k] = v
   409 
   409 
   410         msg[r'Date'] = encoding.strfromlocal(
   410         msg[r'Date'] = encoding.strfromlocal(
   411             dateutil.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
   411             dateutil.datestr(format=b"%a, %d %b %Y %H:%M:%S %1%2")
   412         )
   412         )
   413 
   413 
   414         # try to make subject line exist and be useful
   414         # try to make subject line exist and be useful
   415         if not subject:
   415         if not subject:
   416             if count > 1:
   416             if count > 1:
   417                 subject = _('%s: %d new changesets') % (self.root, count)
   417                 subject = _(b'%s: %d new changesets') % (self.root, count)
   418             else:
   418             else:
   419                 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
   419                 s = ctx.description().lstrip().split(b'\n', 1)[0].rstrip()
   420                 subject = '%s: %s' % (self.root, s)
   420                 subject = b'%s: %s' % (self.root, s)
   421         maxsubject = int(self.ui.config('notify', 'maxsubject'))
   421         maxsubject = int(self.ui.config(b'notify', b'maxsubject'))
   422         if maxsubject:
   422         if maxsubject:
   423             subject = stringutil.ellipsis(subject, maxsubject)
   423             subject = stringutil.ellipsis(subject, maxsubject)
   424         msg[r'Subject'] = encoding.strfromlocal(
   424         msg[r'Subject'] = encoding.strfromlocal(
   425             mail.headencode(self.ui, subject, self.charsets, self.test)
   425             mail.headencode(self.ui, subject, self.charsets, self.test)
   426         )
   426         )
   427 
   427 
   428         # try to make message have proper sender
   428         # try to make message have proper sender
   429         if not sender:
   429         if not sender:
   430             sender = self.ui.config('email', 'from') or self.ui.username()
   430             sender = self.ui.config(b'email', b'from') or self.ui.username()
   431         if '@' not in sender or '@localhost' in sender:
   431         if b'@' not in sender or b'@localhost' in sender:
   432             sender = self.fixmail(sender)
   432             sender = self.fixmail(sender)
   433         msg[r'From'] = encoding.strfromlocal(
   433         msg[r'From'] = encoding.strfromlocal(
   434             mail.addressencode(self.ui, sender, self.charsets, self.test)
   434             mail.addressencode(self.ui, sender, self.charsets, self.test)
   435         )
   435         )
   436 
   436 
   437         msg[r'X-Hg-Notification'] = r'changeset %s' % ctx
   437         msg[r'X-Hg-Notification'] = r'changeset %s' % ctx
   438         if not msg[r'Message-Id']:
   438         if not msg[r'Message-Id']:
   439             msg[r'Message-Id'] = messageid(ctx, self.domain, self.messageidseed)
   439             msg[r'Message-Id'] = messageid(ctx, self.domain, self.messageidseed)
   440         msg[r'To'] = encoding.strfromlocal(', '.join(sorted(subs)))
   440         msg[r'To'] = encoding.strfromlocal(b', '.join(sorted(subs)))
   441 
   441 
   442         msgtext = encoding.strtolocal(msg.as_string())
   442         msgtext = encoding.strtolocal(msg.as_string())
   443         if self.test:
   443         if self.test:
   444             self.ui.write(msgtext)
   444             self.ui.write(msgtext)
   445             if not msgtext.endswith('\n'):
   445             if not msgtext.endswith(b'\n'):
   446                 self.ui.write('\n')
   446                 self.ui.write(b'\n')
   447         else:
   447         else:
   448             self.ui.status(
   448             self.ui.status(
   449                 _('notify: sending %d subscribers %d changes\n')
   449                 _(b'notify: sending %d subscribers %d changes\n')
   450                 % (len(subs), count)
   450                 % (len(subs), count)
   451             )
   451             )
   452             mail.sendmail(
   452             mail.sendmail(
   453                 self.ui,
   453                 self.ui,
   454                 stringutil.email(msg[r'From']),
   454                 stringutil.email(msg[r'From']),
   457                 mbox=self.mbox,
   457                 mbox=self.mbox,
   458             )
   458             )
   459 
   459 
   460     def diff(self, ctx, ref=None):
   460     def diff(self, ctx, ref=None):
   461 
   461 
   462         maxdiff = int(self.ui.config('notify', 'maxdiff'))
   462         maxdiff = int(self.ui.config(b'notify', b'maxdiff'))
   463         prev = ctx.p1().node()
   463         prev = ctx.p1().node()
   464         if ref:
   464         if ref:
   465             ref = ref.node()
   465             ref = ref.node()
   466         else:
   466         else:
   467             ref = ctx.node()
   467             ref = ctx.node()
   468         diffopts = patch.diffallopts(self.ui)
   468         diffopts = patch.diffallopts(self.ui)
   469         diffopts.showfunc = self.showfunc
   469         diffopts.showfunc = self.showfunc
   470         chunks = patch.diff(self.repo, prev, ref, opts=diffopts)
   470         chunks = patch.diff(self.repo, prev, ref, opts=diffopts)
   471         difflines = ''.join(chunks).splitlines()
   471         difflines = b''.join(chunks).splitlines()
   472 
   472 
   473         if self.ui.configbool('notify', 'diffstat'):
   473         if self.ui.configbool(b'notify', b'diffstat'):
   474             maxdiffstat = int(self.ui.config('notify', 'maxdiffstat'))
   474             maxdiffstat = int(self.ui.config(b'notify', b'maxdiffstat'))
   475             s = patch.diffstat(difflines)
   475             s = patch.diffstat(difflines)
   476             # s may be nil, don't include the header if it is
   476             # s may be nil, don't include the header if it is
   477             if s:
   477             if s:
   478                 if maxdiffstat >= 0 and s.count("\n") > maxdiffstat + 1:
   478                 if maxdiffstat >= 0 and s.count(b"\n") > maxdiffstat + 1:
   479                     s = s.split("\n")
   479                     s = s.split(b"\n")
   480                     msg = _('\ndiffstat (truncated from %d to %d lines):\n\n')
   480                     msg = _(b'\ndiffstat (truncated from %d to %d lines):\n\n')
   481                     self.ui.write(msg % (len(s) - 2, maxdiffstat))
   481                     self.ui.write(msg % (len(s) - 2, maxdiffstat))
   482                     self.ui.write("\n".join(s[:maxdiffstat] + s[-2:]))
   482                     self.ui.write(b"\n".join(s[:maxdiffstat] + s[-2:]))
   483                 else:
   483                 else:
   484                     self.ui.write(_('\ndiffstat:\n\n%s') % s)
   484                     self.ui.write(_(b'\ndiffstat:\n\n%s') % s)
   485 
   485 
   486         if maxdiff == 0:
   486         if maxdiff == 0:
   487             return
   487             return
   488         elif maxdiff > 0 and len(difflines) > maxdiff:
   488         elif maxdiff > 0 and len(difflines) > maxdiff:
   489             msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
   489             msg = _(b'\ndiffs (truncated from %d to %d lines):\n\n')
   490             self.ui.write(msg % (len(difflines), maxdiff))
   490             self.ui.write(msg % (len(difflines), maxdiff))
   491             difflines = difflines[:maxdiff]
   491             difflines = difflines[:maxdiff]
   492         elif difflines:
   492         elif difflines:
   493             self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
   493             self.ui.write(_(b'\ndiffs (%d lines):\n\n') % len(difflines))
   494 
   494 
   495         self.ui.write("\n".join(difflines))
   495         self.ui.write(b"\n".join(difflines))
   496 
   496 
   497 
   497 
   498 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
   498 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
   499     '''send email notifications to interested subscribers.
   499     '''send email notifications to interested subscribers.
   500 
   500 
   503 
   503 
   504     n = notifier(ui, repo, hooktype)
   504     n = notifier(ui, repo, hooktype)
   505     ctx = repo.unfiltered()[node]
   505     ctx = repo.unfiltered()[node]
   506 
   506 
   507     if not n.subs:
   507     if not n.subs:
   508         ui.debug('notify: no subscribers to repository %s\n' % n.root)
   508         ui.debug(b'notify: no subscribers to repository %s\n' % n.root)
   509         return
   509         return
   510     if n.skipsource(source):
   510     if n.skipsource(source):
   511         ui.debug('notify: changes have source "%s" - skipping\n' % source)
   511         ui.debug(b'notify: changes have source "%s" - skipping\n' % source)
   512         return
   512         return
   513 
   513 
   514     ui.pushbuffer()
   514     ui.pushbuffer()
   515     data = ''
   515     data = b''
   516     count = 0
   516     count = 0
   517     author = ''
   517     author = b''
   518     if hooktype == 'changegroup' or hooktype == 'outgoing':
   518     if hooktype == b'changegroup' or hooktype == b'outgoing':
   519         for rev in repo.changelog.revs(start=ctx.rev()):
   519         for rev in repo.changelog.revs(start=ctx.rev()):
   520             if n.node(repo[rev]):
   520             if n.node(repo[rev]):
   521                 count += 1
   521                 count += 1
   522                 if not author:
   522                 if not author:
   523                     author = repo[rev].user()
   523                     author = repo[rev].user()
   524             else:
   524             else:
   525                 data += ui.popbuffer()
   525                 data += ui.popbuffer()
   526                 ui.note(
   526                 ui.note(
   527                     _('notify: suppressing notification for merge %d:%s\n')
   527                     _(b'notify: suppressing notification for merge %d:%s\n')
   528                     % (rev, repo[rev].hex()[:12])
   528                     % (rev, repo[rev].hex()[:12])
   529                 )
   529                 )
   530                 ui.pushbuffer()
   530                 ui.pushbuffer()
   531         if count:
   531         if count:
   532             n.diff(ctx, repo['tip'])
   532             n.diff(ctx, repo[b'tip'])
   533     elif ctx.rev() in repo:
   533     elif ctx.rev() in repo:
   534         if not n.node(ctx):
   534         if not n.node(ctx):
   535             ui.popbuffer()
   535             ui.popbuffer()
   536             ui.note(
   536             ui.note(
   537                 _('notify: suppressing notification for merge %d:%s\n')
   537                 _(b'notify: suppressing notification for merge %d:%s\n')
   538                 % (ctx.rev(), ctx.hex()[:12])
   538                 % (ctx.rev(), ctx.hex()[:12])
   539             )
   539             )
   540             return
   540             return
   541         count += 1
   541         count += 1
   542         n.diff(ctx)
   542         n.diff(ctx)
   543         if not author:
   543         if not author:
   544             author = ctx.user()
   544             author = ctx.user()
   545 
   545 
   546     data += ui.popbuffer()
   546     data += ui.popbuffer()
   547     fromauthor = ui.config('notify', 'fromauthor')
   547     fromauthor = ui.config(b'notify', b'fromauthor')
   548     if author and fromauthor:
   548     if author and fromauthor:
   549         data = '\n'.join(['From: %s' % author, data])
   549         data = b'\n'.join([b'From: %s' % author, data])
   550 
   550 
   551     if count:
   551     if count:
   552         n.send(ctx, count, data)
   552         n.send(ctx, count, data)
   553 
   553 
   554 
   554 
   557         host = domain
   557         host = domain
   558     else:
   558     else:
   559         host = encoding.strtolocal(socket.getfqdn())
   559         host = encoding.strtolocal(socket.getfqdn())
   560     if messageidseed:
   560     if messageidseed:
   561         messagehash = hashlib.sha512(ctx.hex() + messageidseed)
   561         messagehash = hashlib.sha512(ctx.hex() + messageidseed)
   562         messageid = '<hg.%s@%s>' % (messagehash.hexdigest()[:64], host)
   562         messageid = b'<hg.%s@%s>' % (messagehash.hexdigest()[:64], host)
   563     else:
   563     else:
   564         messageid = '<hg.%s.%d.%d@%s>' % (
   564         messageid = b'<hg.%s.%d.%d@%s>' % (
   565             ctx,
   565             ctx,
   566             int(time.time()),
   566             int(time.time()),
   567             hash(ctx.repo().root),
   567             hash(ctx.repo().root),
   568             host,
   568             host,
   569         )
   569         )