##// END OF EJS Templates
hook: report untrusted hooks as failure (issue5110) (BC)...
Pierre-Yves David -
r28938:ea1fec3e default
parent child Browse files
Show More
@@ -1,250 +1,271 b''
1 1 # hook.py - hook support for mercurial
2 2 #
3 3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import os
11 11 import sys
12 12 import time
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 demandimport,
17 17 error,
18 18 extensions,
19 19 util,
20 20 )
21 21
22 22 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
23 23 '''call python hook. hook is callable object, looked up as
24 24 name in python module. if callable returns "true", hook
25 25 fails, else passes. if hook raises exception, treated as
26 26 hook failure. exception propagates if throw is "true".
27 27
28 28 reason for "true" meaning "hook failed" is so that
29 29 unmodified commands (e.g. mercurial.commands.update) can
30 30 be run as hooks without wrappers to convert return values.'''
31 31
32 32 if callable(funcname):
33 33 obj = funcname
34 34 funcname = obj.__module__ + "." + obj.__name__
35 35 else:
36 36 d = funcname.rfind('.')
37 37 if d == -1:
38 38 raise error.HookLoadError(
39 39 _('%s hook is invalid: "%s" not in a module')
40 40 % (hname, funcname))
41 41 modname = funcname[:d]
42 42 oldpaths = sys.path
43 43 if util.mainfrozen():
44 44 # binary installs require sys.path manipulation
45 45 modpath, modfile = os.path.split(modname)
46 46 if modpath and modfile:
47 47 sys.path = sys.path[:] + [modpath]
48 48 modname = modfile
49 49 with demandimport.deactivated():
50 50 try:
51 51 obj = __import__(modname)
52 52 except (ImportError, SyntaxError):
53 53 e1 = sys.exc_info()
54 54 try:
55 55 # extensions are loaded with hgext_ prefix
56 56 obj = __import__("hgext_%s" % modname)
57 57 except (ImportError, SyntaxError):
58 58 e2 = sys.exc_info()
59 59 if ui.tracebackflag:
60 60 ui.warn(_('exception from first failed import '
61 61 'attempt:\n'))
62 62 ui.traceback(e1)
63 63 if ui.tracebackflag:
64 64 ui.warn(_('exception from second failed import '
65 65 'attempt:\n'))
66 66 ui.traceback(e2)
67 67
68 68 if not ui.tracebackflag:
69 69 tracebackhint = _(
70 70 'run with --traceback for stack trace')
71 71 else:
72 72 tracebackhint = None
73 73 raise error.HookLoadError(
74 74 _('%s hook is invalid: import of "%s" failed') %
75 75 (hname, modname), hint=tracebackhint)
76 76 sys.path = oldpaths
77 77 try:
78 78 for p in funcname.split('.')[1:]:
79 79 obj = getattr(obj, p)
80 80 except AttributeError:
81 81 raise error.HookLoadError(
82 82 _('%s hook is invalid: "%s" is not defined')
83 83 % (hname, funcname))
84 84 if not callable(obj):
85 85 raise error.HookLoadError(
86 86 _('%s hook is invalid: "%s" is not callable')
87 87 % (hname, funcname))
88 88
89 89 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
90 90 starttime = time.time()
91 91
92 92 try:
93 93 # redirect IO descriptors to the ui descriptors so hooks
94 94 # that write directly to these don't mess up the command
95 95 # protocol when running through the command server
96 96 old = sys.stdout, sys.stderr, sys.stdin
97 97 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
98 98
99 99 r = obj(ui=ui, repo=repo, hooktype=name, **args)
100 100 except Exception as exc:
101 101 if isinstance(exc, error.Abort):
102 102 ui.warn(_('error: %s hook failed: %s\n') %
103 103 (hname, exc.args[0]))
104 104 else:
105 105 ui.warn(_('error: %s hook raised an exception: '
106 106 '%s\n') % (hname, exc))
107 107 if throw:
108 108 raise
109 109 if not ui.tracebackflag:
110 110 ui.warn(_('(run with --traceback for stack trace)\n'))
111 111 ui.traceback()
112 112 return True, True
113 113 finally:
114 114 sys.stdout, sys.stderr, sys.stdin = old
115 115 duration = time.time() - starttime
116 116 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
117 117 name, funcname, duration)
118 118 if r:
119 119 if throw:
120 120 raise error.HookAbort(_('%s hook failed') % hname)
121 121 ui.warn(_('warning: %s hook failed\n') % hname)
122 122 return r, False
123 123
124 124 def _exthook(ui, repo, name, cmd, args, throw):
125 125 ui.note(_("running hook %s: %s\n") % (name, cmd))
126 126
127 127 starttime = time.time()
128 128 env = {}
129 129
130 130 # make in-memory changes visible to external process
131 131 if repo is not None:
132 132 tr = repo.currenttransaction()
133 133 repo.dirstate.write(tr)
134 134 if tr and tr.writepending():
135 135 env['HG_PENDING'] = repo.root
136 136
137 137 for k, v in args.iteritems():
138 138 if callable(v):
139 139 v = v()
140 140 if isinstance(v, dict):
141 141 # make the dictionary element order stable across Python
142 142 # implementations
143 143 v = ('{' +
144 144 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
145 145 '}')
146 146 env['HG_' + k.upper()] = v
147 147
148 148 if repo:
149 149 cwd = repo.root
150 150 else:
151 151 cwd = os.getcwd()
152 152 r = ui.system(cmd, environ=env, cwd=cwd)
153 153
154 154 duration = time.time() - starttime
155 155 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
156 156 name, cmd, duration)
157 157 if r:
158 158 desc, r = util.explainexit(r)
159 159 if throw:
160 160 raise error.HookAbort(_('%s hook %s') % (name, desc))
161 161 ui.warn(_('warning: %s hook %s\n') % (name, desc))
162 162 return r
163 163
164 # represent an untrusted hook command
165 _fromuntrusted = object()
166
164 167 def _allhooks(ui):
165 168 """return a list of (hook-id, cmd) pairs sorted by priority"""
166 169 hooks = _hookitems(ui)
170 # Be careful in this section, propagating the real commands from untrusted
171 # sources would create a security vulnerability, make sure anything altered
172 # in that section uses "_fromuntrusted" as its command.
173 untrustedhooks = _hookitems(ui, _untrusted=True)
174 for name, value in untrustedhooks.items():
175 trustedvalue = hooks.get(name, (None, None, name, _fromuntrusted))
176 if value != trustedvalue:
177 (lp, lo, lk, lv) = trustedvalue
178 hooks[name] = (lp, lo, lk, _fromuntrusted)
179 # (end of the security sensitive section)
167 180 return [(k, v) for p, o, k, v in sorted(hooks.values())]
168 181
169 def _hookitems(ui):
182 def _hookitems(ui, _untrusted=False):
170 183 """return all hooks items ready to be sorted"""
171 184 hooks = {}
172 for name, cmd in ui.configitems('hooks'):
185 for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
173 186 if not name.startswith('priority'):
174 187 priority = ui.configint('hooks', 'priority.%s' % name, 0)
175 188 hooks[name] = (-priority, len(hooks), name, cmd)
176 189 return hooks
177 190
178 191 _redirect = False
179 192 def redirect(state):
180 193 global _redirect
181 194 _redirect = state
182 195
183 196 def hook(ui, repo, name, throw=False, **args):
184 197 if not ui.callhooks:
185 198 return False
186 199
187 200 hooks = []
188 201 for hname, cmd in _allhooks(ui):
189 202 if hname.split('.')[0] == name and cmd:
190 203 hooks.append((hname, cmd))
191 204
192 205 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
193 206 r = False
194 207 for hname, cmd in hooks:
195 208 r = res[hname][0] or r
196 209 return r
197 210
198 211 def runhooks(ui, repo, name, hooks, throw=False, **args):
199 212 res = {}
200 213 oldstdout = -1
201 214
202 215 try:
203 216 for hname, cmd in hooks:
204 217 if oldstdout == -1 and _redirect:
205 218 try:
206 219 stdoutno = sys.__stdout__.fileno()
207 220 stderrno = sys.__stderr__.fileno()
208 221 # temporarily redirect stdout to stderr, if possible
209 222 if stdoutno >= 0 and stderrno >= 0:
210 223 sys.__stdout__.flush()
211 224 oldstdout = os.dup(stdoutno)
212 225 os.dup2(stderrno, stdoutno)
213 226 except (OSError, AttributeError):
214 227 # files seem to be bogus, give up on redirecting (WSGI, etc)
215 228 pass
216 229
217 if callable(cmd):
230 if cmd is _fromuntrusted:
231 if throw:
232 raise error.HookAbort(
233 _('untrusted hook %s not executed') % name,
234 hint = _("see 'hg help config.trusted'"))
235 ui.warn(_('warning: untrusted hook %s not executed\n') % name)
236 r = 1
237 raised = False
238 elif callable(cmd):
218 239 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
219 240 elif cmd.startswith('python:'):
220 241 if cmd.count(':') >= 2:
221 242 path, cmd = cmd[7:].rsplit(':', 1)
222 243 path = util.expandpath(path)
223 244 if repo:
224 245 path = os.path.join(repo.root, path)
225 246 try:
226 247 mod = extensions.loadpath(path, 'hghook.%s' % hname)
227 248 except Exception:
228 249 ui.write(_("loading %s hook failed:\n") % hname)
229 250 raise
230 251 hookfn = getattr(mod, cmd)
231 252 else:
232 253 hookfn = cmd[7:].strip()
233 254 r, raised = _pythonhook(ui, repo, name, hname, hookfn, args,
234 255 throw)
235 256 else:
236 257 r = _exthook(ui, repo, hname, cmd, args, throw)
237 258 raised = False
238 259
239 260 res[hname] = r, raised
240 261
241 262 # The stderr is fully buffered on Windows when connected to a pipe.
242 263 # A forcible flush is required to make small stderr data in the
243 264 # remote side available to the client immediately.
244 265 sys.stderr.flush()
245 266 finally:
246 267 if _redirect and oldstdout >= 0:
247 268 os.dup2(oldstdout, stdoutno)
248 269 os.close(oldstdout)
249 270
250 271 return res
@@ -1,818 +1,876 b''
1 1 commit hooks can see env vars
2 2 (and post-transaction one are run unlocked)
3 3
4 4 $ cat << EOF >> $HGRCPATH
5 5 > [experimental]
6 6 > # drop me once bundle2 is the default,
7 7 > # added to get test change early.
8 8 > bundle2-exp = True
9 9 > EOF
10 10
11 11 $ cat > $TESTTMP/txnabort.checkargs.py <<EOF
12 12 > def showargs(ui, repo, hooktype, **kwargs):
13 13 > ui.write('%s python hook: %s\n' % (hooktype, ','.join(sorted(kwargs))))
14 14 > EOF
15 15
16 16 $ hg init a
17 17 $ cd a
18 18 $ cat > .hg/hgrc <<EOF
19 19 > [hooks]
20 20 > commit = sh -c "HG_LOCAL= HG_TAG= printenv.py commit"
21 21 > commit.b = sh -c "HG_LOCAL= HG_TAG= printenv.py commit.b"
22 22 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= printenv.py precommit"
23 23 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxncommit"
24 24 > pretxncommit.tip = hg -q tip
25 25 > pre-identify = printenv.py pre-identify 1
26 26 > pre-cat = printenv.py pre-cat
27 27 > post-cat = printenv.py post-cat
28 28 > pretxnopen = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnopen"
29 29 > pretxnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnclose"
30 30 > txnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py txnclose"
31 31 > txnabort.0 = python:$TESTTMP/txnabort.checkargs.py:showargs
32 32 > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= printenv.py txnabort"
33 33 > txnclose.checklock = sh -c "hg debuglock > /dev/null"
34 34 > EOF
35 35 $ echo a > a
36 36 $ hg add a
37 37 $ hg commit -m a
38 38 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
39 39 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
40 40 pretxncommit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
41 41 0:cb9a9f314b8b
42 42 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
43 43 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
44 44 commit hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
45 45 commit.b hook: HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
46 46
47 47 $ hg clone . ../b
48 48 updating to branch default
49 49 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 50 $ cd ../b
51 51
52 52 changegroup hooks can see env vars
53 53
54 54 $ cat > .hg/hgrc <<EOF
55 55 > [hooks]
56 56 > prechangegroup = printenv.py prechangegroup
57 57 > changegroup = printenv.py changegroup
58 58 > incoming = printenv.py incoming
59 59 > EOF
60 60
61 61 pretxncommit and commit hooks can see both parents of merge
62 62
63 63 $ cd ../a
64 64 $ echo b >> a
65 65 $ hg commit -m a1 -d "1 0"
66 66 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
67 67 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
68 68 pretxncommit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
69 69 1:ab228980c14d
70 70 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
71 71 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
72 72 commit hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
73 73 commit.b hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
74 74 $ hg update -C 0
75 75 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 76 $ echo b > b
77 77 $ hg add b
78 78 $ hg commit -m b -d '1 0'
79 79 precommit hook: HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
80 80 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
81 81 pretxncommit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
82 82 2:ee9deb46ab31
83 83 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
84 84 created new head
85 85 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
86 86 commit hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
87 87 commit.b hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
88 88 $ hg merge 1
89 89 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 90 (branch merge, don't forget to commit)
91 91 $ hg commit -m merge -d '2 0'
92 92 precommit hook: HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
93 93 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
94 94 pretxncommit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
95 95 3:07f3376c1e65
96 96 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
97 97 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
98 98 commit hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
99 99 commit.b hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
100 100
101 101 test generic hooks
102 102
103 103 $ hg id
104 104 pre-identify hook: HG_ARGS=id HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'insecure': None, 'num': None, 'remotecmd': '', 'rev': '', 'ssh': '', 'tags': None} HG_PATS=[]
105 105 abort: pre-identify hook exited with status 1
106 106 [255]
107 107 $ hg cat b
108 108 pre-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b']
109 109 b
110 110 post-cat hook: HG_ARGS=cat b HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0
111 111
112 112 $ cd ../b
113 113 $ hg pull ../a
114 114 pulling from ../a
115 115 searching for changes
116 116 prechangegroup hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
117 117 adding changesets
118 118 adding manifests
119 119 adding file changes
120 120 added 3 changesets with 2 changes to 2 files
121 121 changegroup hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_NODE_LAST=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
122 122 incoming hook: HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
123 123 incoming hook: HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
124 124 incoming hook: HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
125 125 (run 'hg update' to get a working copy)
126 126
127 127 tag hooks can see env vars
128 128
129 129 $ cd ../a
130 130 $ cat >> .hg/hgrc <<EOF
131 131 > pretag = printenv.py pretag
132 132 > tag = sh -c "HG_PARENT1= HG_PARENT2= printenv.py tag"
133 133 > EOF
134 134 $ hg tag -d '3 0' a
135 135 pretag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
136 136 precommit hook: HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
137 137 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
138 138 pretxncommit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
139 139 4:539e4b31b6dc
140 140 pretxnclose hook: HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
141 141 tag hook: HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
142 142 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
143 143 commit hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
144 144 commit.b hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
145 145 $ hg tag -l la
146 146 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
147 147 tag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
148 148
149 149 pretag hook can forbid tagging
150 150
151 151 $ echo "pretag.forbid = printenv.py pretag.forbid 1" >> .hg/hgrc
152 152 $ hg tag -d '4 0' fa
153 153 pretag hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
154 154 pretag.forbid hook: HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
155 155 abort: pretag.forbid hook exited with status 1
156 156 [255]
157 157 $ hg tag -l fla
158 158 pretag hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
159 159 pretag.forbid hook: HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
160 160 abort: pretag.forbid hook exited with status 1
161 161 [255]
162 162
163 163 pretxncommit hook can see changeset, can roll back txn, changeset no
164 164 more there after
165 165
166 166 $ echo "pretxncommit.forbid0 = hg tip -q" >> .hg/hgrc
167 167 $ echo "pretxncommit.forbid1 = printenv.py pretxncommit.forbid 1" >> .hg/hgrc
168 168 $ echo z > z
169 169 $ hg add z
170 170 $ hg -q tip
171 171 4:539e4b31b6dc
172 172 $ hg commit -m 'fail' -d '4 0'
173 173 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
174 174 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
175 175 pretxncommit hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
176 176 5:6f611f8018c1
177 177 5:6f611f8018c1
178 178 pretxncommit.forbid hook: HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
179 179 transaction abort!
180 180 txnabort python hook: txnid,txnname
181 181 txnabort hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
182 182 rollback completed
183 183 abort: pretxncommit.forbid1 hook exited with status 1
184 184 [255]
185 185 $ hg -q tip
186 186 4:539e4b31b6dc
187 187
188 188 (Check that no 'changelog.i.a' file were left behind)
189 189
190 190 $ ls -1 .hg/store/
191 191 00changelog.i
192 192 00manifest.i
193 193 data
194 194 fncache
195 195 journal.phaseroots
196 196 phaseroots
197 197 undo
198 198 undo.backup.fncache
199 199 undo.backupfiles
200 200 undo.phaseroots
201 201
202 202
203 203 precommit hook can prevent commit
204 204
205 205 $ echo "precommit.forbid = printenv.py precommit.forbid 1" >> .hg/hgrc
206 206 $ hg commit -m 'fail' -d '4 0'
207 207 precommit hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
208 208 precommit.forbid hook: HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
209 209 abort: precommit.forbid hook exited with status 1
210 210 [255]
211 211 $ hg -q tip
212 212 4:539e4b31b6dc
213 213
214 214 preupdate hook can prevent update
215 215
216 216 $ echo "preupdate = printenv.py preupdate" >> .hg/hgrc
217 217 $ hg update 1
218 218 preupdate hook: HG_PARENT1=ab228980c14d
219 219 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
220 220
221 221 update hook
222 222
223 223 $ echo "update = printenv.py update" >> .hg/hgrc
224 224 $ hg update
225 225 preupdate hook: HG_PARENT1=539e4b31b6dc
226 226 update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc
227 227 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 228
229 229 pushkey hook
230 230
231 231 $ echo "pushkey = printenv.py pushkey" >> .hg/hgrc
232 232 $ cd ../b
233 233 $ hg bookmark -r null foo
234 234 $ hg push -B foo ../a
235 235 pushing to ../a
236 236 searching for changes
237 237 no changes found
238 238 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=push (glob)
239 239 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_PENDING=$TESTTMP/a HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob)
240 240 pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
241 241 txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob)
242 242 exporting bookmark foo
243 243 [1]
244 244 $ cd ../a
245 245
246 246 listkeys hook
247 247
248 248 $ echo "listkeys = printenv.py listkeys" >> .hg/hgrc
249 249 $ hg bookmark -r null bar
250 250 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
251 251 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
252 252 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
253 253 $ cd ../b
254 254 $ hg pull -B bar ../a
255 255 pulling from ../a
256 256 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
257 257 no changes found
258 258 listkeys hook: HG_NAMESPACE=phase HG_VALUES={}
259 259 adding remote bookmark bar
260 260 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
261 261 $ cd ../a
262 262
263 263 test that prepushkey can prevent incoming keys
264 264
265 265 $ echo "prepushkey = printenv.py prepushkey.forbid 1" >> .hg/hgrc
266 266 $ cd ../b
267 267 $ hg bookmark -r null baz
268 268 $ hg push -B baz ../a
269 269 pushing to ../a
270 270 searching for changes
271 271 listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
272 272 listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
273 273 no changes found
274 274 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=push (glob)
275 275 prepushkey.forbid hook: HG_BUNDLE2=1 HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_SOURCE=push HG_TXNID=TXN:* HG_URL=push (glob)
276 276 pushkey-abort: prepushkey hook exited with status 1
277 277 abort: exporting bookmark baz failed!
278 278 [255]
279 279 $ cd ../a
280 280
281 281 test that prelistkeys can prevent listing keys
282 282
283 283 $ echo "prelistkeys = printenv.py prelistkeys.forbid 1" >> .hg/hgrc
284 284 $ hg bookmark -r null quux
285 285 pretxnopen hook: HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
286 286 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_PENDING=$TESTTMP/a HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
287 287 txnclose hook: HG_BOOKMARK_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=bookmark (glob)
288 288 $ cd ../b
289 289 $ hg pull -B quux ../a
290 290 pulling from ../a
291 291 prelistkeys.forbid hook: HG_NAMESPACE=bookmarks
292 292 abort: prelistkeys hook exited with status 1
293 293 [255]
294 294 $ cd ../a
295 295 $ rm .hg/hgrc
296 296
297 297 prechangegroup hook can prevent incoming changes
298 298
299 299 $ cd ../b
300 300 $ hg -q tip
301 301 3:07f3376c1e65
302 302 $ cat > .hg/hgrc <<EOF
303 303 > [hooks]
304 304 > prechangegroup.forbid = printenv.py prechangegroup.forbid 1
305 305 > EOF
306 306 $ hg pull ../a
307 307 pulling from ../a
308 308 searching for changes
309 309 prechangegroup.forbid hook: HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
310 310 abort: prechangegroup.forbid hook exited with status 1
311 311 [255]
312 312
313 313 pretxnchangegroup hook can see incoming changes, can roll back txn,
314 314 incoming changes no longer there after
315 315
316 316 $ cat > .hg/hgrc <<EOF
317 317 > [hooks]
318 318 > pretxnchangegroup.forbid0 = hg tip -q
319 319 > pretxnchangegroup.forbid1 = printenv.py pretxnchangegroup.forbid 1
320 320 > EOF
321 321 $ hg pull ../a
322 322 pulling from ../a
323 323 searching for changes
324 324 adding changesets
325 325 adding manifests
326 326 adding file changes
327 327 added 1 changesets with 1 changes to 1 files
328 328 4:539e4b31b6dc
329 329 pretxnchangegroup.forbid hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_NODE_LAST=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:* HG_URL=file:$TESTTMP/a (glob)
330 330 transaction abort!
331 331 rollback completed
332 332 abort: pretxnchangegroup.forbid1 hook exited with status 1
333 333 [255]
334 334 $ hg -q tip
335 335 3:07f3376c1e65
336 336
337 337 outgoing hooks can see env vars
338 338
339 339 $ rm .hg/hgrc
340 340 $ cat > ../a/.hg/hgrc <<EOF
341 341 > [hooks]
342 342 > preoutgoing = printenv.py preoutgoing
343 343 > outgoing = printenv.py outgoing
344 344 > EOF
345 345 $ hg pull ../a
346 346 pulling from ../a
347 347 searching for changes
348 348 preoutgoing hook: HG_SOURCE=pull
349 349 outgoing hook: HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
350 350 adding changesets
351 351 adding manifests
352 352 adding file changes
353 353 added 1 changesets with 1 changes to 1 files
354 354 adding remote bookmark quux
355 355 (run 'hg update' to get a working copy)
356 356 $ hg rollback
357 357 repository tip rolled back to revision 3 (undo pull)
358 358
359 359 preoutgoing hook can prevent outgoing changes
360 360
361 361 $ echo "preoutgoing.forbid = printenv.py preoutgoing.forbid 1" >> ../a/.hg/hgrc
362 362 $ hg pull ../a
363 363 pulling from ../a
364 364 searching for changes
365 365 preoutgoing hook: HG_SOURCE=pull
366 366 preoutgoing.forbid hook: HG_SOURCE=pull
367 367 abort: preoutgoing.forbid hook exited with status 1
368 368 [255]
369 369
370 370 outgoing hooks work for local clones
371 371
372 372 $ cd ..
373 373 $ cat > a/.hg/hgrc <<EOF
374 374 > [hooks]
375 375 > preoutgoing = printenv.py preoutgoing
376 376 > outgoing = printenv.py outgoing
377 377 > EOF
378 378 $ hg clone a c
379 379 preoutgoing hook: HG_SOURCE=clone
380 380 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
381 381 updating to branch default
382 382 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
383 383 $ rm -rf c
384 384
385 385 preoutgoing hook can prevent outgoing changes for local clones
386 386
387 387 $ echo "preoutgoing.forbid = printenv.py preoutgoing.forbid 1" >> a/.hg/hgrc
388 388 $ hg clone a zzz
389 389 preoutgoing hook: HG_SOURCE=clone
390 390 preoutgoing.forbid hook: HG_SOURCE=clone
391 391 abort: preoutgoing.forbid hook exited with status 1
392 392 [255]
393 393
394 394 $ cd "$TESTTMP/b"
395 395
396 396 $ cat > hooktests.py <<EOF
397 397 > from mercurial import error
398 398 >
399 399 > uncallable = 0
400 400 >
401 401 > def printargs(args):
402 402 > args.pop('ui', None)
403 403 > args.pop('repo', None)
404 404 > a = list(args.items())
405 405 > a.sort()
406 406 > print 'hook args:'
407 407 > for k, v in a:
408 408 > print ' ', k, v
409 409 >
410 410 > def passhook(**args):
411 411 > printargs(args)
412 412 >
413 413 > def failhook(**args):
414 414 > printargs(args)
415 415 > return True
416 416 >
417 417 > class LocalException(Exception):
418 418 > pass
419 419 >
420 420 > def raisehook(**args):
421 421 > raise LocalException('exception from hook')
422 422 >
423 423 > def aborthook(**args):
424 424 > raise error.Abort('raise abort from hook')
425 425 >
426 426 > def brokenhook(**args):
427 427 > return 1 + {}
428 428 >
429 429 > def verbosehook(ui, **args):
430 430 > ui.note('verbose output from hook\n')
431 431 >
432 432 > def printtags(ui, repo, **args):
433 433 > print sorted(repo.tags())
434 434 >
435 435 > class container:
436 436 > unreachable = 1
437 437 > EOF
438 438
439 439 $ cat > syntaxerror.py << EOF
440 440 > (foo
441 441 > EOF
442 442
443 443 test python hooks
444 444
445 445 #if windows
446 446 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
447 447 #else
448 448 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
449 449 #endif
450 450 $ export PYTHONPATH
451 451
452 452 $ echo '[hooks]' > ../a/.hg/hgrc
453 453 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
454 454 $ hg pull ../a 2>&1 | grep 'raised an exception'
455 455 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
456 456
457 457 $ echo '[hooks]' > ../a/.hg/hgrc
458 458 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
459 459 $ hg pull ../a 2>&1 | grep 'raised an exception'
460 460 error: preoutgoing.raise hook raised an exception: exception from hook
461 461
462 462 $ echo '[hooks]' > ../a/.hg/hgrc
463 463 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
464 464 $ hg pull ../a
465 465 pulling from ../a
466 466 searching for changes
467 467 error: preoutgoing.abort hook failed: raise abort from hook
468 468 abort: raise abort from hook
469 469 [255]
470 470
471 471 $ echo '[hooks]' > ../a/.hg/hgrc
472 472 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
473 473 $ hg pull ../a
474 474 pulling from ../a
475 475 searching for changes
476 476 hook args:
477 477 hooktype preoutgoing
478 478 source pull
479 479 abort: preoutgoing.fail hook failed
480 480 [255]
481 481
482 482 $ echo '[hooks]' > ../a/.hg/hgrc
483 483 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
484 484 $ hg pull ../a
485 485 pulling from ../a
486 486 searching for changes
487 487 abort: preoutgoing.uncallable hook is invalid: "hooktests.uncallable" is not callable
488 488 [255]
489 489
490 490 $ echo '[hooks]' > ../a/.hg/hgrc
491 491 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
492 492 $ hg pull ../a
493 493 pulling from ../a
494 494 searching for changes
495 495 abort: preoutgoing.nohook hook is invalid: "hooktests.nohook" is not defined
496 496 [255]
497 497
498 498 $ echo '[hooks]' > ../a/.hg/hgrc
499 499 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
500 500 $ hg pull ../a
501 501 pulling from ../a
502 502 searching for changes
503 503 abort: preoutgoing.nomodule hook is invalid: "nomodule" not in a module
504 504 [255]
505 505
506 506 $ echo '[hooks]' > ../a/.hg/hgrc
507 507 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
508 508 $ hg pull ../a
509 509 pulling from ../a
510 510 searching for changes
511 511 abort: preoutgoing.badmodule hook is invalid: import of "nomodule" failed
512 512 (run with --traceback for stack trace)
513 513 [255]
514 514
515 515 $ echo '[hooks]' > ../a/.hg/hgrc
516 516 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
517 517 $ hg pull ../a
518 518 pulling from ../a
519 519 searching for changes
520 520 abort: preoutgoing.unreachable hook is invalid: import of "hooktests.container" failed
521 521 (run with --traceback for stack trace)
522 522 [255]
523 523
524 524 $ echo '[hooks]' > ../a/.hg/hgrc
525 525 $ echo 'preoutgoing.syntaxerror = python:syntaxerror.syntaxerror' >> ../a/.hg/hgrc
526 526 $ hg pull ../a
527 527 pulling from ../a
528 528 searching for changes
529 529 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
530 530 (run with --traceback for stack trace)
531 531 [255]
532 532
533 533 The second egrep is to filter out lines like ' ^', which are slightly
534 534 different between Python 2.6 and Python 2.7.
535 535 $ hg pull ../a --traceback 2>&1 | egrep -v '^( +File| [_a-zA-Z*(])' | egrep -v '^( )+(\^)?$'
536 536 pulling from ../a
537 537 searching for changes
538 538 exception from first failed import attempt:
539 539 Traceback (most recent call last):
540 540 SyntaxError: * (glob)
541 541 exception from second failed import attempt:
542 542 Traceback (most recent call last):
543 543 ImportError: No module named hgext_syntaxerror
544 544 Traceback (most recent call last):
545 545 HookLoadError: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
546 546 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
547 547
548 548 $ echo '[hooks]' > ../a/.hg/hgrc
549 549 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
550 550 $ hg pull ../a
551 551 pulling from ../a
552 552 searching for changes
553 553 hook args:
554 554 hooktype preoutgoing
555 555 source pull
556 556 adding changesets
557 557 adding manifests
558 558 adding file changes
559 559 added 1 changesets with 1 changes to 1 files
560 560 adding remote bookmark quux
561 561 (run 'hg update' to get a working copy)
562 562
563 563 post- python hooks that fail to *run* don't cause an abort
564 564 $ rm ../a/.hg/hgrc
565 565 $ echo '[hooks]' > .hg/hgrc
566 566 $ echo 'post-pull.broken = python:hooktests.brokenhook' >> .hg/hgrc
567 567 $ hg pull ../a
568 568 pulling from ../a
569 569 searching for changes
570 570 no changes found
571 571 error: post-pull.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
572 572 (run with --traceback for stack trace)
573 573
574 574 but post- python hooks that fail to *load* do
575 575 $ echo '[hooks]' > .hg/hgrc
576 576 $ echo 'post-pull.nomodule = python:nomodule' >> .hg/hgrc
577 577 $ hg pull ../a
578 578 pulling from ../a
579 579 searching for changes
580 580 no changes found
581 581 abort: post-pull.nomodule hook is invalid: "nomodule" not in a module
582 582 [255]
583 583
584 584 $ echo '[hooks]' > .hg/hgrc
585 585 $ echo 'post-pull.badmodule = python:nomodule.nowhere' >> .hg/hgrc
586 586 $ hg pull ../a
587 587 pulling from ../a
588 588 searching for changes
589 589 no changes found
590 590 abort: post-pull.badmodule hook is invalid: import of "nomodule" failed
591 591 (run with --traceback for stack trace)
592 592 [255]
593 593
594 594 $ echo '[hooks]' > .hg/hgrc
595 595 $ echo 'post-pull.nohook = python:hooktests.nohook' >> .hg/hgrc
596 596 $ hg pull ../a
597 597 pulling from ../a
598 598 searching for changes
599 599 no changes found
600 600 abort: post-pull.nohook hook is invalid: "hooktests.nohook" is not defined
601 601 [255]
602 602
603 603 make sure --traceback works
604 604
605 605 $ echo '[hooks]' > .hg/hgrc
606 606 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
607 607
608 608 $ echo aa > a
609 609 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
610 610 Traceback (most recent call last):
611 611
612 612 $ cd ..
613 613 $ hg init c
614 614 $ cd c
615 615
616 616 $ cat > hookext.py <<EOF
617 617 > def autohook(**args):
618 618 > print "Automatically installed hook"
619 619 >
620 620 > def reposetup(ui, repo):
621 621 > repo.ui.setconfig("hooks", "commit.auto", autohook)
622 622 > EOF
623 623 $ echo '[extensions]' >> .hg/hgrc
624 624 $ echo 'hookext = hookext.py' >> .hg/hgrc
625 625
626 626 $ touch foo
627 627 $ hg add foo
628 628 $ hg ci -d '0 0' -m 'add foo'
629 629 Automatically installed hook
630 630 $ echo >> foo
631 631 $ hg ci --debug -d '0 0' -m 'change foo'
632 632 committing files:
633 633 foo
634 634 committing manifest
635 635 committing changelog
636 636 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
637 637 calling hook commit.auto: hgext_hookext.autohook
638 638 Automatically installed hook
639 639
640 640 $ hg showconfig hooks
641 641 hooks.commit.auto=<function autohook at *> (glob)
642 642
643 643 test python hook configured with python:[file]:[hook] syntax
644 644
645 645 $ cd ..
646 646 $ mkdir d
647 647 $ cd d
648 648 $ hg init repo
649 649 $ mkdir hooks
650 650
651 651 $ cd hooks
652 652 $ cat > testhooks.py <<EOF
653 653 > def testhook(**args):
654 654 > print 'hook works'
655 655 > EOF
656 656 $ echo '[hooks]' > ../repo/.hg/hgrc
657 657 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
658 658
659 659 $ cd ../repo
660 660 $ hg commit -d '0 0'
661 661 hook works
662 662 nothing changed
663 663 [1]
664 664
665 665 $ echo '[hooks]' > .hg/hgrc
666 666 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
667 667 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
668 668
669 669 $ hg up null
670 670 loading update.ne hook failed:
671 671 abort: No such file or directory: $TESTTMP/d/repo/nonexistent.py
672 672 [255]
673 673
674 674 $ hg id
675 675 loading pre-identify.npmd hook failed:
676 676 abort: No module named repo!
677 677 [255]
678 678
679 679 $ cd ../../b
680 680
681 681 make sure --traceback works on hook import failure
682 682
683 683 $ cat > importfail.py <<EOF
684 684 > import somebogusmodule
685 685 > # dereference something in the module to force demandimport to load it
686 686 > somebogusmodule.whatever
687 687 > EOF
688 688
689 689 $ echo '[hooks]' > .hg/hgrc
690 690 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
691 691
692 692 $ echo a >> a
693 693 $ hg --traceback commit -ma 2>&1 | egrep -v '^( +File| [a-zA-Z(])'
694 694 exception from first failed import attempt:
695 695 Traceback (most recent call last):
696 696 ImportError: No module named somebogusmodule
697 697 exception from second failed import attempt:
698 698 Traceback (most recent call last):
699 699 ImportError: No module named hgext_importfail
700 700 Traceback (most recent call last):
701 701 HookLoadError: precommit.importfail hook is invalid: import of "importfail" failed
702 702 abort: precommit.importfail hook is invalid: import of "importfail" failed
703 703
704 704 Issue1827: Hooks Update & Commit not completely post operation
705 705
706 706 commit and update hooks should run after command completion. The largefiles
707 707 use demonstrates a recursive wlock, showing the hook doesn't run until the
708 708 final release (and dirstate flush).
709 709
710 710 $ echo '[hooks]' > .hg/hgrc
711 711 $ echo 'commit = hg id' >> .hg/hgrc
712 712 $ echo 'update = hg id' >> .hg/hgrc
713 713 $ echo bb > a
714 714 $ hg ci -ma
715 715 223eafe2750c tip
716 716 $ hg up 0 --config extensions.largefiles=
717 717 cb9a9f314b8b
718 718 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
719 719
720 720 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
721 721 that is passed to pre/post hooks
722 722
723 723 $ echo '[hooks]' > .hg/hgrc
724 724 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
725 725 $ hg id
726 726 cb9a9f314b8b
727 727 $ hg id --verbose
728 728 calling hook pre-identify: hooktests.verbosehook
729 729 verbose output from hook
730 730 cb9a9f314b8b
731 731
732 732 Ensure hooks can be prioritized
733 733
734 734 $ echo '[hooks]' > .hg/hgrc
735 735 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
736 736 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
737 737 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
738 738 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
739 739 $ hg id --verbose
740 740 calling hook pre-identify.b: hooktests.verbosehook
741 741 verbose output from hook
742 742 calling hook pre-identify.a: hooktests.verbosehook
743 743 verbose output from hook
744 744 calling hook pre-identify.c: hooktests.verbosehook
745 745 verbose output from hook
746 746 cb9a9f314b8b
747 747
748 748 new tags must be visible in pretxncommit (issue3210)
749 749
750 750 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
751 751 $ hg tag -f foo
752 752 ['a', 'foo', 'tip']
753 753
754 754 post-init hooks must not crash (issue4983)
755 755 This also creates the `to` repo for the next test block.
756 756
757 757 $ cd ..
758 758 $ cat << EOF >> hgrc-with-post-init-hook
759 759 > [hooks]
760 760 > post-init = printenv.py post-init
761 761 > EOF
762 762 $ HGRCPATH=hgrc-with-post-init-hook hg init to
763 763 post-init hook: HG_ARGS=init to HG_OPTS={'insecure': None, 'remotecmd': '', 'ssh': ''} HG_PATS=['to'] HG_RESULT=0
764 764
765 765 new commits must be visible in pretxnchangegroup (issue3428)
766 766
767 767 $ echo '[hooks]' >> to/.hg/hgrc
768 768 $ echo 'prechangegroup = hg --traceback tip' >> to/.hg/hgrc
769 769 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
770 770 $ echo a >> to/a
771 771 $ hg --cwd to ci -Ama
772 772 adding a
773 773 $ hg clone to from
774 774 updating to branch default
775 775 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
776 776 $ echo aa >> from/a
777 777 $ hg --cwd from ci -mb
778 778 $ hg --cwd from push
779 779 pushing to $TESTTMP/to (glob)
780 780 searching for changes
781 781 changeset: 0:cb9a9f314b8b
782 782 tag: tip
783 783 user: test
784 784 date: Thu Jan 01 00:00:00 1970 +0000
785 785 summary: a
786 786
787 787 adding changesets
788 788 adding manifests
789 789 adding file changes
790 790 added 1 changesets with 1 changes to 1 files
791 791 changeset: 1:9836a07b9b9d
792 792 tag: tip
793 793 user: test
794 794 date: Thu Jan 01 00:00:00 1970 +0000
795 795 summary: b
796 796
797 $ cd ..
798 797
799 798 pretxnclose hook failure should abort the transaction
800 799
801 800 $ hg init txnfailure
802 801 $ cd txnfailure
803 802 $ touch a && hg commit -Aqm a
804 803 $ cat >> .hg/hgrc <<EOF
805 804 > [hooks]
806 805 > pretxnclose.error = exit 1
807 806 > EOF
808 807 $ hg strip -r 0 --config extensions.strip=
809 808 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
810 809 saved backup bundle to * (glob)
811 810 transaction abort!
812 811 rollback completed
813 812 strip failed, full bundle stored in * (glob)
814 813 abort: pretxnclose.error hook exited with status 1
815 814 [255]
816 815 $ hg recover
817 816 no interrupted transaction available
818 817 [1]
818 $ cd ..
819
820 Hook from untrusted hgrc are reported as failure
821 ================================================
822
823 $ cat << EOF > $TESTTMP/untrusted.py
824 > from mercurial import scmutil, util
825 > def uisetup(ui):
826 > class untrustedui(ui.__class__):
827 > def _trusted(self, fp, f):
828 > if util.normpath(fp.name).endswith('untrusted/.hg/hgrc'):
829 > return False
830 > return super(untrustedui, self)._trusted(fp, f)
831 > ui.__class__ = untrustedui
832 > EOF
833 $ cat << EOF >> $HGRCPATH
834 > [extensions]
835 > untrusted=$TESTTMP/untrusted.py
836 > EOF
837 $ hg init untrusted
838 $ cd untrusted
839
840 Non-blocking hook
841 -----------------
842
843 $ cat << EOF >> .hg/hgrc
844 > [hooks]
845 > txnclose.testing=echo txnclose hook called
846 > EOF
847 $ touch a && hg commit -Aqm a
848 warning: untrusted hook txnclose not executed
849 $ hg log
850 changeset: 0:3903775176ed
851 tag: tip
852 user: test
853 date: Thu Jan 01 00:00:00 1970 +0000
854 summary: a
855
856
857 Non-blocking hook
858 -----------------
859
860 $ cat << EOF >> .hg/hgrc
861 > [hooks]
862 > pretxnclose.testing=echo pre-txnclose hook called
863 > EOF
864 $ touch b && hg commit -Aqm a
865 transaction abort!
866 rollback completed
867 abort: untrusted hook pretxnclose not executed
868 (see 'hg help config.trusted')
869 [255]
870 $ hg log
871 changeset: 0:3903775176ed
872 tag: tip
873 user: test
874 date: Thu Jan 01 00:00:00 1970 +0000
875 summary: a
876
General Comments 0
You need to be logged in to leave comments. Login now