Show More
@@ -0,0 +1,26 b'' | |||||
|
1 | """collection of simple hooks for common tasks (EXPERIMENTAL) | |||
|
2 | ||||
|
3 | This extension provides a number of simple hooks to handle issues | |||
|
4 | commonly found in repositories with many contributors: | |||
|
5 | - email notification when changesets move from draft to public phase | |||
|
6 | - email notification when changesets are obsoleted | |||
|
7 | - enforcement of draft phase for all incoming changesets | |||
|
8 | - enforcement of a no-branch-merge policy | |||
|
9 | - enforcement of a no-multiple-heads policy | |||
|
10 | ||||
|
11 | The implementation of the hooks is subject to change, e.g. whether to | |||
|
12 | implement them as individual hooks or merge them into the notify | |||
|
13 | extension as option. The functionality itself is planned to be supported | |||
|
14 | long-term. | |||
|
15 | """ | |||
|
16 | from __future__ import absolute_import | |||
|
17 | from . import ( | |||
|
18 | changeset_obsoleted, | |||
|
19 | changeset_published, | |||
|
20 | ) | |||
|
21 | ||||
|
22 | # configtable is only picked up from the "top-level" module of the extension, | |||
|
23 | # so expand it here to ensure all items are properly loaded | |||
|
24 | configtable = {} | |||
|
25 | configtable.update(changeset_published.configtable) | |||
|
26 | configtable.update(changeset_obsoleted.configtable) |
@@ -0,0 +1,131 b'' | |||||
|
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]) |
@@ -0,0 +1,131 b'' | |||||
|
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_published is a hook to send a mail when an | |||
|
6 | existing draft changeset is moved to the public phase. | |||
|
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 | txnclose-phase.changeset_published = \ | |||
|
17 | python:hgext.hooklib.changeset_published.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 | pycompat, | |||
|
32 | registrar, | |||
|
33 | ) | |||
|
34 | from mercurial.utils import dateutil | |||
|
35 | from .. import notify | |||
|
36 | ||||
|
37 | configtable = {} | |||
|
38 | configitem = registrar.configitem(configtable) | |||
|
39 | ||||
|
40 | configitem( | |||
|
41 | b'notify_published', b'domain', default=None, | |||
|
42 | ) | |||
|
43 | configitem( | |||
|
44 | b'notify_published', b'messageidseed', default=None, | |||
|
45 | ) | |||
|
46 | configitem( | |||
|
47 | b'notify_published', | |||
|
48 | b'template', | |||
|
49 | default=b'''Subject: changeset published | |||
|
50 | ||||
|
51 | This changeset has been published. | |||
|
52 | ''', | |||
|
53 | ) | |||
|
54 | ||||
|
55 | ||||
|
56 | def _report_commit(ui, repo, ctx): | |||
|
57 | domain = ui.config(b'notify_published', b'domain') or ui.config( | |||
|
58 | b'notify', b'domain' | |||
|
59 | ) | |||
|
60 | messageidseed = ui.config( | |||
|
61 | b'notify_published', b'messageidseed' | |||
|
62 | ) or ui.config(b'notify', b'messageidseed') | |||
|
63 | template = ui.config(b'notify_published', b'template') | |||
|
64 | spec = logcmdutil.templatespec(template, None) | |||
|
65 | templater = logcmdutil.changesettemplater(ui, repo, spec) | |||
|
66 | ui.pushbuffer() | |||
|
67 | n = notify.notifier(ui, repo, b'incoming') | |||
|
68 | ||||
|
69 | subs = set() | |||
|
70 | for sub, spec in n.subs: | |||
|
71 | if spec is None: | |||
|
72 | subs.add(sub) | |||
|
73 | continue | |||
|
74 | revs = repo.revs(b'%r and %d:', spec, ctx.rev()) | |||
|
75 | if len(revs): | |||
|
76 | subs.add(sub) | |||
|
77 | continue | |||
|
78 | if len(subs) == 0: | |||
|
79 | ui.debug( | |||
|
80 | b'notify_published: no subscribers to selected repo and revset\n' | |||
|
81 | ) | |||
|
82 | return | |||
|
83 | ||||
|
84 | templater.show( | |||
|
85 | ctx, | |||
|
86 | changes=ctx.changeset(), | |||
|
87 | baseurl=ui.config(b'web', b'baseurl'), | |||
|
88 | root=repo.root, | |||
|
89 | webroot=n.root, | |||
|
90 | ) | |||
|
91 | data = ui.popbuffer() | |||
|
92 | ||||
|
93 | try: | |||
|
94 | msg = mail.parsebytes(data) | |||
|
95 | except emailerrors.MessageParseError as inst: | |||
|
96 | raise error.Abort(inst) | |||
|
97 | ||||
|
98 | msg['In-reply-to'] = notify.messageid(ctx, domain, messageidseed) | |||
|
99 | msg['Message-Id'] = notify.messageid( | |||
|
100 | ctx, domain, messageidseed + b'-published' | |||
|
101 | ) | |||
|
102 | msg['Date'] = encoding.strfromlocal( | |||
|
103 | dateutil.datestr(format=b"%a, %d %b %Y %H:%M:%S %1%2") | |||
|
104 | ) | |||
|
105 | if not msg['From']: | |||
|
106 | sender = ui.config(b'email', b'from') or ui.username() | |||
|
107 | if b'@' not in sender or b'@localhost' in sender: | |||
|
108 | sender = n.fixmail(sender) | |||
|
109 | msg['From'] = mail.addressencode(ui, sender, n.charsets, n.test) | |||
|
110 | msg['To'] = ', '.join(sorted(subs)) | |||
|
111 | ||||
|
112 | msgtext = msg.as_bytes() if pycompat.ispy3 else msg.as_string() | |||
|
113 | if ui.configbool(b'notify', b'test'): | |||
|
114 | ui.write(msgtext) | |||
|
115 | if not msgtext.endswith(b'\n'): | |||
|
116 | ui.write(b'\n') | |||
|
117 | else: | |||
|
118 | ui.status(_(b'notify_published: sending mail for %d\n') % ctx.rev()) | |||
|
119 | mail.sendmail( | |||
|
120 | ui, emailutils.parseaddr(msg['From'])[1], subs, msgtext, mbox=n.mbox | |||
|
121 | ) | |||
|
122 | ||||
|
123 | ||||
|
124 | def hook(ui, repo, hooktype, node=None, **kwargs): | |||
|
125 | if hooktype != b"txnclose-phase": | |||
|
126 | raise error.Abort( | |||
|
127 | _(b'Unsupported hook type %r') % pycompat.bytestr(hooktype) | |||
|
128 | ) | |||
|
129 | ctx = repo.unfiltered()[node] | |||
|
130 | if kwargs['oldphase'] == b'draft' and kwargs['phase'] == b'public': | |||
|
131 | _report_commit(ui, repo, ctx) |
@@ -0,0 +1,45 b'' | |||||
|
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 | ||||
|
6 | """enforce_draft_commits us a hook to ensure that all new changesets are | |||
|
7 | in the draft phase. This allows enforcing policies for work-in-progress | |||
|
8 | changes in overlay repositories, i.e. a shared hidden repositories with | |||
|
9 | different views for work-in-progress code and public history. | |||
|
10 | ||||
|
11 | Usage: | |||
|
12 | [hooks] | |||
|
13 | pretxnclose-phase.enforce_draft_commits = \ | |||
|
14 | python:hgext.hooklib.enforce_draft_commits.hook | |||
|
15 | """ | |||
|
16 | ||||
|
17 | from __future__ import absolute_import | |||
|
18 | ||||
|
19 | from mercurial.i18n import _ | |||
|
20 | from mercurial import ( | |||
|
21 | error, | |||
|
22 | pycompat, | |||
|
23 | ) | |||
|
24 | ||||
|
25 | ||||
|
26 | def hook(ui, repo, hooktype, node=None, **kwargs): | |||
|
27 | if hooktype != b"pretxnclose-phase": | |||
|
28 | raise error.Abort( | |||
|
29 | _(b'Unsupported hook type %r') % pycompat.bytestr(hooktype) | |||
|
30 | ) | |||
|
31 | ctx = repo.unfiltered()[node] | |||
|
32 | if kwargs['oldphase']: | |||
|
33 | raise error.Abort( | |||
|
34 | _(b'Phase change from %r to %r for %s rejected') | |||
|
35 | % ( | |||
|
36 | pycompat.bytestr(kwargs['oldphase']), | |||
|
37 | pycompat.bytestr(kwargs['phase']), | |||
|
38 | ctx, | |||
|
39 | ) | |||
|
40 | ) | |||
|
41 | elif kwargs['phase'] != b'draft': | |||
|
42 | raise error.Abort( | |||
|
43 | _(b'New changeset %s in phase %r rejected') | |||
|
44 | % (ctx, pycompat.bytestr(kwargs['phase'])) | |||
|
45 | ) |
@@ -0,0 +1,45 b'' | |||||
|
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 | ||||
|
6 | """reject_merge_commits is a hook to check new changesets for merge commits. | |||
|
7 | Merge commits are allowed only between different branches, i.e. merging | |||
|
8 | a feature branch into the main development branch. This can be used to | |||
|
9 | enforce policies for linear commit histories. | |||
|
10 | ||||
|
11 | Usage: | |||
|
12 | [hooks] | |||
|
13 | pretxnchangegroup.reject_merge_commits = \ | |||
|
14 | python:hgext.hooklib.reject_merge_commits.hook | |||
|
15 | """ | |||
|
16 | ||||
|
17 | from __future__ import absolute_import | |||
|
18 | ||||
|
19 | from mercurial.i18n import _ | |||
|
20 | from mercurial import ( | |||
|
21 | error, | |||
|
22 | pycompat, | |||
|
23 | ) | |||
|
24 | ||||
|
25 | ||||
|
26 | def hook(ui, repo, hooktype, node=None, **kwargs): | |||
|
27 | if hooktype != b"pretxnchangegroup": | |||
|
28 | raise error.Abort( | |||
|
29 | _(b'Unsupported hook type %r') % pycompat.bytestr(hooktype) | |||
|
30 | ) | |||
|
31 | ||||
|
32 | ctx = repo.unfiltered()[node] | |||
|
33 | for rev in repo.changelog.revs(start=ctx.rev()): | |||
|
34 | rev = repo[rev] | |||
|
35 | parents = rev.parents() | |||
|
36 | if len(parents) < 2: | |||
|
37 | continue | |||
|
38 | if all(repo[p].branch() == rev.branch() for p in parents): | |||
|
39 | raise error.Abort( | |||
|
40 | _( | |||
|
41 | b'%s rejected as merge on the same branch. ' | |||
|
42 | b'Please consider rebase.' | |||
|
43 | ) | |||
|
44 | % rev | |||
|
45 | ) |
@@ -0,0 +1,41 b'' | |||||
|
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 | ||||
|
6 | """reject_new_heads is a hook to check that branches touched by new changesets | |||
|
7 | have at most one open head. It can be used to enforce policies for | |||
|
8 | merge-before-push or rebase-before-push. It does not handle pre-existing | |||
|
9 | hydras. | |||
|
10 | ||||
|
11 | Usage: | |||
|
12 | [hooks] | |||
|
13 | pretxnclose.reject_new_heads = \ | |||
|
14 | python:hgext.hooklib.reject_new_heads.hook | |||
|
15 | """ | |||
|
16 | ||||
|
17 | from __future__ import absolute_import | |||
|
18 | ||||
|
19 | from mercurial.i18n import _ | |||
|
20 | from mercurial import ( | |||
|
21 | error, | |||
|
22 | pycompat, | |||
|
23 | ) | |||
|
24 | ||||
|
25 | ||||
|
26 | def hook(ui, repo, hooktype, node=None, **kwargs): | |||
|
27 | if hooktype != b"pretxnclose": | |||
|
28 | raise error.Abort( | |||
|
29 | _(b'Unsupported hook type %r') % pycompat.bytestr(hooktype) | |||
|
30 | ) | |||
|
31 | ctx = repo.unfiltered()[node] | |||
|
32 | branches = set() | |||
|
33 | for rev in repo.changelog.revs(start=ctx.rev()): | |||
|
34 | rev = repo[rev] | |||
|
35 | branches.add(rev.branch()) | |||
|
36 | for branch in branches: | |||
|
37 | if len(repo.revs("head() and not closed() and branch(%s)", branch)) > 1: | |||
|
38 | raise error.Abort( | |||
|
39 | _(b'Changes on branch %r resulted in multiple heads') | |||
|
40 | % pycompat.bytestr(branch) | |||
|
41 | ) |
@@ -0,0 +1,84 b'' | |||||
|
1 | $ cat <<EOF >> $HGRCPATH | |||
|
2 | > [experimental] | |||
|
3 | > evolution = true | |||
|
4 | > | |||
|
5 | > [extensions] | |||
|
6 | > notify = | |||
|
7 | > hooklib = | |||
|
8 | > | |||
|
9 | > [phases] | |||
|
10 | > publish = False | |||
|
11 | > | |||
|
12 | > [notify] | |||
|
13 | > sources = pull | |||
|
14 | > diffstat = False | |||
|
15 | > messageidseed = example | |||
|
16 | > domain = example.com | |||
|
17 | > | |||
|
18 | > [reposubs] | |||
|
19 | > * = baz | |||
|
20 | > EOF | |||
|
21 | $ hg init a | |||
|
22 | $ hg --cwd a debugbuilddag +2 | |||
|
23 | $ hg init b | |||
|
24 | $ cat <<EOF >> b/.hg/hgrc | |||
|
25 | > [hooks] | |||
|
26 | > incoming.notify = python:hgext.notify.hook | |||
|
27 | > pretxnclose.changeset_obsoleted = python:hgext.hooklib.changeset_obsoleted.hook | |||
|
28 | > EOF | |||
|
29 | $ hg --cwd b pull ../a | "$PYTHON" $TESTDIR/unwrap-message-id.py | |||
|
30 | pulling from ../a | |||
|
31 | requesting all changes | |||
|
32 | adding changesets | |||
|
33 | adding manifests | |||
|
34 | adding file changes | |||
|
35 | added 2 changesets with 0 changes to 0 files | |||
|
36 | new changesets 1ea73414a91b:66f7d451a68b (2 drafts) | |||
|
37 | MIME-Version: 1.0 | |||
|
38 | Content-Type: text/plain; charset="us-ascii" | |||
|
39 | Content-Transfer-Encoding: 7bit | |||
|
40 | Date: * (glob) | |||
|
41 | Subject: changeset in * (glob) | |||
|
42 | From: debugbuilddag@example.com | |||
|
43 | X-Hg-Notification: changeset 1ea73414a91b | |||
|
44 | Message-Id: <hg.81c297828fd2d5afaadf2775a6a71b74143b6451dfaac09fac939e9107a50d01@example.com> | |||
|
45 | To: baz@example.com | |||
|
46 | ||||
|
47 | changeset 1ea73414a91b in $TESTTMP/b | |||
|
48 | details: $TESTTMP/b?cmd=changeset;node=1ea73414a91b | |||
|
49 | description: | |||
|
50 | r0 | |||
|
51 | MIME-Version: 1.0 | |||
|
52 | Content-Type: text/plain; charset="us-ascii" | |||
|
53 | Content-Transfer-Encoding: 7bit | |||
|
54 | Date: * (glob) | |||
|
55 | Subject: changeset in * (glob) | |||
|
56 | From: debugbuilddag@example.com | |||
|
57 | X-Hg-Notification: changeset 66f7d451a68b | |||
|
58 | Message-Id: <hg.364d03da7dc13829eb779a805be7e37f54f572e9afcea7d2626856a794d3e8f3@example.com> | |||
|
59 | To: baz@example.com | |||
|
60 | ||||
|
61 | changeset 66f7d451a68b in $TESTTMP/b | |||
|
62 | details: $TESTTMP/b?cmd=changeset;node=66f7d451a68b | |||
|
63 | description: | |||
|
64 | r1 | |||
|
65 | (run 'hg update' to get a working copy) | |||
|
66 | $ hg --cwd a debugobsolete 1ea73414a91b0920940797d8fc6a11e447f8ea1e | |||
|
67 | 1 new obsolescence markers | |||
|
68 | obsoleted 1 changesets | |||
|
69 | 1 new orphan changesets | |||
|
70 | $ hg --cwd a push ../b --hidden | "$PYTHON" $TESTDIR/unwrap-message-id.py | |||
|
71 | 1 new orphan changesets | |||
|
72 | pushing to ../b | |||
|
73 | searching for changes | |||
|
74 | no changes found | |||
|
75 | Subject: changeset abandoned | |||
|
76 | In-reply-to: <hg.81c297828fd2d5afaadf2775a6a71b74143b6451dfaac09fac939e9107a50d01@example.com> | |||
|
77 | Message-Id: <hg.d6329e9481594f0f3c8a84362b3511318bfbce50748ab1123f909eb6fbcab018@example.com> | |||
|
78 | Date: * (glob) | |||
|
79 | From: test@example.com | |||
|
80 | To: baz@example.com | |||
|
81 | ||||
|
82 | This changeset has been abandoned. | |||
|
83 | 1 new obsolescence markers | |||
|
84 | obsoleted 1 changesets |
@@ -0,0 +1,84 b'' | |||||
|
1 | $ cat <<EOF >> $HGRCPATH | |||
|
2 | > [extensions] | |||
|
3 | > notify = | |||
|
4 | > hooklib = | |||
|
5 | > | |||
|
6 | > [phases] | |||
|
7 | > publish = False | |||
|
8 | > | |||
|
9 | > [notify] | |||
|
10 | > sources = pull | |||
|
11 | > diffstat = False | |||
|
12 | > messageidseed = example | |||
|
13 | > domain = example.com | |||
|
14 | > | |||
|
15 | > [reposubs] | |||
|
16 | > * = baz | |||
|
17 | > EOF | |||
|
18 | $ hg init a | |||
|
19 | $ hg --cwd a debugbuilddag . | |||
|
20 | $ hg init b | |||
|
21 | $ cat <<EOF >> b/.hg/hgrc | |||
|
22 | > [hooks] | |||
|
23 | > incoming.notify = python:hgext.notify.hook | |||
|
24 | > txnclose-phase.changeset_published = python:hgext.hooklib.changeset_published.hook | |||
|
25 | > EOF | |||
|
26 | $ hg --cwd b pull ../a | "$PYTHON" $TESTDIR/unwrap-message-id.py | |||
|
27 | pulling from ../a | |||
|
28 | requesting all changes | |||
|
29 | adding changesets | |||
|
30 | adding manifests | |||
|
31 | adding file changes | |||
|
32 | added 1 changesets with 0 changes to 0 files | |||
|
33 | new changesets 1ea73414a91b (1 drafts) | |||
|
34 | MIME-Version: 1.0 | |||
|
35 | Content-Type: text/plain; charset="us-ascii" | |||
|
36 | Content-Transfer-Encoding: 7bit | |||
|
37 | Date: * (glob) | |||
|
38 | Subject: changeset in * (glob) | |||
|
39 | From: debugbuilddag@example.com | |||
|
40 | X-Hg-Notification: changeset 1ea73414a91b | |||
|
41 | Message-Id: <hg.81c297828fd2d5afaadf2775a6a71b74143b6451dfaac09fac939e9107a50d01@example.com> | |||
|
42 | To: baz@example.com | |||
|
43 | ||||
|
44 | changeset 1ea73414a91b in $TESTTMP/b | |||
|
45 | details: $TESTTMP/b?cmd=changeset;node=1ea73414a91b | |||
|
46 | description: | |||
|
47 | r0 | |||
|
48 | (run 'hg update' to get a working copy) | |||
|
49 | $ hg --cwd a phase --public 0 | |||
|
50 | $ hg --cwd b pull ../a | "$PYTHON" $TESTDIR/unwrap-message-id.py | |||
|
51 | pulling from ../a | |||
|
52 | searching for changes | |||
|
53 | no changes found | |||
|
54 | 1 local changesets published | |||
|
55 | Subject: changeset published | |||
|
56 | In-reply-to: <hg.81c297828fd2d5afaadf2775a6a71b74143b6451dfaac09fac939e9107a50d01@example.com> | |||
|
57 | Message-Id: <hg.2ec19bbddee5b542442bf5e1aed97bf706afff6aa765629883fbd1f4edd6fcb0@example.com> | |||
|
58 | Date: * (glob) | |||
|
59 | From: test@example.com | |||
|
60 | To: baz@example.com | |||
|
61 | ||||
|
62 | This changeset has been published. | |||
|
63 | $ hg --cwd b phase --force --draft 0 | |||
|
64 | $ cat <<EOF >> b/.hg/hgrc | |||
|
65 | > [notify_published] | |||
|
66 | > messageidseed = example2 | |||
|
67 | > domain = alt.example.com | |||
|
68 | > template = Subject: changeset published | |||
|
69 | > From: hg@example.com\n | |||
|
70 | > This draft changeset has been published.\n | |||
|
71 | > EOF | |||
|
72 | $ hg --cwd b pull ../a | "$PYTHON" $TESTDIR/unwrap-message-id.py | |||
|
73 | pulling from ../a | |||
|
74 | searching for changes | |||
|
75 | no changes found | |||
|
76 | 1 local changesets published | |||
|
77 | Subject: changeset published | |||
|
78 | From: hg@example.com | |||
|
79 | In-reply-to: <hg.e3381dc41c051215e50b1c166a72949d0fff99609eb373420bcb763af80ef230@alt.example.com> | |||
|
80 | Message-Id: <hg.c927f3d324e645a4245bfed20b0efb5b9582999d6be9bef45a37e7ec21208b24@alt.example.com> | |||
|
81 | Date: * (glob) | |||
|
82 | To: baz@example.com | |||
|
83 | ||||
|
84 | This draft changeset has been published. |
@@ -0,0 +1,45 b'' | |||||
|
1 | $ cat <<EOF >> $HGRCPATH | |||
|
2 | > [extensions] | |||
|
3 | > hooklib = | |||
|
4 | > | |||
|
5 | > [phases] | |||
|
6 | > publish = False | |||
|
7 | > EOF | |||
|
8 | $ hg init a | |||
|
9 | $ hg --cwd a debugbuilddag . | |||
|
10 | $ hg --cwd a phase --public 0 | |||
|
11 | $ hg init b | |||
|
12 | $ cat <<EOF >> b/.hg/hgrc | |||
|
13 | > [hooks] | |||
|
14 | > pretxnclose-phase.enforce_draft_commits = \ | |||
|
15 | > python:hgext.hooklib.enforce_draft_commits.hook | |||
|
16 | > EOF | |||
|
17 | $ hg --cwd b pull ../a | |||
|
18 | pulling from ../a | |||
|
19 | requesting all changes | |||
|
20 | adding changesets | |||
|
21 | adding manifests | |||
|
22 | adding file changes | |||
|
23 | error: pretxnclose-phase.enforce_draft_commits hook failed: New changeset 1ea73414a91b in phase 'public' rejected | |||
|
24 | transaction abort! | |||
|
25 | rollback completed | |||
|
26 | abort: New changeset 1ea73414a91b in phase 'public' rejected | |||
|
27 | [255] | |||
|
28 | $ hg --cwd a phase --force --draft 0 | |||
|
29 | $ hg --cwd b pull ../a | |||
|
30 | pulling from ../a | |||
|
31 | requesting all changes | |||
|
32 | adding changesets | |||
|
33 | adding manifests | |||
|
34 | adding file changes | |||
|
35 | added 1 changesets with 0 changes to 0 files | |||
|
36 | new changesets 1ea73414a91b (1 drafts) | |||
|
37 | (run 'hg update' to get a working copy) | |||
|
38 | $ hg --cwd a phase --public 0 | |||
|
39 | $ hg --cwd b pull ../a | |||
|
40 | pulling from ../a | |||
|
41 | searching for changes | |||
|
42 | no changes found | |||
|
43 | error: pretxnclose-phase.enforce_draft_commits hook failed: Phase change from 'draft' to 'public' for 1ea73414a91b rejected | |||
|
44 | abort: Phase change from 'draft' to 'public' for 1ea73414a91b rejected | |||
|
45 | [255] |
@@ -0,0 +1,78 b'' | |||||
|
1 | $ cat <<EOF >> $HGRCPATH | |||
|
2 | > [extensions] | |||
|
3 | > hooklib = | |||
|
4 | > | |||
|
5 | > [phases] | |||
|
6 | > publish = False | |||
|
7 | > EOF | |||
|
8 | $ hg init a | |||
|
9 | $ hg --cwd a debugbuilddag '.:parent.:childa*parent/childa<parent@otherbranch./childa' | |||
|
10 | $ hg --cwd a log -G | |||
|
11 | o changeset: 4:a9fb040caedd | |||
|
12 | |\ branch: otherbranch | |||
|
13 | | | tag: tip | |||
|
14 | | | parent: 3:af739dfc49b4 | |||
|
15 | | | parent: 1:66f7d451a68b | |||
|
16 | | | user: debugbuilddag | |||
|
17 | | | date: Thu Jan 01 00:00:04 1970 +0000 | |||
|
18 | | | summary: r4 | |||
|
19 | | | | |||
|
20 | | o changeset: 3:af739dfc49b4 | |||
|
21 | | | branch: otherbranch | |||
|
22 | | | parent: 0:1ea73414a91b | |||
|
23 | | | user: debugbuilddag | |||
|
24 | | | date: Thu Jan 01 00:00:03 1970 +0000 | |||
|
25 | | | summary: r3 | |||
|
26 | | | | |||
|
27 | +---o changeset: 2:a6b287721c3b | |||
|
28 | | |/ parent: 0:1ea73414a91b | |||
|
29 | | | parent: 1:66f7d451a68b | |||
|
30 | | | user: debugbuilddag | |||
|
31 | | | date: Thu Jan 01 00:00:02 1970 +0000 | |||
|
32 | | | summary: r2 | |||
|
33 | | | | |||
|
34 | o | changeset: 1:66f7d451a68b | |||
|
35 | |/ tag: childa | |||
|
36 | | user: debugbuilddag | |||
|
37 | | date: Thu Jan 01 00:00:01 1970 +0000 | |||
|
38 | | summary: r1 | |||
|
39 | | | |||
|
40 | o changeset: 0:1ea73414a91b | |||
|
41 | tag: parent | |||
|
42 | user: debugbuilddag | |||
|
43 | date: Thu Jan 01 00:00:00 1970 +0000 | |||
|
44 | summary: r0 | |||
|
45 | ||||
|
46 | $ hg init b | |||
|
47 | $ cat <<EOF >> b/.hg/hgrc | |||
|
48 | > [hooks] | |||
|
49 | > pretxnchangegroup.reject_merge_commits = \ | |||
|
50 | > python:hgext.hooklib.reject_merge_commits.hook | |||
|
51 | > EOF | |||
|
52 | $ hg --cwd b pull ../a -r a6b287721c3b | |||
|
53 | pulling from ../a | |||
|
54 | adding changesets | |||
|
55 | adding manifests | |||
|
56 | adding file changes | |||
|
57 | error: pretxnchangegroup.reject_merge_commits hook failed: a6b287721c3b rejected as merge on the same branch. Please consider rebase. | |||
|
58 | transaction abort! | |||
|
59 | rollback completed | |||
|
60 | abort: a6b287721c3b rejected as merge on the same branch. Please consider rebase. | |||
|
61 | [255] | |||
|
62 | $ hg --cwd b pull ../a -r 1ea73414a91b | |||
|
63 | pulling from ../a | |||
|
64 | adding changesets | |||
|
65 | adding manifests | |||
|
66 | adding file changes | |||
|
67 | added 1 changesets with 0 changes to 0 files | |||
|
68 | new changesets 1ea73414a91b (1 drafts) | |||
|
69 | (run 'hg update' to get a working copy) | |||
|
70 | $ hg --cwd b pull ../a -r a9fb040caedd | |||
|
71 | pulling from ../a | |||
|
72 | searching for changes | |||
|
73 | adding changesets | |||
|
74 | adding manifests | |||
|
75 | adding file changes | |||
|
76 | added 3 changesets with 0 changes to 0 files | |||
|
77 | new changesets 66f7d451a68b:a9fb040caedd (3 drafts) | |||
|
78 | (run 'hg update' to get a working copy) |
@@ -0,0 +1,53 b'' | |||||
|
1 | $ cat <<EOF >> $HGRCPATH | |||
|
2 | > [extensions] | |||
|
3 | > hooklib = | |||
|
4 | > | |||
|
5 | > [phases] | |||
|
6 | > publish = False | |||
|
7 | > EOF | |||
|
8 | $ hg init a | |||
|
9 | $ hg --cwd a debugbuilddag '.:parent.*parent' | |||
|
10 | $ hg --cwd a log -G | |||
|
11 | o changeset: 2:fa942426a6fd | |||
|
12 | | tag: tip | |||
|
13 | | parent: 0:1ea73414a91b | |||
|
14 | | user: debugbuilddag | |||
|
15 | | date: Thu Jan 01 00:00:02 1970 +0000 | |||
|
16 | | summary: r2 | |||
|
17 | | | |||
|
18 | | o changeset: 1:66f7d451a68b | |||
|
19 | |/ user: debugbuilddag | |||
|
20 | | date: Thu Jan 01 00:00:01 1970 +0000 | |||
|
21 | | summary: r1 | |||
|
22 | | | |||
|
23 | o changeset: 0:1ea73414a91b | |||
|
24 | tag: parent | |||
|
25 | user: debugbuilddag | |||
|
26 | date: Thu Jan 01 00:00:00 1970 +0000 | |||
|
27 | summary: r0 | |||
|
28 | ||||
|
29 | $ hg init b | |||
|
30 | $ cat <<EOF >> b/.hg/hgrc | |||
|
31 | > [hooks] | |||
|
32 | > pretxnclose.reject_new_heads = \ | |||
|
33 | > python:hgext.hooklib.reject_new_heads.hook | |||
|
34 | > EOF | |||
|
35 | $ hg --cwd b pull ../a | |||
|
36 | pulling from ../a | |||
|
37 | requesting all changes | |||
|
38 | adding changesets | |||
|
39 | adding manifests | |||
|
40 | adding file changes | |||
|
41 | error: pretxnclose.reject_new_heads hook failed: Changes on branch 'default' resulted in multiple heads | |||
|
42 | transaction abort! | |||
|
43 | rollback completed | |||
|
44 | abort: Changes on branch 'default' resulted in multiple heads | |||
|
45 | [255] | |||
|
46 | $ hg --cwd b pull ../a -r 1ea73414a91b | |||
|
47 | pulling from ../a | |||
|
48 | adding changesets | |||
|
49 | adding manifests | |||
|
50 | adding file changes | |||
|
51 | added 1 changesets with 0 changes to 0 files | |||
|
52 | new changesets 1ea73414a91b (1 drafts) | |||
|
53 | (run 'hg update' to get a working copy) |
@@ -392,9 +392,10 b' def imported_modules(source, modulename,' | |||||
392 | modnotfound = True |
|
392 | modnotfound = True | |
393 | continue |
|
393 | continue | |
394 | yield found[1] |
|
394 | yield found[1] | |
395 | if modnotfound: |
|
395 | if modnotfound and dottedpath != modulename: | |
396 | # "dottedpath" is a package, but imported because of non-module |
|
396 | # "dottedpath" is a package, but imported because of non-module | |
397 | # lookup |
|
397 | # lookup | |
|
398 | # specifically allow "from . import foo" from __init__.py | |||
398 | yield dottedpath |
|
399 | yield dottedpath | |
399 |
|
400 | |||
400 |
|
401 |
@@ -1210,6 +1210,7 b' packages = [' | |||||
1210 | 'hgext.fastannotate', |
|
1210 | 'hgext.fastannotate', | |
1211 | 'hgext.fsmonitor.pywatchman', |
|
1211 | 'hgext.fsmonitor.pywatchman', | |
1212 | 'hgext.highlight', |
|
1212 | 'hgext.highlight', | |
|
1213 | 'hgext.hooklib', | |||
1213 | 'hgext.infinitepush', |
|
1214 | 'hgext.infinitepush', | |
1214 | 'hgext.largefiles', |
|
1215 | 'hgext.largefiles', | |
1215 | 'hgext.lfs', |
|
1216 | 'hgext.lfs', |
General Comments 0
You need to be logged in to leave comments.
Login now