##// END OF EJS Templates
hooks: fix pre- and post- hooks specified in .hg/hgrc...
Matt Mackall -
r5869:2c565b95 default
parent child Browse files
Show More
@@ -1,413 +1,413
1 1 # dispatch.py - command dispatching 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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import *
9 9 from i18n import _
10 10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex, time
11 11 import util, commands, hg, lock, fancyopts, revlog, version, extensions, hook
12 12 import cmdutil
13 13 import ui as _ui
14 14
15 15 class ParseError(Exception):
16 16 """Exception raised on errors in parsing the command line."""
17 17
18 18 def run():
19 19 "run the command in sys.argv"
20 20 sys.exit(dispatch(sys.argv[1:]))
21 21
22 22 def dispatch(args):
23 23 "run the command specified in args"
24 24 try:
25 25 u = _ui.ui(traceback='--traceback' in args)
26 26 except util.Abort, inst:
27 27 sys.stderr.write(_("abort: %s\n") % inst)
28 28 return -1
29 29 return _runcatch(u, args)
30 30
31 31 def _runcatch(ui, args):
32 32 def catchterm(*args):
33 33 raise util.SignalInterrupt
34 34
35 35 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
36 36 num = getattr(signal, name, None)
37 37 if num: signal.signal(num, catchterm)
38 38
39 39 try:
40 40 try:
41 41 # enter the debugger before command execution
42 42 if '--debugger' in args:
43 43 pdb.set_trace()
44 44 try:
45 45 return _dispatch(ui, args)
46 46 finally:
47 47 ui.flush()
48 48 except:
49 49 # enter the debugger when we hit an exception
50 50 if '--debugger' in args:
51 51 pdb.post_mortem(sys.exc_info()[2])
52 52 ui.print_exc()
53 53 raise
54 54
55 55 except ParseError, inst:
56 56 if inst.args[0]:
57 57 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
58 58 commands.help_(ui, inst.args[0])
59 59 else:
60 60 ui.warn(_("hg: %s\n") % inst.args[1])
61 61 commands.help_(ui, 'shortlist')
62 62 except cmdutil.AmbiguousCommand, inst:
63 63 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
64 64 (inst.args[0], " ".join(inst.args[1])))
65 65 except cmdutil.UnknownCommand, inst:
66 66 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
67 67 commands.help_(ui, 'shortlist')
68 68 except hg.RepoError, inst:
69 69 ui.warn(_("abort: %s!\n") % inst)
70 70 except lock.LockHeld, inst:
71 71 if inst.errno == errno.ETIMEDOUT:
72 72 reason = _('timed out waiting for lock held by %s') % inst.locker
73 73 else:
74 74 reason = _('lock held by %s') % inst.locker
75 75 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
76 76 except lock.LockUnavailable, inst:
77 77 ui.warn(_("abort: could not lock %s: %s\n") %
78 78 (inst.desc or inst.filename, inst.strerror))
79 79 except revlog.RevlogError, inst:
80 80 ui.warn(_("abort: %s!\n") % inst)
81 81 except util.SignalInterrupt:
82 82 ui.warn(_("killed!\n"))
83 83 except KeyboardInterrupt:
84 84 try:
85 85 ui.warn(_("interrupted!\n"))
86 86 except IOError, inst:
87 87 if inst.errno == errno.EPIPE:
88 88 if ui.debugflag:
89 89 ui.warn(_("\nbroken pipe\n"))
90 90 else:
91 91 raise
92 92 except socket.error, inst:
93 93 ui.warn(_("abort: %s\n") % inst[1])
94 94 except IOError, inst:
95 95 if hasattr(inst, "code"):
96 96 ui.warn(_("abort: %s\n") % inst)
97 97 elif hasattr(inst, "reason"):
98 98 try: # usually it is in the form (errno, strerror)
99 99 reason = inst.reason.args[1]
100 100 except: # it might be anything, for example a string
101 101 reason = inst.reason
102 102 ui.warn(_("abort: error: %s\n") % reason)
103 103 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
104 104 if ui.debugflag:
105 105 ui.warn(_("broken pipe\n"))
106 106 elif getattr(inst, "strerror", None):
107 107 if getattr(inst, "filename", None):
108 108 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
109 109 else:
110 110 ui.warn(_("abort: %s\n") % inst.strerror)
111 111 else:
112 112 raise
113 113 except OSError, inst:
114 114 if getattr(inst, "filename", None):
115 115 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
116 116 else:
117 117 ui.warn(_("abort: %s\n") % inst.strerror)
118 118 except util.UnexpectedOutput, inst:
119 119 ui.warn(_("abort: %s") % inst[0])
120 120 if not isinstance(inst[1], basestring):
121 121 ui.warn(" %r\n" % (inst[1],))
122 122 elif not inst[1]:
123 123 ui.warn(_(" empty string\n"))
124 124 else:
125 125 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
126 126 except ImportError, inst:
127 127 m = str(inst).split()[-1]
128 128 ui.warn(_("abort: could not import module %s!\n") % m)
129 129 if m in "mpatch bdiff".split():
130 130 ui.warn(_("(did you forget to compile extensions?)\n"))
131 131 elif m in "zlib".split():
132 132 ui.warn(_("(is your Python install correct?)\n"))
133 133
134 134 except util.Abort, inst:
135 135 ui.warn(_("abort: %s\n") % inst)
136 136 except MemoryError:
137 137 ui.warn(_("abort: out of memory\n"))
138 138 except SystemExit, inst:
139 139 # Commands shouldn't sys.exit directly, but give a return code.
140 140 # Just in case catch this and and pass exit code to caller.
141 141 return inst.code
142 142 except:
143 143 ui.warn(_("** unknown exception encountered, details follow\n"))
144 144 ui.warn(_("** report bug details to "
145 145 "http://www.selenic.com/mercurial/bts\n"))
146 146 ui.warn(_("** or mercurial@selenic.com\n"))
147 147 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
148 148 % version.get_version())
149 149 raise
150 150
151 151 return -1
152 152
153 153 def _findrepo():
154 154 p = os.getcwd()
155 155 while not os.path.isdir(os.path.join(p, ".hg")):
156 156 oldp, p = p, os.path.dirname(p)
157 157 if p == oldp:
158 158 return None
159 159
160 160 return p
161 161
162 162 def _parse(ui, args):
163 163 options = {}
164 164 cmdoptions = {}
165 165
166 166 try:
167 167 args = fancyopts.fancyopts(args, commands.globalopts, options)
168 168 except fancyopts.getopt.GetoptError, inst:
169 169 raise ParseError(None, inst)
170 170
171 171 if args:
172 172 cmd, args = args[0], args[1:]
173 173 aliases, i = cmdutil.findcmd(ui, cmd, commands.table)
174 174 cmd = aliases[0]
175 175 defaults = ui.config("defaults", cmd)
176 176 if defaults:
177 177 args = shlex.split(defaults) + args
178 178 c = list(i[1])
179 179 else:
180 180 cmd = None
181 181 c = []
182 182
183 183 # combine global options into local
184 184 for o in commands.globalopts:
185 185 c.append((o[0], o[1], options[o[1]], o[3]))
186 186
187 187 try:
188 188 args = fancyopts.fancyopts(args, c, cmdoptions)
189 189 except fancyopts.getopt.GetoptError, inst:
190 190 raise ParseError(cmd, inst)
191 191
192 192 # separate global options back out
193 193 for o in commands.globalopts:
194 194 n = o[1]
195 195 options[n] = cmdoptions[n]
196 196 del cmdoptions[n]
197 197
198 198 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
199 199
200 200 def _parseconfig(config):
201 201 """parse the --config options from the command line"""
202 202 parsed = []
203 203 for cfg in config:
204 204 try:
205 205 name, value = cfg.split('=', 1)
206 206 section, name = name.split('.', 1)
207 207 if not section or not name:
208 208 raise IndexError
209 209 parsed.append((section, name, value))
210 210 except (IndexError, ValueError):
211 211 raise util.Abort(_('malformed --config option: %s') % cfg)
212 212 return parsed
213 213
214 214 def _earlygetopt(aliases, args):
215 215 """Return list of values for an option (or aliases).
216 216
217 217 The values are listed in the order they appear in args.
218 218 The options and values are removed from args.
219 219 """
220 220 try:
221 221 argcount = args.index("--")
222 222 except ValueError:
223 223 argcount = len(args)
224 224 shortopts = [opt for opt in aliases if len(opt) == 2]
225 225 values = []
226 226 pos = 0
227 227 while pos < argcount:
228 228 if args[pos] in aliases:
229 229 if pos + 1 >= argcount:
230 230 # ignore and let getopt report an error if there is no value
231 231 break
232 232 del args[pos]
233 233 values.append(args.pop(pos))
234 234 argcount -= 2
235 235 elif args[pos][:2] in shortopts:
236 236 # short option can have no following space, e.g. hg log -Rfoo
237 237 values.append(args.pop(pos)[2:])
238 238 argcount -= 1
239 239 else:
240 240 pos += 1
241 241 return values
242 242
243 243 _loaded = {}
244 244 def _dispatch(ui, args):
245 245 # read --config before doing anything else
246 246 # (e.g. to change trust settings for reading .hg/hgrc)
247 247 config = _earlygetopt(['--config'], args)
248 248 if config:
249 249 ui.updateopts(config=_parseconfig(config))
250 250
251 251 # check for cwd
252 252 cwd = _earlygetopt(['--cwd'], args)
253 253 if cwd:
254 254 os.chdir(cwd[-1])
255 255
256 256 # read the local repository .hgrc into a local ui object
257 257 path = _findrepo() or ""
258 258 if not path:
259 259 lui = ui
260 260 if path:
261 261 try:
262 262 lui = _ui.ui(parentui=ui)
263 263 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
264 264 except IOError:
265 265 pass
266 266
267 267 # now we can expand paths, even ones in .hg/hgrc
268 268 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
269 269 if rpath:
270 270 path = lui.expandpath(rpath[-1])
271 271 lui = _ui.ui(parentui=ui)
272 272 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
273 273
274 274 extensions.loadall(lui)
275 275 for name, module in extensions.extensions():
276 276 if name in _loaded:
277 277 continue
278 278
279 279 # setup extensions
280 280 # TODO this should be generalized to scheme, where extensions can
281 281 # redepend on other extensions. then we should toposort them, and
282 282 # do initialization in correct order
283 283 extsetup = getattr(module, 'extsetup', None)
284 284 if extsetup:
285 285 extsetup()
286 286
287 287 cmdtable = getattr(module, 'cmdtable', {})
288 288 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
289 289 if overrides:
290 290 ui.warn(_("extension '%s' overrides commands: %s\n")
291 291 % (name, " ".join(overrides)))
292 292 commands.table.update(cmdtable)
293 293 _loaded[name] = 1
294 294 # check for fallback encoding
295 295 fallback = lui.config('ui', 'fallbackencoding')
296 296 if fallback:
297 297 util._fallbackencoding = fallback
298 298
299 299 fullargs = args
300 300 cmd, func, args, options, cmdoptions = _parse(lui, args)
301 301
302 302 if options["config"]:
303 303 raise util.Abort(_("Option --config may not be abbreviated!"))
304 304 if options["cwd"]:
305 305 raise util.Abort(_("Option --cwd may not be abbreviated!"))
306 306 if options["repository"]:
307 307 raise util.Abort(_(
308 308 "Option -R has to be separated from other options (i.e. not -qR) "
309 309 "and --repository may only be abbreviated as --repo!"))
310 310
311 311 if options["encoding"]:
312 312 util._encoding = options["encoding"]
313 313 if options["encodingmode"]:
314 314 util._encodingmode = options["encodingmode"]
315 315 if options["time"]:
316 316 def get_times():
317 317 t = os.times()
318 318 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
319 319 t = (t[0], t[1], t[2], t[3], time.clock())
320 320 return t
321 321 s = get_times()
322 322 def print_time():
323 323 t = get_times()
324 324 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
325 325 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
326 326 atexit.register(print_time)
327 327
328 328 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
329 329 not options["noninteractive"], options["traceback"])
330 330
331 331 if options['help']:
332 332 return commands.help_(ui, cmd, options['version'])
333 333 elif options['version']:
334 334 return commands.version_(ui)
335 335 elif not cmd:
336 336 return commands.help_(ui, 'shortlist')
337 337
338 338 repo = None
339 339 if cmd not in commands.norepo.split():
340 340 try:
341 341 repo = hg.repository(ui, path=path)
342 342 ui = repo.ui
343 343 ui.setconfig("bundle", "mainreporoot", repo.root)
344 344 if not repo.local():
345 345 raise util.Abort(_("repository '%s' is not local") % path)
346 346 except hg.RepoError:
347 347 if cmd not in commands.optionalrepo.split():
348 348 if not path:
349 349 raise hg.RepoError(_("There is no Mercurial repository here"
350 350 " (.hg not found)"))
351 351 raise
352 352 d = lambda: func(ui, repo, *args, **cmdoptions)
353 353 else:
354 354 d = lambda: func(ui, *args, **cmdoptions)
355 355
356 356 # run pre-hook, and abort if it fails
357 ret = hook.hook(ui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
357 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
358 358 if ret:
359 359 return ret
360 360 ret = _runcommand(ui, options, cmd, d)
361 361 # run post-hook, passing command result
362 hook.hook(ui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
362 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
363 363 result = ret)
364 364 return ret
365 365
366 366 def _runcommand(ui, options, cmd, cmdfunc):
367 367 def checkargs():
368 368 try:
369 369 return cmdfunc()
370 370 except TypeError, inst:
371 371 # was this an argument error?
372 372 tb = traceback.extract_tb(sys.exc_info()[2])
373 373 if len(tb) != 2: # no
374 374 raise
375 375 raise ParseError(cmd, _("invalid arguments"))
376 376
377 377 if options['profile']:
378 378 import hotshot, hotshot.stats
379 379 prof = hotshot.Profile("hg.prof")
380 380 try:
381 381 try:
382 382 return prof.runcall(checkargs)
383 383 except:
384 384 try:
385 385 ui.warn(_('exception raised - generating '
386 386 'profile anyway\n'))
387 387 except:
388 388 pass
389 389 raise
390 390 finally:
391 391 prof.close()
392 392 stats = hotshot.stats.load("hg.prof")
393 393 stats.strip_dirs()
394 394 stats.sort_stats('time', 'calls')
395 395 stats.print_stats(40)
396 396 elif options['lsprof']:
397 397 try:
398 398 from mercurial import lsprof
399 399 except ImportError:
400 400 raise util.Abort(_(
401 401 'lsprof not available - install from '
402 402 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
403 403 p = lsprof.Profiler()
404 404 p.enable(subcalls=True)
405 405 try:
406 406 return checkargs()
407 407 finally:
408 408 p.disable()
409 409 stats = lsprof.Stats(p.getstats())
410 410 stats.sort()
411 411 stats.pprint(top=10, file=sys.stderr, climit=5)
412 412 else:
413 413 return checkargs()
@@ -1,109 +1,113
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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import util, os, sys
10 10
11 11 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
12 12 '''call python hook. hook is callable object, looked up as
13 13 name in python module. if callable returns "true", hook
14 14 fails, else passes. if hook raises exception, treated as
15 15 hook failure. exception propagates if throw is "true".
16 16
17 17 reason for "true" meaning "hook failed" is so that
18 18 unmodified commands (e.g. mercurial.commands.update) can
19 19 be run as hooks without wrappers to convert return values.'''
20 20
21 21 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
22 22 obj = funcname
23 23 if not callable(obj):
24 24 d = funcname.rfind('.')
25 25 if d == -1:
26 26 raise util.Abort(_('%s hook is invalid ("%s" not in '
27 27 'a module)') % (hname, funcname))
28 28 modname = funcname[:d]
29 29 try:
30 30 obj = __import__(modname)
31 31 except ImportError:
32 32 try:
33 33 # extensions are loaded with hgext_ prefix
34 34 obj = __import__("hgext_%s" % modname)
35 35 except ImportError:
36 36 raise util.Abort(_('%s hook is invalid '
37 37 '(import of "%s" failed)') %
38 38 (hname, modname))
39 39 try:
40 40 for p in funcname.split('.')[1:]:
41 41 obj = getattr(obj, p)
42 42 except AttributeError, err:
43 43 raise util.Abort(_('%s hook is invalid '
44 44 '("%s" is not defined)') %
45 45 (hname, funcname))
46 46 if not callable(obj):
47 47 raise util.Abort(_('%s hook is invalid '
48 48 '("%s" is not callable)') %
49 49 (hname, funcname))
50 50 try:
51 51 r = obj(ui=ui, repo=repo, hooktype=name, **args)
52 52 except (KeyboardInterrupt, util.SignalInterrupt):
53 53 raise
54 54 except Exception, exc:
55 55 if isinstance(exc, util.Abort):
56 56 ui.warn(_('error: %s hook failed: %s\n') %
57 57 (hname, exc.args[0]))
58 58 else:
59 59 ui.warn(_('error: %s hook raised an exception: '
60 60 '%s\n') % (hname, exc))
61 61 if throw:
62 62 raise
63 63 ui.print_exc()
64 64 return True
65 65 if r:
66 66 if throw:
67 67 raise util.Abort(_('%s hook failed') % hname)
68 68 ui.warn(_('warning: %s hook failed\n') % hname)
69 69 return r
70 70
71 71 def _exthook(ui, repo, name, cmd, args, throw):
72 72 ui.note(_("running hook %s: %s\n") % (name, cmd))
73 73 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
74 r = util.system(cmd, environ=env, cwd=repo.root)
74 if repo:
75 cwd = repo.root
76 else:
77 cwd = os.getcwd()
78 r = util.system(cmd, environ=env, cwd=cwd)
75 79 if r:
76 80 desc, r = util.explain_exit(r)
77 81 if throw:
78 82 raise util.Abort(_('%s hook %s') % (name, desc))
79 83 ui.warn(_('warning: %s hook %s\n') % (name, desc))
80 84 return r
81 85
82 86 _redirect = False
83 87 def redirect(state):
84 88 _redirect = state
85 89
86 90 def hook(ui, repo, name, throw=False, **args):
87 91 r = False
88 92
89 93 if _redirect:
90 94 # temporarily redirect stdout to stderr
91 95 oldstdout = os.dup(sys.stdout.fileno())
92 96 os.dup2(sys.stderr.fileno(), sys.stdout.fileno())
93 97
94 98 hooks = [(hname, cmd) for hname, cmd in ui.configitems("hooks")
95 99 if hname.split(".", 1)[0] == name and cmd]
96 100 hooks.sort()
97 101 for hname, cmd in hooks:
98 102 if callable(cmd):
99 103 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
100 104 elif cmd.startswith('python:'):
101 105 r = _pythonhook(ui, repo, name, hname, cmd[7:].strip(),
102 106 args, throw) or r
103 107 else:
104 108 r = _exthook(ui, repo, hname, cmd, args, throw) or r
105 109 return r
106 110
107 111 if _redirect:
108 112 os.dup2(oldstdout, sys.stdout.fileno())
109 113 os.close(oldstdout)
General Comments 0
You need to be logged in to leave comments. Login now