##// END OF EJS Templates
hgext: start building a library for simple hooks...
Joerg Sonnenberger -
r44897:4cabeea6 default
parent child Browse files
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