##// 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 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:
53 53 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
54 54 try:
55 55 # extensions are loaded with hgext_ prefix
56 56 obj = __import__("hgext_%s" % modname)
57 57 except ImportError:
58 58 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
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 raise error.HookLoadError(
68 68 _('%s hook is invalid (import of "%s" failed)') %
69 69 (hname, modname))
70 70 sys.path = oldpaths
71 71 try:
72 72 for p in funcname.split('.')[1:]:
73 73 obj = getattr(obj, p)
74 74 except AttributeError:
75 75 raise error.HookLoadError(
76 76 _('%s hook is invalid ("%s" is not defined)')
77 77 % (hname, funcname))
78 78 if not callable(obj):
79 79 raise error.HookLoadError(
80 80 _('%s hook is invalid ("%s" is not callable)')
81 81 % (hname, funcname))
82 82
83 83 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
84 84 starttime = time.time()
85 85
86 86 try:
87 87 # redirect IO descriptors to the ui descriptors so hooks
88 88 # that write directly to these don't mess up the command
89 89 # protocol when running through the command server
90 90 old = sys.stdout, sys.stderr, sys.stdin
91 91 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
92 92
93 93 r = obj(ui=ui, repo=repo, hooktype=name, **args)
94 94 except Exception as exc:
95 95 if isinstance(exc, error.Abort):
96 96 ui.warn(_('error: %s hook failed: %s\n') %
97 97 (hname, exc.args[0]))
98 98 else:
99 99 ui.warn(_('error: %s hook raised an exception: '
100 100 '%s\n') % (hname, exc))
101 101 if throw:
102 102 raise
103 103 ui.traceback()
104 return True
104 return True, True
105 105 finally:
106 106 sys.stdout, sys.stderr, sys.stdin = old
107 107 duration = time.time() - starttime
108 108 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
109 109 name, funcname, duration)
110 110 if r:
111 111 if throw:
112 112 raise error.HookAbort(_('%s hook failed') % hname)
113 113 ui.warn(_('warning: %s hook failed\n') % hname)
114 return r
114 return r, False
115 115
116 116 def _exthook(ui, repo, name, cmd, args, throw):
117 117 ui.note(_("running hook %s: %s\n") % (name, cmd))
118 118
119 119 starttime = time.time()
120 120 env = {}
121 121 for k, v in args.iteritems():
122 122 if callable(v):
123 123 v = v()
124 124 if isinstance(v, dict):
125 125 # make the dictionary element order stable across Python
126 126 # implementations
127 127 v = ('{' +
128 128 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
129 129 '}')
130 130 env['HG_' + k.upper()] = v
131 131
132 132 if repo:
133 133 cwd = repo.root
134 134 else:
135 135 cwd = os.getcwd()
136 136 r = ui.system(cmd, environ=env, cwd=cwd)
137 137
138 138 duration = time.time() - starttime
139 139 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
140 140 name, cmd, duration)
141 141 if r:
142 142 desc, r = util.explainexit(r)
143 143 if throw:
144 144 raise error.HookAbort(_('%s hook %s') % (name, desc))
145 145 ui.warn(_('warning: %s hook %s\n') % (name, desc))
146 146 return r
147 147
148 148 def _allhooks(ui):
149 149 hooks = []
150 150 for name, cmd in ui.configitems('hooks'):
151 151 if not name.startswith('priority'):
152 152 priority = ui.configint('hooks', 'priority.%s' % name, 0)
153 153 hooks.append((-priority, len(hooks), name, cmd))
154 154 return [(k, v) for p, o, k, v in sorted(hooks)]
155 155
156 156 _redirect = False
157 157 def redirect(state):
158 158 global _redirect
159 159 _redirect = state
160 160
161 161 def hook(ui, repo, name, throw=False, **args):
162 162 if not ui.callhooks:
163 163 return False
164 164
165 165 hooks = []
166 166 for hname, cmd in _allhooks(ui):
167 167 if hname.split('.')[0] == name and cmd:
168 168 hooks.append((hname, cmd))
169 169
170 170 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
171 171 r = False
172 172 for hname, cmd in hooks:
173 r = res[hname] or r
173 r = res[hname][0] or r
174 174 return r
175 175
176 176 def runhooks(ui, repo, name, hooks, throw=False, **args):
177 177 res = {}
178 178 oldstdout = -1
179 179
180 180 try:
181 181 for hname, cmd in hooks:
182 182 if oldstdout == -1 and _redirect:
183 183 try:
184 184 stdoutno = sys.__stdout__.fileno()
185 185 stderrno = sys.__stderr__.fileno()
186 186 # temporarily redirect stdout to stderr, if possible
187 187 if stdoutno >= 0 and stderrno >= 0:
188 188 sys.__stdout__.flush()
189 189 oldstdout = os.dup(stdoutno)
190 190 os.dup2(stderrno, stdoutno)
191 191 except (OSError, AttributeError):
192 192 # files seem to be bogus, give up on redirecting (WSGI, etc)
193 193 pass
194 194
195 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 197 elif cmd.startswith('python:'):
198 198 if cmd.count(':') >= 2:
199 199 path, cmd = cmd[7:].rsplit(':', 1)
200 200 path = util.expandpath(path)
201 201 if repo:
202 202 path = os.path.join(repo.root, path)
203 203 try:
204 204 mod = extensions.loadpath(path, 'hghook.%s' % hname)
205 205 except Exception:
206 206 ui.write(_("loading %s hook failed:\n") % hname)
207 207 raise
208 208 hookfn = getattr(mod, cmd)
209 209 else:
210 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 213 else:
213 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 219 # The stderr is fully buffered on Windows when connected to a pipe.
218 220 # A forcible flush is required to make small stderr data in the
219 221 # remote side available to the client immediately.
220 222 sys.stderr.flush()
221 223 finally:
222 224 if _redirect and oldstdout >= 0:
223 225 os.dup2(oldstdout, stdoutno)
224 226 os.close(oldstdout)
225 227
226 228 return res
General Comments 0
You need to be logged in to leave comments. Login now