hgext/hooklib/changeset_obsoleted.py
changeset 44413 4cabeea6d214
child 45075 04ef381000a8
equal deleted inserted replaced
44412:edc8504bc26b 44413:4cabeea6d214
       
     1 # Copyright 2020 Joerg Sonnenberger <joerg@bec.de>
       
     2 #
       
     3 # This software may be used and distributed according to the terms of the
       
     4 # GNU General Public License version 2 or any later version.
       
     5 """changeset_obsoleted is a hook to send a mail when an
       
     6 existing draft changeset is obsoleted by an obsmarker without successor.
       
     7 
       
     8 Correct message threading requires the same messageidseed to be used for both
       
     9 the original notification and the new mail.
       
    10 
       
    11 Usage:
       
    12   [notify]
       
    13   messageidseed = myseed
       
    14 
       
    15   [hooks]
       
    16   pretxnclose.changeset_obsoleted = \
       
    17     python:hgext.hooklib.changeset_obsoleted.hook
       
    18 """
       
    19 
       
    20 from __future__ import absolute_import
       
    21 
       
    22 import email.errors as emailerrors
       
    23 import email.utils as emailutils
       
    24 
       
    25 from mercurial.i18n import _
       
    26 from mercurial import (
       
    27     encoding,
       
    28     error,
       
    29     logcmdutil,
       
    30     mail,
       
    31     obsutil,
       
    32     pycompat,
       
    33     registrar,
       
    34 )
       
    35 from mercurial.utils import dateutil
       
    36 from .. import notify
       
    37 
       
    38 configtable = {}
       
    39 configitem = registrar.configitem(configtable)
       
    40 
       
    41 configitem(
       
    42     b'notify_obsoleted', b'domain', default=None,
       
    43 )
       
    44 configitem(
       
    45     b'notify_obsoleted', b'messageidseed', default=None,
       
    46 )
       
    47 configitem(
       
    48     b'notify_obsoleted',
       
    49     b'template',
       
    50     default=b'''Subject: changeset abandoned
       
    51 
       
    52 This changeset has been abandoned.
       
    53 ''',
       
    54 )
       
    55 
       
    56 
       
    57 def _report_commit(ui, repo, ctx):
       
    58     domain = ui.config(b'notify_obsoleted', b'domain') or ui.config(
       
    59         b'notify', b'domain'
       
    60     )
       
    61     messageidseed = ui.config(
       
    62         b'notify_obsoleted', b'messageidseed'
       
    63     ) or ui.config(b'notify', b'messageidseed')
       
    64     template = ui.config(b'notify_obsoleted', b'template')
       
    65     spec = logcmdutil.templatespec(template, None)
       
    66     templater = logcmdutil.changesettemplater(ui, repo, spec)
       
    67     ui.pushbuffer()
       
    68     n = notify.notifier(ui, repo, b'incoming')
       
    69 
       
    70     subs = set()
       
    71     for sub, spec in n.subs:
       
    72         if spec is None:
       
    73             subs.add(sub)
       
    74             continue
       
    75         revs = repo.revs(b'%r and %d:', spec, ctx.rev())
       
    76         if len(revs):
       
    77             subs.add(sub)
       
    78             continue
       
    79     if len(subs) == 0:
       
    80         ui.debug(
       
    81             b'notify_obsoleted: no subscribers to selected repo and revset\n'
       
    82         )
       
    83         return
       
    84 
       
    85     templater.show(
       
    86         ctx,
       
    87         changes=ctx.changeset(),
       
    88         baseurl=ui.config(b'web', b'baseurl'),
       
    89         root=repo.root,
       
    90         webroot=n.root,
       
    91     )
       
    92     data = ui.popbuffer()
       
    93 
       
    94     try:
       
    95         msg = mail.parsebytes(data)
       
    96     except emailerrors.MessageParseError as inst:
       
    97         raise error.Abort(inst)
       
    98 
       
    99     msg['In-reply-to'] = notify.messageid(ctx, domain, messageidseed)
       
   100     msg['Message-Id'] = notify.messageid(
       
   101         ctx, domain, messageidseed + b'-obsoleted'
       
   102     )
       
   103     msg['Date'] = encoding.strfromlocal(
       
   104         dateutil.datestr(format=b"%a, %d %b %Y %H:%M:%S %1%2")
       
   105     )
       
   106     if not msg['From']:
       
   107         sender = ui.config(b'email', b'from') or ui.username()
       
   108         if b'@' not in sender or b'@localhost' in sender:
       
   109             sender = n.fixmail(sender)
       
   110         msg['From'] = mail.addressencode(ui, sender, n.charsets, n.test)
       
   111     msg['To'] = ', '.join(sorted(subs))
       
   112 
       
   113     msgtext = msg.as_bytes() if pycompat.ispy3 else msg.as_string()
       
   114     if ui.configbool(b'notify', b'test'):
       
   115         ui.write(msgtext)
       
   116         if not msgtext.endswith(b'\n'):
       
   117             ui.write(b'\n')
       
   118     else:
       
   119         ui.status(_(b'notify_obsoleted: sending mail for %d\n') % ctx.rev())
       
   120         mail.sendmail(
       
   121             ui, emailutils.parseaddr(msg['From'])[1], subs, msgtext, mbox=n.mbox
       
   122         )
       
   123 
       
   124 
       
   125 def hook(ui, repo, hooktype, node=None, **kwargs):
       
   126     if hooktype != b"pretxnclose":
       
   127         raise error.Abort(
       
   128             _(b'Unsupported hook type %r') % pycompat.bytestr(hooktype)
       
   129         )
       
   130     for rev in obsutil.getobsoleted(repo, repo.currenttransaction()):
       
   131         _report_commit(ui, repo, repo.unfiltered()[rev])