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