##// END OF EJS Templates
hook: for python hooks, also return whether an exception was raised...
Siddharth Agarwal -
r26739:8429369e default
parent child Browse files
Show More
@@ -1,226 +1,228 b''
1 # hook.py - hook support for mercurial
1 # hook.py - hook support for mercurial
2 #
2 #
3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import sys
11 import sys
12 import time
12 import time
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 demandimport,
16 demandimport,
17 error,
17 error,
18 extensions,
18 extensions,
19 util,
19 util,
20 )
20 )
21
21
22 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
22 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
23 '''call python hook. hook is callable object, looked up as
23 '''call python hook. hook is callable object, looked up as
24 name in python module. if callable returns "true", hook
24 name in python module. if callable returns "true", hook
25 fails, else passes. if hook raises exception, treated as
25 fails, else passes. if hook raises exception, treated as
26 hook failure. exception propagates if throw is "true".
26 hook failure. exception propagates if throw is "true".
27
27
28 reason for "true" meaning "hook failed" is so that
28 reason for "true" meaning "hook failed" is so that
29 unmodified commands (e.g. mercurial.commands.update) can
29 unmodified commands (e.g. mercurial.commands.update) can
30 be run as hooks without wrappers to convert return values.'''
30 be run as hooks without wrappers to convert return values.'''
31
31
32 if callable(funcname):
32 if callable(funcname):
33 obj = funcname
33 obj = funcname
34 funcname = obj.__module__ + "." + obj.__name__
34 funcname = obj.__module__ + "." + obj.__name__
35 else:
35 else:
36 d = funcname.rfind('.')
36 d = funcname.rfind('.')
37 if d == -1:
37 if d == -1:
38 raise error.HookLoadError(
38 raise error.HookLoadError(
39 _('%s hook is invalid ("%s" not in a module)')
39 _('%s hook is invalid ("%s" not in a module)')
40 % (hname, funcname))
40 % (hname, funcname))
41 modname = funcname[:d]
41 modname = funcname[:d]
42 oldpaths = sys.path
42 oldpaths = sys.path
43 if util.mainfrozen():
43 if util.mainfrozen():
44 # binary installs require sys.path manipulation
44 # binary installs require sys.path manipulation
45 modpath, modfile = os.path.split(modname)
45 modpath, modfile = os.path.split(modname)
46 if modpath and modfile:
46 if modpath and modfile:
47 sys.path = sys.path[:] + [modpath]
47 sys.path = sys.path[:] + [modpath]
48 modname = modfile
48 modname = modfile
49 with demandimport.deactivated():
49 with demandimport.deactivated():
50 try:
50 try:
51 obj = __import__(modname)
51 obj = __import__(modname)
52 except ImportError:
52 except ImportError:
53 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
53 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
54 try:
54 try:
55 # extensions are loaded with hgext_ prefix
55 # extensions are loaded with hgext_ prefix
56 obj = __import__("hgext_%s" % modname)
56 obj = __import__("hgext_%s" % modname)
57 except ImportError:
57 except ImportError:
58 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
58 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
59 if ui.tracebackflag:
59 if ui.tracebackflag:
60 ui.warn(_('exception from first failed import '
60 ui.warn(_('exception from first failed import '
61 'attempt:\n'))
61 'attempt:\n'))
62 ui.traceback(e1)
62 ui.traceback(e1)
63 if ui.tracebackflag:
63 if ui.tracebackflag:
64 ui.warn(_('exception from second failed import '
64 ui.warn(_('exception from second failed import '
65 'attempt:\n'))
65 'attempt:\n'))
66 ui.traceback(e2)
66 ui.traceback(e2)
67 raise error.HookLoadError(
67 raise error.HookLoadError(
68 _('%s hook is invalid (import of "%s" failed)') %
68 _('%s hook is invalid (import of "%s" failed)') %
69 (hname, modname))
69 (hname, modname))
70 sys.path = oldpaths
70 sys.path = oldpaths
71 try:
71 try:
72 for p in funcname.split('.')[1:]:
72 for p in funcname.split('.')[1:]:
73 obj = getattr(obj, p)
73 obj = getattr(obj, p)
74 except AttributeError:
74 except AttributeError:
75 raise error.HookLoadError(
75 raise error.HookLoadError(
76 _('%s hook is invalid ("%s" is not defined)')
76 _('%s hook is invalid ("%s" is not defined)')
77 % (hname, funcname))
77 % (hname, funcname))
78 if not callable(obj):
78 if not callable(obj):
79 raise error.HookLoadError(
79 raise error.HookLoadError(
80 _('%s hook is invalid ("%s" is not callable)')
80 _('%s hook is invalid ("%s" is not callable)')
81 % (hname, funcname))
81 % (hname, funcname))
82
82
83 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
83 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
84 starttime = time.time()
84 starttime = time.time()
85
85
86 try:
86 try:
87 # redirect IO descriptors to the ui descriptors so hooks
87 # redirect IO descriptors to the ui descriptors so hooks
88 # that write directly to these don't mess up the command
88 # that write directly to these don't mess up the command
89 # protocol when running through the command server
89 # protocol when running through the command server
90 old = sys.stdout, sys.stderr, sys.stdin
90 old = sys.stdout, sys.stderr, sys.stdin
91 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
91 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
92
92
93 r = obj(ui=ui, repo=repo, hooktype=name, **args)
93 r = obj(ui=ui, repo=repo, hooktype=name, **args)
94 except Exception as exc:
94 except Exception as exc:
95 if isinstance(exc, error.Abort):
95 if isinstance(exc, error.Abort):
96 ui.warn(_('error: %s hook failed: %s\n') %
96 ui.warn(_('error: %s hook failed: %s\n') %
97 (hname, exc.args[0]))
97 (hname, exc.args[0]))
98 else:
98 else:
99 ui.warn(_('error: %s hook raised an exception: '
99 ui.warn(_('error: %s hook raised an exception: '
100 '%s\n') % (hname, exc))
100 '%s\n') % (hname, exc))
101 if throw:
101 if throw:
102 raise
102 raise
103 ui.traceback()
103 ui.traceback()
104 return True
104 return True, True
105 finally:
105 finally:
106 sys.stdout, sys.stderr, sys.stdin = old
106 sys.stdout, sys.stderr, sys.stdin = old
107 duration = time.time() - starttime
107 duration = time.time() - starttime
108 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
108 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
109 name, funcname, duration)
109 name, funcname, duration)
110 if r:
110 if r:
111 if throw:
111 if throw:
112 raise error.HookAbort(_('%s hook failed') % hname)
112 raise error.HookAbort(_('%s hook failed') % hname)
113 ui.warn(_('warning: %s hook failed\n') % hname)
113 ui.warn(_('warning: %s hook failed\n') % hname)
114 return r
114 return r, False
115
115
116 def _exthook(ui, repo, name, cmd, args, throw):
116 def _exthook(ui, repo, name, cmd, args, throw):
117 ui.note(_("running hook %s: %s\n") % (name, cmd))
117 ui.note(_("running hook %s: %s\n") % (name, cmd))
118
118
119 starttime = time.time()
119 starttime = time.time()
120 env = {}
120 env = {}
121 for k, v in args.iteritems():
121 for k, v in args.iteritems():
122 if callable(v):
122 if callable(v):
123 v = v()
123 v = v()
124 if isinstance(v, dict):
124 if isinstance(v, dict):
125 # make the dictionary element order stable across Python
125 # make the dictionary element order stable across Python
126 # implementations
126 # implementations
127 v = ('{' +
127 v = ('{' +
128 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
128 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
129 '}')
129 '}')
130 env['HG_' + k.upper()] = v
130 env['HG_' + k.upper()] = v
131
131
132 if repo:
132 if repo:
133 cwd = repo.root
133 cwd = repo.root
134 else:
134 else:
135 cwd = os.getcwd()
135 cwd = os.getcwd()
136 r = ui.system(cmd, environ=env, cwd=cwd)
136 r = ui.system(cmd, environ=env, cwd=cwd)
137
137
138 duration = time.time() - starttime
138 duration = time.time() - starttime
139 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
139 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
140 name, cmd, duration)
140 name, cmd, duration)
141 if r:
141 if r:
142 desc, r = util.explainexit(r)
142 desc, r = util.explainexit(r)
143 if throw:
143 if throw:
144 raise error.HookAbort(_('%s hook %s') % (name, desc))
144 raise error.HookAbort(_('%s hook %s') % (name, desc))
145 ui.warn(_('warning: %s hook %s\n') % (name, desc))
145 ui.warn(_('warning: %s hook %s\n') % (name, desc))
146 return r
146 return r
147
147
148 def _allhooks(ui):
148 def _allhooks(ui):
149 hooks = []
149 hooks = []
150 for name, cmd in ui.configitems('hooks'):
150 for name, cmd in ui.configitems('hooks'):
151 if not name.startswith('priority'):
151 if not name.startswith('priority'):
152 priority = ui.configint('hooks', 'priority.%s' % name, 0)
152 priority = ui.configint('hooks', 'priority.%s' % name, 0)
153 hooks.append((-priority, len(hooks), name, cmd))
153 hooks.append((-priority, len(hooks), name, cmd))
154 return [(k, v) for p, o, k, v in sorted(hooks)]
154 return [(k, v) for p, o, k, v in sorted(hooks)]
155
155
156 _redirect = False
156 _redirect = False
157 def redirect(state):
157 def redirect(state):
158 global _redirect
158 global _redirect
159 _redirect = state
159 _redirect = state
160
160
161 def hook(ui, repo, name, throw=False, **args):
161 def hook(ui, repo, name, throw=False, **args):
162 if not ui.callhooks:
162 if not ui.callhooks:
163 return False
163 return False
164
164
165 hooks = []
165 hooks = []
166 for hname, cmd in _allhooks(ui):
166 for hname, cmd in _allhooks(ui):
167 if hname.split('.')[0] == name and cmd:
167 if hname.split('.')[0] == name and cmd:
168 hooks.append((hname, cmd))
168 hooks.append((hname, cmd))
169
169
170 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
170 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
171 r = False
171 r = False
172 for hname, cmd in hooks:
172 for hname, cmd in hooks:
173 r = res[hname] or r
173 r = res[hname][0] or r
174 return r
174 return r
175
175
176 def runhooks(ui, repo, name, hooks, throw=False, **args):
176 def runhooks(ui, repo, name, hooks, throw=False, **args):
177 res = {}
177 res = {}
178 oldstdout = -1
178 oldstdout = -1
179
179
180 try:
180 try:
181 for hname, cmd in hooks:
181 for hname, cmd in hooks:
182 if oldstdout == -1 and _redirect:
182 if oldstdout == -1 and _redirect:
183 try:
183 try:
184 stdoutno = sys.__stdout__.fileno()
184 stdoutno = sys.__stdout__.fileno()
185 stderrno = sys.__stderr__.fileno()
185 stderrno = sys.__stderr__.fileno()
186 # temporarily redirect stdout to stderr, if possible
186 # temporarily redirect stdout to stderr, if possible
187 if stdoutno >= 0 and stderrno >= 0:
187 if stdoutno >= 0 and stderrno >= 0:
188 sys.__stdout__.flush()
188 sys.__stdout__.flush()
189 oldstdout = os.dup(stdoutno)
189 oldstdout = os.dup(stdoutno)
190 os.dup2(stderrno, stdoutno)
190 os.dup2(stderrno, stdoutno)
191 except (OSError, AttributeError):
191 except (OSError, AttributeError):
192 # files seem to be bogus, give up on redirecting (WSGI, etc)
192 # files seem to be bogus, give up on redirecting (WSGI, etc)
193 pass
193 pass
194
194
195 if callable(cmd):
195 if callable(cmd):
196 r = _pythonhook(ui, repo, name, hname, cmd, args, throw)
196 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
197 elif cmd.startswith('python:'):
197 elif cmd.startswith('python:'):
198 if cmd.count(':') >= 2:
198 if cmd.count(':') >= 2:
199 path, cmd = cmd[7:].rsplit(':', 1)
199 path, cmd = cmd[7:].rsplit(':', 1)
200 path = util.expandpath(path)
200 path = util.expandpath(path)
201 if repo:
201 if repo:
202 path = os.path.join(repo.root, path)
202 path = os.path.join(repo.root, path)
203 try:
203 try:
204 mod = extensions.loadpath(path, 'hghook.%s' % hname)
204 mod = extensions.loadpath(path, 'hghook.%s' % hname)
205 except Exception:
205 except Exception:
206 ui.write(_("loading %s hook failed:\n") % hname)
206 ui.write(_("loading %s hook failed:\n") % hname)
207 raise
207 raise
208 hookfn = getattr(mod, cmd)
208 hookfn = getattr(mod, cmd)
209 else:
209 else:
210 hookfn = cmd[7:].strip()
210 hookfn = cmd[7:].strip()
211 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw)
211 r, raised = _pythonhook(ui, repo, name, hname, hookfn, args,
212 throw)
212 else:
213 else:
213 r = _exthook(ui, repo, hname, cmd, args, throw)
214 r = _exthook(ui, repo, hname, cmd, args, throw)
215 raised = False
214
216
215 res[hname] = r
217 res[hname] = r, raised
216
218
217 # The stderr is fully buffered on Windows when connected to a pipe.
219 # The stderr is fully buffered on Windows when connected to a pipe.
218 # A forcible flush is required to make small stderr data in the
220 # A forcible flush is required to make small stderr data in the
219 # remote side available to the client immediately.
221 # remote side available to the client immediately.
220 sys.stderr.flush()
222 sys.stderr.flush()
221 finally:
223 finally:
222 if _redirect and oldstdout >= 0:
224 if _redirect and oldstdout >= 0:
223 os.dup2(oldstdout, stdoutno)
225 os.dup2(oldstdout, stdoutno)
224 os.close(oldstdout)
226 os.close(oldstdout)
225
227
226 return res
228 return res
General Comments 0
You need to be logged in to leave comments. Login now