##// END OF EJS Templates
hook: give exthooks tags for blocking time...
Simon Farnsworth -
r31205:a48c6ac5 default
parent child Browse files
Show More
@@ -1,265 +1,265
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
13 13 from .i18n import _
14 14 from . import (
15 15 demandimport,
16 16 error,
17 17 extensions,
18 18 pycompat,
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 = util.timer()
91 91
92 92 try:
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 if not ui.tracebackflag:
104 104 ui.warn(_('(run with --traceback for stack trace)\n'))
105 105 ui.traceback()
106 106 return True, True
107 107 finally:
108 108 duration = util.timer() - starttime
109 109 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
110 110 name, funcname, duration)
111 111 if r:
112 112 if throw:
113 113 raise error.HookAbort(_('%s hook failed') % hname)
114 114 ui.warn(_('warning: %s hook failed\n') % hname)
115 115 return r, False
116 116
117 117 def _exthook(ui, repo, name, cmd, args, throw):
118 118 ui.note(_("running hook %s: %s\n") % (name, cmd))
119 119
120 120 starttime = util.timer()
121 121 env = {}
122 122
123 123 # make in-memory changes visible to external process
124 124 if repo is not None:
125 125 tr = repo.currenttransaction()
126 126 repo.dirstate.write(tr)
127 127 if tr and tr.writepending():
128 128 env['HG_PENDING'] = repo.root
129 129
130 130 for k, v in args.iteritems():
131 131 if callable(v):
132 132 v = v()
133 133 if isinstance(v, dict):
134 134 # make the dictionary element order stable across Python
135 135 # implementations
136 136 v = ('{' +
137 137 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
138 138 '}')
139 139 env['HG_' + k.upper()] = v
140 140
141 141 if repo:
142 142 cwd = repo.root
143 143 else:
144 144 cwd = pycompat.getcwd()
145 r = ui.system(cmd, environ=env, cwd=cwd)
145 r = ui.system(cmd, environ=env, cwd=cwd, blockedtag='exthook-%s' % (name,))
146 146
147 147 duration = util.timer() - starttime
148 148 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
149 149 name, cmd, duration)
150 150 if r:
151 151 desc, r = util.explainexit(r)
152 152 if throw:
153 153 raise error.HookAbort(_('%s hook %s') % (name, desc))
154 154 ui.warn(_('warning: %s hook %s\n') % (name, desc))
155 155 return r
156 156
157 157 # represent an untrusted hook command
158 158 _fromuntrusted = object()
159 159
160 160 def _allhooks(ui):
161 161 """return a list of (hook-id, cmd) pairs sorted by priority"""
162 162 hooks = _hookitems(ui)
163 163 # Be careful in this section, propagating the real commands from untrusted
164 164 # sources would create a security vulnerability, make sure anything altered
165 165 # in that section uses "_fromuntrusted" as its command.
166 166 untrustedhooks = _hookitems(ui, _untrusted=True)
167 167 for name, value in untrustedhooks.items():
168 168 trustedvalue = hooks.get(name, (None, None, name, _fromuntrusted))
169 169 if value != trustedvalue:
170 170 (lp, lo, lk, lv) = trustedvalue
171 171 hooks[name] = (lp, lo, lk, _fromuntrusted)
172 172 # (end of the security sensitive section)
173 173 return [(k, v) for p, o, k, v in sorted(hooks.values())]
174 174
175 175 def _hookitems(ui, _untrusted=False):
176 176 """return all hooks items ready to be sorted"""
177 177 hooks = {}
178 178 for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
179 179 if not name.startswith('priority'):
180 180 priority = ui.configint('hooks', 'priority.%s' % name, 0)
181 181 hooks[name] = (-priority, len(hooks), name, cmd)
182 182 return hooks
183 183
184 184 _redirect = False
185 185 def redirect(state):
186 186 global _redirect
187 187 _redirect = state
188 188
189 189 def hook(ui, repo, name, throw=False, **args):
190 190 if not ui.callhooks:
191 191 return False
192 192
193 193 hooks = []
194 194 for hname, cmd in _allhooks(ui):
195 195 if hname.split('.')[0] == name and cmd:
196 196 hooks.append((hname, cmd))
197 197
198 198 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
199 199 r = False
200 200 for hname, cmd in hooks:
201 201 r = res[hname][0] or r
202 202 return r
203 203
204 204 def runhooks(ui, repo, name, hooks, throw=False, **args):
205 205 res = {}
206 206 oldstdout = -1
207 207
208 208 try:
209 209 for hname, cmd in hooks:
210 210 if oldstdout == -1 and _redirect:
211 211 try:
212 212 stdoutno = util.stdout.fileno()
213 213 stderrno = util.stderr.fileno()
214 214 # temporarily redirect stdout to stderr, if possible
215 215 if stdoutno >= 0 and stderrno >= 0:
216 216 util.stdout.flush()
217 217 oldstdout = os.dup(stdoutno)
218 218 os.dup2(stderrno, stdoutno)
219 219 except (OSError, AttributeError):
220 220 # files seem to be bogus, give up on redirecting (WSGI, etc)
221 221 pass
222 222
223 223 if cmd is _fromuntrusted:
224 224 if throw:
225 225 raise error.HookAbort(
226 226 _('untrusted hook %s not executed') % name,
227 227 hint = _("see 'hg help config.trusted'"))
228 228 ui.warn(_('warning: untrusted hook %s not executed\n') % name)
229 229 r = 1
230 230 raised = False
231 231 elif callable(cmd):
232 232 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
233 233 elif cmd.startswith('python:'):
234 234 if cmd.count(':') >= 2:
235 235 path, cmd = cmd[7:].rsplit(':', 1)
236 236 path = util.expandpath(path)
237 237 if repo:
238 238 path = os.path.join(repo.root, path)
239 239 try:
240 240 mod = extensions.loadpath(path, 'hghook.%s' % hname)
241 241 except Exception:
242 242 ui.write(_("loading %s hook failed:\n") % hname)
243 243 raise
244 244 hookfn = getattr(mod, cmd)
245 245 else:
246 246 hookfn = cmd[7:].strip()
247 247 r, raised = _pythonhook(ui, repo, name, hname, hookfn, args,
248 248 throw)
249 249 else:
250 250 r = _exthook(ui, repo, hname, cmd, args, throw)
251 251 raised = False
252 252
253 253 res[hname] = r, raised
254 254
255 255 # The stderr is fully buffered on Windows when connected to a pipe.
256 256 # A forcible flush is required to make small stderr data in the
257 257 # remote side available to the client immediately.
258 258 util.stderr.flush()
259 259 finally:
260 260 if _redirect and oldstdout >= 0:
261 261 util.stdout.flush() # write hook output to stderr fd
262 262 os.dup2(oldstdout, stdoutno)
263 263 os.close(oldstdout)
264 264
265 265 return res
General Comments 0
You need to be logged in to leave comments. Login now