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 |
|
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 |
|
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