##// END OF EJS Templates
Make it possible to debug failed hook imports via use of --traceback...
Bryan O'Sullivan -
r9851:9e7b2c49 default
parent child Browse files
Show More
@@ -1,139 +1,147 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, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import os, sys
10 10 import extensions, util
11 11
12 12 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
13 13 '''call python hook. hook is callable object, looked up as
14 14 name in python module. if callable returns "true", hook
15 15 fails, else passes. if hook raises exception, treated as
16 16 hook failure. exception propagates if throw is "true".
17 17
18 18 reason for "true" meaning "hook failed" is so that
19 19 unmodified commands (e.g. mercurial.commands.update) can
20 20 be run as hooks without wrappers to convert return values.'''
21 21
22 22 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
23 23 obj = funcname
24 24 if not hasattr(obj, '__call__'):
25 25 d = funcname.rfind('.')
26 26 if d == -1:
27 27 raise util.Abort(_('%s hook is invalid ("%s" not in '
28 28 'a module)') % (hname, funcname))
29 29 modname = funcname[:d]
30 30 oldpaths = sys.path[:]
31 31 if hasattr(sys, "frozen"):
32 32 # binary installs require sys.path manipulation
33 33 path, name = os.path.split(modname)
34 34 if path and name:
35 35 sys.path.append(path)
36 36 modname = name
37 37 try:
38 38 obj = __import__(modname)
39 39 except ImportError:
40 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
40 41 try:
41 42 # extensions are loaded with hgext_ prefix
42 43 obj = __import__("hgext_%s" % modname)
43 44 except ImportError:
45 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
46 if ui.tracebackflag:
47 ui.warn(_('exception from first failed import attempt:\n'))
48 ui.traceback(e1)
49 if ui.tracebackflag:
50 ui.warn(_('exception from second failed import attempt:\n'))
51 ui.traceback(e2)
44 52 raise util.Abort(_('%s hook is invalid '
45 53 '(import of "%s" failed)') %
46 54 (hname, modname))
47 55 sys.path = oldpaths
48 56 try:
49 57 for p in funcname.split('.')[1:]:
50 58 obj = getattr(obj, p)
51 59 except AttributeError:
52 60 raise util.Abort(_('%s hook is invalid '
53 61 '("%s" is not defined)') %
54 62 (hname, funcname))
55 63 if not hasattr(obj, '__call__'):
56 64 raise util.Abort(_('%s hook is invalid '
57 65 '("%s" is not callable)') %
58 66 (hname, funcname))
59 67 try:
60 68 r = obj(ui=ui, repo=repo, hooktype=name, **args)
61 69 except KeyboardInterrupt:
62 70 raise
63 71 except Exception, exc:
64 72 if isinstance(exc, util.Abort):
65 73 ui.warn(_('error: %s hook failed: %s\n') %
66 74 (hname, exc.args[0]))
67 75 else:
68 76 ui.warn(_('error: %s hook raised an exception: '
69 77 '%s\n') % (hname, exc))
70 78 if throw:
71 79 raise
72 80 ui.traceback()
73 81 return True
74 82 if r:
75 83 if throw:
76 84 raise util.Abort(_('%s hook failed') % hname)
77 85 ui.warn(_('warning: %s hook failed\n') % hname)
78 86 return r
79 87
80 88 def _exthook(ui, repo, name, cmd, args, throw):
81 89 ui.note(_("running hook %s: %s\n") % (name, cmd))
82 90
83 91 env = {}
84 92 for k, v in args.iteritems():
85 93 if hasattr(v, '__call__'):
86 94 v = v()
87 95 env['HG_' + k.upper()] = v
88 96
89 97 if repo:
90 98 cwd = repo.root
91 99 else:
92 100 cwd = os.getcwd()
93 101 r = util.system(cmd, environ=env, cwd=cwd)
94 102 if r:
95 103 desc, r = util.explain_exit(r)
96 104 if throw:
97 105 raise util.Abort(_('%s hook %s') % (name, desc))
98 106 ui.warn(_('warning: %s hook %s\n') % (name, desc))
99 107 return r
100 108
101 109 _redirect = False
102 110 def redirect(state):
103 111 global _redirect
104 112 _redirect = state
105 113
106 114 def hook(ui, repo, name, throw=False, **args):
107 115 r = False
108 116
109 117 oldstdout = -1
110 118 if _redirect:
111 119 stdoutno = sys.__stdout__.fileno()
112 120 stderrno = sys.__stderr__.fileno()
113 121 # temporarily redirect stdout to stderr, if possible
114 122 if stdoutno >= 0 and stderrno >= 0:
115 123 oldstdout = os.dup(stdoutno)
116 124 os.dup2(stderrno, stdoutno)
117 125
118 126 try:
119 127 for hname, cmd in ui.configitems('hooks'):
120 128 if hname.split('.')[0] != name or not cmd:
121 129 continue
122 130 if hasattr(cmd, '__call__'):
123 131 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
124 132 elif cmd.startswith('python:'):
125 133 if cmd.count(':') >= 2:
126 134 path, cmd = cmd[7:].rsplit(':', 1)
127 135 mod = extensions.loadpath(path, 'hghook.%s' % hname)
128 136 hookfn = getattr(mod, cmd)
129 137 else:
130 138 hookfn = cmd[7:].strip()
131 139 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
132 140 else:
133 141 r = _exthook(ui, repo, hname, cmd, args, throw) or r
134 142 finally:
135 143 if _redirect and oldstdout >= 0:
136 144 os.dup2(oldstdout, stdoutno)
137 145 os.close(oldstdout)
138 146
139 147 return r
@@ -1,383 +1,386 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-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, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, util, error
11 11
12 12 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True,
13 13 '0': False, 'no': False, 'false': False, 'off': False}
14 14
15 15 class ui(object):
16 16 def __init__(self, src=None):
17 17 self._buffers = []
18 self.quiet = self.verbose = self.debugflag = self._traceback = False
18 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
19 19 self._reportuntrusted = True
20 20 self._ocfg = config.config() # overlay
21 21 self._tcfg = config.config() # trusted
22 22 self._ucfg = config.config() # untrusted
23 23 self._trustusers = set()
24 24 self._trustgroups = set()
25 25
26 26 if src:
27 27 self._tcfg = src._tcfg.copy()
28 28 self._ucfg = src._ucfg.copy()
29 29 self._ocfg = src._ocfg.copy()
30 30 self._trustusers = src._trustusers.copy()
31 31 self._trustgroups = src._trustgroups.copy()
32 32 self.fixconfig()
33 33 else:
34 34 # we always trust global config files
35 35 for f in util.rcpath():
36 36 self.readconfig(f, trust=True)
37 37
38 38 def copy(self):
39 39 return self.__class__(self)
40 40
41 41 def _is_trusted(self, fp, f):
42 42 st = util.fstat(fp)
43 43 if util.isowner(st):
44 44 return True
45 45
46 46 tusers, tgroups = self._trustusers, self._trustgroups
47 47 if '*' in tusers or '*' in tgroups:
48 48 return True
49 49
50 50 user = util.username(st.st_uid)
51 51 group = util.groupname(st.st_gid)
52 52 if user in tusers or group in tgroups or user == util.username():
53 53 return True
54 54
55 55 if self._reportuntrusted:
56 56 self.warn(_('Not trusting file %s from untrusted '
57 57 'user %s, group %s\n') % (f, user, group))
58 58 return False
59 59
60 60 def readconfig(self, filename, root=None, trust=False,
61 61 sections=None, remap=None):
62 62 try:
63 63 fp = open(filename)
64 64 except IOError:
65 65 if not sections: # ignore unless we were looking for something
66 66 return
67 67 raise
68 68
69 69 cfg = config.config()
70 70 trusted = sections or trust or self._is_trusted(fp, filename)
71 71
72 72 try:
73 73 cfg.read(filename, fp, sections=sections, remap=remap)
74 74 except error.ConfigError, inst:
75 75 if trusted:
76 76 raise
77 77 self.warn(_("Ignored: %s\n") % str(inst))
78 78
79 79 if trusted:
80 80 self._tcfg.update(cfg)
81 81 self._tcfg.update(self._ocfg)
82 82 self._ucfg.update(cfg)
83 83 self._ucfg.update(self._ocfg)
84 84
85 85 if root is None:
86 86 root = os.path.expanduser('~')
87 87 self.fixconfig(root=root)
88 88
89 89 def fixconfig(self, root=None):
90 90 # translate paths relative to root (or home) into absolute paths
91 91 root = root or os.getcwd()
92 92 for c in self._tcfg, self._ucfg, self._ocfg:
93 93 for n, p in c.items('paths'):
94 94 if p and "://" not in p and not os.path.isabs(p):
95 95 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
96 96
97 97 # update ui options
98 98 self.debugflag = self.configbool('ui', 'debug')
99 99 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
100 100 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
101 101 if self.verbose and self.quiet:
102 102 self.quiet = self.verbose = False
103 103 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
104 self._traceback = self.configbool('ui', 'traceback', False)
104 self.tracebackflag = self.configbool('ui', 'traceback', False)
105 105
106 106 # update trust information
107 107 self._trustusers.update(self.configlist('trusted', 'users'))
108 108 self._trustgroups.update(self.configlist('trusted', 'groups'))
109 109
110 110 def setconfig(self, section, name, value):
111 111 for cfg in (self._ocfg, self._tcfg, self._ucfg):
112 112 cfg.set(section, name, value)
113 113 self.fixconfig()
114 114
115 115 def _data(self, untrusted):
116 116 return untrusted and self._ucfg or self._tcfg
117 117
118 118 def configsource(self, section, name, untrusted=False):
119 119 return self._data(untrusted).source(section, name) or 'none'
120 120
121 121 def config(self, section, name, default=None, untrusted=False):
122 122 value = self._data(untrusted).get(section, name, default)
123 123 if self.debugflag and not untrusted and self._reportuntrusted:
124 124 uvalue = self._ucfg.get(section, name)
125 125 if uvalue is not None and uvalue != value:
126 126 self.debug(_("ignoring untrusted configuration option "
127 127 "%s.%s = %s\n") % (section, name, uvalue))
128 128 return value
129 129
130 130 def configbool(self, section, name, default=False, untrusted=False):
131 131 v = self.config(section, name, None, untrusted)
132 132 if v is None:
133 133 return default
134 134 if v.lower() not in _booleans:
135 135 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
136 136 % (section, name, v))
137 137 return _booleans[v.lower()]
138 138
139 139 def configlist(self, section, name, default=None, untrusted=False):
140 140 """Return a list of comma/space separated strings"""
141 141 result = self.config(section, name, untrusted=untrusted)
142 142 if result is None:
143 143 result = default or []
144 144 if isinstance(result, basestring):
145 145 result = result.replace(",", " ").split()
146 146 return result
147 147
148 148 def has_section(self, section, untrusted=False):
149 149 '''tell whether section exists in config.'''
150 150 return section in self._data(untrusted)
151 151
152 152 def configitems(self, section, untrusted=False):
153 153 items = self._data(untrusted).items(section)
154 154 if self.debugflag and not untrusted and self._reportuntrusted:
155 155 for k, v in self._ucfg.items(section):
156 156 if self._tcfg.get(section, k) != v:
157 157 self.debug(_("ignoring untrusted configuration option "
158 158 "%s.%s = %s\n") % (section, k, v))
159 159 return items
160 160
161 161 def walkconfig(self, untrusted=False):
162 162 cfg = self._data(untrusted)
163 163 for section in cfg.sections():
164 164 for name, value in self.configitems(section, untrusted):
165 165 yield section, name, str(value).replace('\n', '\\n')
166 166
167 167 def username(self):
168 168 """Return default username to be used in commits.
169 169
170 170 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
171 171 and stop searching if one of these is set.
172 172 If not found and ui.askusername is True, ask the user, else use
173 173 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
174 174 """
175 175 user = os.environ.get("HGUSER")
176 176 if user is None:
177 177 user = self.config("ui", "username")
178 178 if user is None:
179 179 user = os.environ.get("EMAIL")
180 180 if user is None and self.configbool("ui", "askusername"):
181 181 user = self.prompt(_("enter a commit username:"), default=None)
182 182 if user is None and not self.interactive():
183 183 try:
184 184 user = '%s@%s' % (util.getuser(), socket.getfqdn())
185 185 self.warn(_("No username found, using '%s' instead\n") % user)
186 186 except KeyError:
187 187 pass
188 188 if not user:
189 189 raise util.Abort(_('no username supplied (see "hg help config")'))
190 190 if "\n" in user:
191 191 raise util.Abort(_("username %s contains a newline\n") % repr(user))
192 192 return user
193 193
194 194 def shortuser(self, user):
195 195 """Return a short representation of a user name or email address."""
196 196 if not self.verbose: user = util.shortuser(user)
197 197 return user
198 198
199 199 def _path(self, loc):
200 200 p = self.config('paths', loc)
201 201 if p:
202 202 if '%%' in p:
203 203 self.warn("(deprecated '%%' in path %s=%s from %s)\n" %
204 204 (loc, p, self.configsource('paths', loc)))
205 205 p = p.replace('%%', '%')
206 206 p = util.expandpath(p)
207 207 return p
208 208
209 209 def expandpath(self, loc, default=None):
210 210 """Return repository location relative to cwd or from [paths]"""
211 211 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
212 212 return loc
213 213
214 214 path = self._path(loc)
215 215 if not path and default is not None:
216 216 path = self._path(default)
217 217 return path or loc
218 218
219 219 def pushbuffer(self):
220 220 self._buffers.append([])
221 221
222 222 def popbuffer(self):
223 223 return "".join(self._buffers.pop())
224 224
225 225 def write(self, *args):
226 226 if self._buffers:
227 227 self._buffers[-1].extend([str(a) for a in args])
228 228 else:
229 229 for a in args:
230 230 sys.stdout.write(str(a))
231 231
232 232 def write_err(self, *args):
233 233 try:
234 234 if not sys.stdout.closed: sys.stdout.flush()
235 235 for a in args:
236 236 sys.stderr.write(str(a))
237 237 # stderr may be buffered under win32 when redirected to files,
238 238 # including stdout.
239 239 if not sys.stderr.closed: sys.stderr.flush()
240 240 except IOError, inst:
241 241 if inst.errno != errno.EPIPE:
242 242 raise
243 243
244 244 def flush(self):
245 245 try: sys.stdout.flush()
246 246 except: pass
247 247 try: sys.stderr.flush()
248 248 except: pass
249 249
250 250 def interactive(self):
251 251 i = self.configbool("ui", "interactive", None)
252 252 if i is None:
253 253 return sys.stdin.isatty()
254 254 return i
255 255
256 256 def _readline(self, prompt=''):
257 257 if sys.stdin.isatty():
258 258 try:
259 259 # magically add command line editing support, where
260 260 # available
261 261 import readline
262 262 # force demandimport to really load the module
263 263 readline.read_history_file
264 264 # windows sometimes raises something other than ImportError
265 265 except Exception:
266 266 pass
267 267 line = raw_input(prompt)
268 268 # When stdin is in binary mode on Windows, it can cause
269 269 # raw_input() to emit an extra trailing carriage return
270 270 if os.linesep == '\r\n' and line and line[-1] == '\r':
271 271 line = line[:-1]
272 272 return line
273 273
274 274 def prompt(self, msg, default="y"):
275 275 """Prompt user with msg, read response.
276 276 If ui is not interactive, the default is returned.
277 277 """
278 278 if not self.interactive():
279 279 self.write(msg, ' ', default, "\n")
280 280 return default
281 281 try:
282 282 r = self._readline(msg + ' ')
283 283 if not r:
284 284 return default
285 285 return r
286 286 except EOFError:
287 287 raise util.Abort(_('response expected'))
288 288
289 289 def promptchoice(self, msg, choices, default=0):
290 290 """Prompt user with msg, read response, and ensure it matches
291 291 one of the provided choices. The index of the choice is returned.
292 292 choices is a sequence of acceptable responses with the format:
293 293 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
294 294 If ui is not interactive, the default is returned.
295 295 """
296 296 resps = [s[s.index('&')+1].lower() for s in choices]
297 297 while True:
298 298 r = self.prompt(msg, resps[default])
299 299 if r.lower() in resps:
300 300 return resps.index(r.lower())
301 301 self.write(_("unrecognized response\n"))
302 302
303 303
304 304 def getpass(self, prompt=None, default=None):
305 305 if not self.interactive(): return default
306 306 try:
307 307 return getpass.getpass(prompt or _('password: '))
308 308 except EOFError:
309 309 raise util.Abort(_('response expected'))
310 310 def status(self, *msg):
311 311 if not self.quiet: self.write(*msg)
312 312 def warn(self, *msg):
313 313 self.write_err(*msg)
314 314 def note(self, *msg):
315 315 if self.verbose: self.write(*msg)
316 316 def debug(self, *msg):
317 317 if self.debugflag: self.write(*msg)
318 318 def edit(self, text, user):
319 319 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
320 320 text=True)
321 321 try:
322 322 f = os.fdopen(fd, "w")
323 323 f.write(text)
324 324 f.close()
325 325
326 326 editor = self.geteditor()
327 327
328 328 util.system("%s \"%s\"" % (editor, name),
329 329 environ={'HGUSER': user},
330 330 onerr=util.Abort, errprefix=_("edit failed"))
331 331
332 332 f = open(name)
333 333 t = f.read()
334 334 f.close()
335 335 finally:
336 336 os.unlink(name)
337 337
338 338 return t
339 339
340 def traceback(self):
340 def traceback(self, exc=None):
341 341 '''print exception traceback if traceback printing enabled.
342 342 only to call in exception handler. returns true if traceback
343 343 printed.'''
344 if self._traceback:
345 traceback.print_exc()
346 return self._traceback
344 if self.tracebackflag:
345 if exc:
346 traceback.print_exception(exc[0], exc[1], exc[2])
347 else:
348 traceback.print_exc()
349 return self.tracebackflag
347 350
348 351 def geteditor(self):
349 352 '''return editor to use'''
350 353 return (os.environ.get("HGEDITOR") or
351 354 self.config("ui", "editor") or
352 355 os.environ.get("VISUAL") or
353 356 os.environ.get("EDITOR", "vi"))
354 357
355 358 def progress(self, topic, pos, item="", unit="", total=None):
356 359 '''show a progress message
357 360
358 361 With stock hg, this is simply a debug message that is hidden
359 362 by default, but with extensions or GUI tools it may be
360 363 visible. 'topic' is the current operation, 'item' is a
361 364 non-numeric marker of the current position (ie the currently
362 365 in-process file), 'pos' is the current numeric position (ie
363 366 revision, bytes, etc.), unit is a corresponding unit label,
364 367 and total is the highest expected pos.
365 368
366 369 Multiple nested topics may be active at a time. All topics
367 370 should be marked closed by setting pos to None at termination.
368 371 '''
369 372
370 373 if pos == None or not self.debugflag:
371 374 return
372 375
373 376 if unit:
374 377 unit = ' ' + unit
375 378 if item:
376 379 item = ' ' + item
377 380
378 381 if total:
379 382 pct = 100.0 * pos / total
380 383 self.debug('%s:%s %s/%s%s (%4.2g%%)\n'
381 384 % (topic, item, pos, total, unit, pct))
382 385 else:
383 386 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
@@ -1,251 +1,265 b''
1 1 #!/bin/sh
2 2
3 3 cp "$TESTDIR"/printenv.py .
4 4
5 5 # commit hooks can see env vars
6 6 hg init a
7 7 cd a
8 8 echo "[hooks]" > .hg/hgrc
9 9 echo 'commit = unset HG_LOCAL HG_TAG; python ../printenv.py commit' >> .hg/hgrc
10 10 echo 'commit.b = unset HG_LOCAL HG_TAG; python ../printenv.py commit.b' >> .hg/hgrc
11 11 echo 'precommit = unset HG_LOCAL HG_NODE HG_TAG; python ../printenv.py precommit' >> .hg/hgrc
12 12 echo 'pretxncommit = unset HG_LOCAL HG_TAG; python ../printenv.py pretxncommit' >> .hg/hgrc
13 13 echo 'pretxncommit.tip = hg -q tip' >> .hg/hgrc
14 14 echo 'pre-identify = python ../printenv.py pre-identify 1' >> .hg/hgrc
15 15 echo 'pre-cat = python ../printenv.py pre-cat' >> .hg/hgrc
16 16 echo 'post-cat = python ../printenv.py post-cat' >> .hg/hgrc
17 17 echo a > a
18 18 hg add a
19 19 hg commit -m a -d "1000000 0"
20 20
21 21 hg clone . ../b
22 22 cd ../b
23 23
24 24 # changegroup hooks can see env vars
25 25 echo '[hooks]' > .hg/hgrc
26 26 echo 'prechangegroup = python ../printenv.py prechangegroup' >> .hg/hgrc
27 27 echo 'changegroup = python ../printenv.py changegroup' >> .hg/hgrc
28 28 echo 'incoming = python ../printenv.py incoming' >> .hg/hgrc
29 29
30 30 # pretxncommit and commit hooks can see both parents of merge
31 31 cd ../a
32 32 echo b >> a
33 33 hg commit -m a1 -d "1 0"
34 34 hg update -C 0
35 35 echo b > b
36 36 hg add b
37 37 hg commit -m b -d '1 0'
38 38 hg merge 1
39 39 hg commit -m merge -d '2 0'
40 40
41 41 # test generic hooks
42 42 hg id
43 43 hg cat b
44 44
45 45 cd ../b
46 46 hg pull ../a
47 47
48 48 # tag hooks can see env vars
49 49 cd ../a
50 50 echo 'pretag = python ../printenv.py pretag' >> .hg/hgrc
51 51 echo 'tag = unset HG_PARENT1 HG_PARENT2; python ../printenv.py tag' >> .hg/hgrc
52 52 hg tag -d '3 0' a
53 53 hg tag -l la
54 54
55 55 # pretag hook can forbid tagging
56 56 echo 'pretag.forbid = python ../printenv.py pretag.forbid 1' >> .hg/hgrc
57 57 hg tag -d '4 0' fa
58 58 hg tag -l fla
59 59
60 60 # pretxncommit hook can see changeset, can roll back txn, changeset
61 61 # no more there after
62 62 echo 'pretxncommit.forbid0 = hg tip -q' >> .hg/hgrc
63 63 echo 'pretxncommit.forbid1 = python ../printenv.py pretxncommit.forbid 1' >> .hg/hgrc
64 64 echo z > z
65 65 hg add z
66 66 hg -q tip
67 67 hg commit -m 'fail' -d '4 0'
68 68 hg -q tip
69 69
70 70 # precommit hook can prevent commit
71 71 echo 'precommit.forbid = python ../printenv.py precommit.forbid 1' >> .hg/hgrc
72 72 hg commit -m 'fail' -d '4 0'
73 73 hg -q tip
74 74
75 75 # preupdate hook can prevent update
76 76 echo 'preupdate = python ../printenv.py preupdate' >> .hg/hgrc
77 77 hg update 1
78 78
79 79 # update hook
80 80 echo 'update = python ../printenv.py update' >> .hg/hgrc
81 81 hg update
82 82
83 83 # prechangegroup hook can prevent incoming changes
84 84 cd ../b
85 85 hg -q tip
86 86 echo '[hooks]' > .hg/hgrc
87 87 echo 'prechangegroup.forbid = python ../printenv.py prechangegroup.forbid 1' >> .hg/hgrc
88 88 hg pull ../a
89 89
90 90 # pretxnchangegroup hook can see incoming changes, can roll back txn,
91 91 # incoming changes no longer there after
92 92 echo '[hooks]' > .hg/hgrc
93 93 echo 'pretxnchangegroup.forbid0 = hg tip -q' >> .hg/hgrc
94 94 echo 'pretxnchangegroup.forbid1 = python ../printenv.py pretxnchangegroup.forbid 1' >> .hg/hgrc
95 95 hg pull ../a
96 96 hg -q tip
97 97
98 98 # outgoing hooks can see env vars
99 99 rm .hg/hgrc
100 100 echo '[hooks]' > ../a/.hg/hgrc
101 101 echo 'preoutgoing = python ../printenv.py preoutgoing' >> ../a/.hg/hgrc
102 102 echo 'outgoing = python ../printenv.py outgoing' >> ../a/.hg/hgrc
103 103 hg pull ../a
104 104 hg rollback
105 105
106 106 # preoutgoing hook can prevent outgoing changes
107 107 echo 'preoutgoing.forbid = python ../printenv.py preoutgoing.forbid 1' >> ../a/.hg/hgrc
108 108 hg pull ../a
109 109
110 110 # outgoing hooks work for local clones
111 111 cd ..
112 112 echo '[hooks]' > a/.hg/hgrc
113 113 echo 'preoutgoing = python ../printenv.py preoutgoing' >> a/.hg/hgrc
114 114 echo 'outgoing = python ../printenv.py outgoing' >> a/.hg/hgrc
115 115 hg clone a c
116 116 rm -rf c
117 117
118 118 # preoutgoing hook can prevent outgoing changes for local clones
119 119 echo 'preoutgoing.forbid = python ../printenv.py preoutgoing.forbid 1' >> a/.hg/hgrc
120 120 hg clone a zzz
121 121 cd b
122 122
123 123 cat > hooktests.py <<EOF
124 124 from mercurial import util
125 125
126 126 uncallable = 0
127 127
128 128 def printargs(args):
129 129 args.pop('ui', None)
130 130 args.pop('repo', None)
131 131 a = list(args.items())
132 132 a.sort()
133 133 print 'hook args:'
134 134 for k, v in a:
135 135 print ' ', k, v
136 136
137 137 def passhook(**args):
138 138 printargs(args)
139 139
140 140 def failhook(**args):
141 141 printargs(args)
142 142 return True
143 143
144 144 class LocalException(Exception):
145 145 pass
146 146
147 147 def raisehook(**args):
148 148 raise LocalException('exception from hook')
149 149
150 150 def aborthook(**args):
151 151 raise util.Abort('raise abort from hook')
152 152
153 153 def brokenhook(**args):
154 154 return 1 + {}
155 155
156 156 class container:
157 157 unreachable = 1
158 158 EOF
159 159
160 160 echo '# test python hooks'
161 161 PYTHONPATH="`pwd`:$PYTHONPATH"
162 162 export PYTHONPATH
163 163
164 164 echo '[hooks]' > ../a/.hg/hgrc
165 165 echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
166 166 hg pull ../a 2>&1 | grep 'raised an exception'
167 167
168 168 echo '[hooks]' > ../a/.hg/hgrc
169 169 echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
170 170 hg pull ../a 2>&1 | grep 'raised an exception'
171 171
172 172 echo '[hooks]' > ../a/.hg/hgrc
173 173 echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
174 174 hg pull ../a
175 175
176 176 echo '[hooks]' > ../a/.hg/hgrc
177 177 echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
178 178 hg pull ../a
179 179
180 180 echo '[hooks]' > ../a/.hg/hgrc
181 181 echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
182 182 hg pull ../a
183 183
184 184 echo '[hooks]' > ../a/.hg/hgrc
185 185 echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
186 186 hg pull ../a
187 187
188 188 echo '[hooks]' > ../a/.hg/hgrc
189 189 echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
190 190 hg pull ../a
191 191
192 192 echo '[hooks]' > ../a/.hg/hgrc
193 193 echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
194 194 hg pull ../a
195 195
196 196 echo '[hooks]' > ../a/.hg/hgrc
197 197 echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
198 198 hg pull ../a
199 199
200 200 echo '[hooks]' > ../a/.hg/hgrc
201 201 echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
202 202 hg pull ../a
203 203
204 204 echo '# make sure --traceback works'
205 205 echo '[hooks]' > .hg/hgrc
206 206 echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
207 207
208 208 echo a >> a
209 209 hg --traceback commit -A -m a 2>&1 | grep '^Traceback'
210 210
211 211 cd ..
212 212 hg init c
213 213 cd c
214 214
215 215 cat > hookext.py <<EOF
216 216 def autohook(**args):
217 217 print "Automatically installed hook"
218 218
219 219 def reposetup(ui, repo):
220 220 repo.ui.setconfig("hooks", "commit.auto", autohook)
221 221 EOF
222 222 echo '[extensions]' >> .hg/hgrc
223 223 echo 'hookext = hookext.py' >> .hg/hgrc
224 224
225 225 touch foo
226 226 hg add foo
227 227 hg ci -m 'add foo'
228 228 echo >> foo
229 229 hg ci --debug -m 'change foo' | sed -e 's/ at .*>/>/'
230 230
231 231 hg showconfig hooks | sed -e 's/ at .*>/>/'
232 232
233 233 echo '# test python hook configured with python:[file]:[hook] syntax'
234 234 cd ..
235 235 mkdir d
236 236 cd d
237 237 hg init repo
238 238 mkdir hooks
239 239
240 240 cd hooks
241 241 cat > testhooks.py <<EOF
242 242 def testhook(**args):
243 243 print 'hook works'
244 244 EOF
245 245 echo '[hooks]' > ../repo/.hg/hgrc
246 246 echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
247 247
248 248 cd ../repo
249 249 hg commit
250 250
251 cd ../../b
252 echo '# make sure --traceback works on hook import failure'
253 cat > importfail.py <<EOF
254 import somebogusmodule
255 # dereference something in the module to force demandimport to load it
256 somebogusmodule.whatever
257 EOF
258
259 echo '[hooks]' > .hg/hgrc
260 echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
261
262 echo a >> a
263 hg --traceback commit -Ama 2>&1 | grep '^\(exception\|Traceback\|ImportError\)'
264
251 265 exit 0
@@ -1,165 +1,173 b''
1 1 precommit hook: HG_PARENT1=0000000000000000000000000000000000000000
2 2 pretxncommit hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$HGTMP/test-hook/a
3 3 0:29b62aeb769f
4 4 commit hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000
5 5 commit.b hook: HG_NODE=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PARENT1=0000000000000000000000000000000000000000
6 6 updating to branch default
7 7 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 8 precommit hook: HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b
9 9 pretxncommit hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PENDING=$HGTMP/test-hook/a
10 10 1:b702efe96888
11 11 commit hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b
12 12 commit.b hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b
13 13 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
14 14 precommit hook: HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b
15 15 pretxncommit hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b HG_PENDING=$HGTMP/test-hook/a
16 16 2:1324a5531bac
17 17 commit hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b
18 18 commit.b hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b
19 19 created new head
20 20 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
21 21 (branch merge, don't forget to commit)
22 22 precommit hook: HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2
23 23 pretxncommit hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2 HG_PENDING=$HGTMP/test-hook/a
24 24 3:4c52fb2e4022
25 25 commit hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2
26 26 commit.b hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_PARENT1=1324a5531bac09b329c3845d35ae6a7526874edb HG_PARENT2=b702efe9688826e3a91283852b328b84dbf37bc2
27 27 pre-identify hook: HG_ARGS=id
28 28 warning: pre-identify hook exited with status 1
29 29 pre-cat hook: HG_ARGS=cat b
30 30 post-cat hook: HG_ARGS=cat b HG_RESULT=0
31 31 b
32 32 prechangegroup hook: HG_SOURCE=pull HG_URL=file:
33 33 changegroup hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_SOURCE=pull HG_URL=file:
34 34 incoming hook: HG_NODE=b702efe9688826e3a91283852b328b84dbf37bc2 HG_SOURCE=pull HG_URL=file:
35 35 incoming hook: HG_NODE=1324a5531bac09b329c3845d35ae6a7526874edb HG_SOURCE=pull HG_URL=file:
36 36 incoming hook: HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_SOURCE=pull HG_URL=file:
37 37 pulling from ../a
38 38 searching for changes
39 39 adding changesets
40 40 adding manifests
41 41 adding file changes
42 42 added 3 changesets with 2 changes to 2 files
43 43 (run 'hg update' to get a working copy)
44 44 pretag hook: HG_LOCAL=0 HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_TAG=a
45 45 precommit hook: HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321
46 46 pretxncommit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321 HG_PENDING=$HGTMP/test-hook/a
47 47 4:8ea2ef7ad3e8
48 48 commit hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321
49 49 commit.b hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PARENT1=4c52fb2e402287dd5dc052090682536c8406c321
50 50 tag hook: HG_LOCAL=0 HG_NODE=4c52fb2e402287dd5dc052090682536c8406c321 HG_TAG=a
51 51 pretag hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=la
52 52 tag hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=la
53 53 pretag hook: HG_LOCAL=0 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fa
54 54 pretag.forbid hook: HG_LOCAL=0 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fa
55 55 abort: pretag.forbid hook exited with status 1
56 56 pretag hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fla
57 57 pretag.forbid hook: HG_LOCAL=1 HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_TAG=fla
58 58 abort: pretag.forbid hook exited with status 1
59 59 4:8ea2ef7ad3e8
60 60 precommit hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198
61 61 pretxncommit hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PENDING=$HGTMP/test-hook/a
62 62 5:fad284daf8c0
63 63 5:fad284daf8c0
64 64 pretxncommit.forbid hook: HG_NODE=fad284daf8c032148abaffcd745dafeceefceb61 HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PENDING=$HGTMP/test-hook/a
65 65 transaction abort!
66 66 rollback completed
67 67 abort: pretxncommit.forbid1 hook exited with status 1
68 68 4:8ea2ef7ad3e8
69 69 precommit hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198
70 70 precommit.forbid hook: HG_PARENT1=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198
71 71 abort: precommit.forbid hook exited with status 1
72 72 4:8ea2ef7ad3e8
73 73 preupdate hook: HG_PARENT1=b702efe96888
74 74 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
75 75 preupdate hook: HG_PARENT1=8ea2ef7ad3e8
76 76 update hook: HG_ERROR=0 HG_PARENT1=8ea2ef7ad3e8
77 77 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
78 78 3:4c52fb2e4022
79 79 prechangegroup.forbid hook: HG_SOURCE=pull HG_URL=file:
80 80 pulling from ../a
81 81 searching for changes
82 82 abort: prechangegroup.forbid hook exited with status 1
83 83 4:8ea2ef7ad3e8
84 84 pretxnchangegroup.forbid hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_PENDING=$HGTMP/test-hook/b HG_SOURCE=pull HG_URL=file:
85 85 pulling from ../a
86 86 searching for changes
87 87 adding changesets
88 88 adding manifests
89 89 adding file changes
90 90 added 1 changesets with 1 changes to 1 files
91 91 transaction abort!
92 92 rollback completed
93 93 abort: pretxnchangegroup.forbid1 hook exited with status 1
94 94 3:4c52fb2e4022
95 95 preoutgoing hook: HG_SOURCE=pull
96 96 outgoing hook: HG_NODE=8ea2ef7ad3e8cac946c72f1e0c79d6aebc301198 HG_SOURCE=pull
97 97 pulling from ../a
98 98 searching for changes
99 99 adding changesets
100 100 adding manifests
101 101 adding file changes
102 102 added 1 changesets with 1 changes to 1 files
103 103 (run 'hg update' to get a working copy)
104 104 rolling back last transaction
105 105 preoutgoing hook: HG_SOURCE=pull
106 106 preoutgoing.forbid hook: HG_SOURCE=pull
107 107 pulling from ../a
108 108 searching for changes
109 109 abort: preoutgoing.forbid hook exited with status 1
110 110 preoutgoing hook: HG_SOURCE=clone
111 111 outgoing hook: HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
112 112 updating to branch default
113 113 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
114 114 preoutgoing hook: HG_SOURCE=clone
115 115 preoutgoing.forbid hook: HG_SOURCE=clone
116 116 abort: preoutgoing.forbid hook exited with status 1
117 117 # test python hooks
118 118 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
119 119 error: preoutgoing.raise hook raised an exception: exception from hook
120 120 pulling from ../a
121 121 searching for changes
122 122 error: preoutgoing.abort hook failed: raise abort from hook
123 123 abort: raise abort from hook
124 124 pulling from ../a
125 125 searching for changes
126 126 hook args:
127 127 hooktype preoutgoing
128 128 source pull
129 129 abort: preoutgoing.fail hook failed
130 130 pulling from ../a
131 131 searching for changes
132 132 abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable)
133 133 pulling from ../a
134 134 searching for changes
135 135 abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined)
136 136 pulling from ../a
137 137 searching for changes
138 138 abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module)
139 139 pulling from ../a
140 140 searching for changes
141 141 abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed)
142 142 pulling from ../a
143 143 searching for changes
144 144 abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed)
145 145 pulling from ../a
146 146 searching for changes
147 147 hook args:
148 148 hooktype preoutgoing
149 149 source pull
150 150 adding changesets
151 151 adding manifests
152 152 adding file changes
153 153 added 1 changesets with 1 changes to 1 files
154 154 (run 'hg update' to get a working copy)
155 155 # make sure --traceback works
156 156 Traceback (most recent call last):
157 157 Automatically installed hook
158 158 foo
159 159 calling hook commit.auto: <function autohook>
160 160 Automatically installed hook
161 161 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
162 162 hooks.commit.auto=<function autohook>
163 163 # test python hook configured with python:[file]:[hook] syntax
164 164 hook works
165 165 nothing changed
166 # make sure --traceback works on hook import failure
167 exception from first failed import attempt:
168 Traceback (most recent call last):
169 ImportError: No module named somebogusmodule
170 exception from second failed import attempt:
171 Traceback (most recent call last):
172 ImportError: No module named hgext_importfail
173 Traceback (most recent call last):
General Comments 0
You need to be logged in to leave comments. Login now