##// END OF EJS Templates
hook: small refactor to store hooks as dict instead of list...
Pierre-Yves David -
r28936:44bd37af default
parent child Browse files
Show More
@@ -1,244 +1,244 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, 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 = time.time()
91 91
92 92 try:
93 93 # redirect IO descriptors to the ui descriptors so hooks
94 94 # that write directly to these don't mess up the command
95 95 # protocol when running through the command server
96 96 old = sys.stdout, sys.stderr, sys.stdin
97 97 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
98 98
99 99 r = obj(ui=ui, repo=repo, hooktype=name, **args)
100 100 except Exception as exc:
101 101 if isinstance(exc, error.Abort):
102 102 ui.warn(_('error: %s hook failed: %s\n') %
103 103 (hname, exc.args[0]))
104 104 else:
105 105 ui.warn(_('error: %s hook raised an exception: '
106 106 '%s\n') % (hname, exc))
107 107 if throw:
108 108 raise
109 109 if not ui.tracebackflag:
110 110 ui.warn(_('(run with --traceback for stack trace)\n'))
111 111 ui.traceback()
112 112 return True, True
113 113 finally:
114 114 sys.stdout, sys.stderr, sys.stdin = old
115 115 duration = time.time() - starttime
116 116 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
117 117 name, funcname, duration)
118 118 if r:
119 119 if throw:
120 120 raise error.HookAbort(_('%s hook failed') % hname)
121 121 ui.warn(_('warning: %s hook failed\n') % hname)
122 122 return r, False
123 123
124 124 def _exthook(ui, repo, name, cmd, args, throw):
125 125 ui.note(_("running hook %s: %s\n") % (name, cmd))
126 126
127 127 starttime = time.time()
128 128 env = {}
129 129
130 130 # make in-memory changes visible to external process
131 131 if repo is not None:
132 132 tr = repo.currenttransaction()
133 133 repo.dirstate.write(tr)
134 134 if tr and tr.writepending():
135 135 env['HG_PENDING'] = repo.root
136 136
137 137 for k, v in args.iteritems():
138 138 if callable(v):
139 139 v = v()
140 140 if isinstance(v, dict):
141 141 # make the dictionary element order stable across Python
142 142 # implementations
143 143 v = ('{' +
144 144 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
145 145 '}')
146 146 env['HG_' + k.upper()] = v
147 147
148 148 if repo:
149 149 cwd = repo.root
150 150 else:
151 151 cwd = os.getcwd()
152 152 r = ui.system(cmd, environ=env, cwd=cwd)
153 153
154 154 duration = time.time() - starttime
155 155 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
156 156 name, cmd, duration)
157 157 if r:
158 158 desc, r = util.explainexit(r)
159 159 if throw:
160 160 raise error.HookAbort(_('%s hook %s') % (name, desc))
161 161 ui.warn(_('warning: %s hook %s\n') % (name, desc))
162 162 return r
163 163
164 164 def _allhooks(ui):
165 hooks = []
165 hooks = {}
166 166 for name, cmd in ui.configitems('hooks'):
167 167 if not name.startswith('priority'):
168 168 priority = ui.configint('hooks', 'priority.%s' % name, 0)
169 hooks.append((-priority, len(hooks), name, cmd))
170 return [(k, v) for p, o, k, v in sorted(hooks)]
169 hooks[name] = (-priority, len(hooks), name, cmd)
170 return [(k, v) for p, o, k, v in sorted(hooks.values())]
171 171
172 172 _redirect = False
173 173 def redirect(state):
174 174 global _redirect
175 175 _redirect = state
176 176
177 177 def hook(ui, repo, name, throw=False, **args):
178 178 if not ui.callhooks:
179 179 return False
180 180
181 181 hooks = []
182 182 for hname, cmd in _allhooks(ui):
183 183 if hname.split('.')[0] == name and cmd:
184 184 hooks.append((hname, cmd))
185 185
186 186 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
187 187 r = False
188 188 for hname, cmd in hooks:
189 189 r = res[hname][0] or r
190 190 return r
191 191
192 192 def runhooks(ui, repo, name, hooks, throw=False, **args):
193 193 res = {}
194 194 oldstdout = -1
195 195
196 196 try:
197 197 for hname, cmd in hooks:
198 198 if oldstdout == -1 and _redirect:
199 199 try:
200 200 stdoutno = sys.__stdout__.fileno()
201 201 stderrno = sys.__stderr__.fileno()
202 202 # temporarily redirect stdout to stderr, if possible
203 203 if stdoutno >= 0 and stderrno >= 0:
204 204 sys.__stdout__.flush()
205 205 oldstdout = os.dup(stdoutno)
206 206 os.dup2(stderrno, stdoutno)
207 207 except (OSError, AttributeError):
208 208 # files seem to be bogus, give up on redirecting (WSGI, etc)
209 209 pass
210 210
211 211 if callable(cmd):
212 212 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
213 213 elif cmd.startswith('python:'):
214 214 if cmd.count(':') >= 2:
215 215 path, cmd = cmd[7:].rsplit(':', 1)
216 216 path = util.expandpath(path)
217 217 if repo:
218 218 path = os.path.join(repo.root, path)
219 219 try:
220 220 mod = extensions.loadpath(path, 'hghook.%s' % hname)
221 221 except Exception:
222 222 ui.write(_("loading %s hook failed:\n") % hname)
223 223 raise
224 224 hookfn = getattr(mod, cmd)
225 225 else:
226 226 hookfn = cmd[7:].strip()
227 227 r, raised = _pythonhook(ui, repo, name, hname, hookfn, args,
228 228 throw)
229 229 else:
230 230 r = _exthook(ui, repo, hname, cmd, args, throw)
231 231 raised = False
232 232
233 233 res[hname] = r, raised
234 234
235 235 # The stderr is fully buffered on Windows when connected to a pipe.
236 236 # A forcible flush is required to make small stderr data in the
237 237 # remote side available to the client immediately.
238 238 sys.stderr.flush()
239 239 finally:
240 240 if _redirect and oldstdout >= 0:
241 241 os.dup2(oldstdout, stdoutno)
242 242 os.close(oldstdout)
243 243
244 244 return res
General Comments 0
You need to be logged in to leave comments. Login now