##// END OF EJS Templates
hook: split config reading further...
Pierre-Yves David -
r28937:3112c5e1 default
parent child Browse files
Show More
@@ -1,244 +1,250 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 """return a list of (hook-id, cmd) pairs sorted by priority"""
166 hooks = _hookitems(ui)
167 return [(k, v) for p, o, k, v in sorted(hooks.values())]
168
169 def _hookitems(ui):
170 """return all hooks items ready to be sorted"""
165 171 hooks = {}
166 172 for name, cmd in ui.configitems('hooks'):
167 173 if not name.startswith('priority'):
168 174 priority = ui.configint('hooks', 'priority.%s' % name, 0)
169 175 hooks[name] = (-priority, len(hooks), name, cmd)
170 return [(k, v) for p, o, k, v in sorted(hooks.values())]
176 return hooks
171 177
172 178 _redirect = False
173 179 def redirect(state):
174 180 global _redirect
175 181 _redirect = state
176 182
177 183 def hook(ui, repo, name, throw=False, **args):
178 184 if not ui.callhooks:
179 185 return False
180 186
181 187 hooks = []
182 188 for hname, cmd in _allhooks(ui):
183 189 if hname.split('.')[0] == name and cmd:
184 190 hooks.append((hname, cmd))
185 191
186 192 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
187 193 r = False
188 194 for hname, cmd in hooks:
189 195 r = res[hname][0] or r
190 196 return r
191 197
192 198 def runhooks(ui, repo, name, hooks, throw=False, **args):
193 199 res = {}
194 200 oldstdout = -1
195 201
196 202 try:
197 203 for hname, cmd in hooks:
198 204 if oldstdout == -1 and _redirect:
199 205 try:
200 206 stdoutno = sys.__stdout__.fileno()
201 207 stderrno = sys.__stderr__.fileno()
202 208 # temporarily redirect stdout to stderr, if possible
203 209 if stdoutno >= 0 and stderrno >= 0:
204 210 sys.__stdout__.flush()
205 211 oldstdout = os.dup(stdoutno)
206 212 os.dup2(stderrno, stdoutno)
207 213 except (OSError, AttributeError):
208 214 # files seem to be bogus, give up on redirecting (WSGI, etc)
209 215 pass
210 216
211 217 if callable(cmd):
212 218 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
213 219 elif cmd.startswith('python:'):
214 220 if cmd.count(':') >= 2:
215 221 path, cmd = cmd[7:].rsplit(':', 1)
216 222 path = util.expandpath(path)
217 223 if repo:
218 224 path = os.path.join(repo.root, path)
219 225 try:
220 226 mod = extensions.loadpath(path, 'hghook.%s' % hname)
221 227 except Exception:
222 228 ui.write(_("loading %s hook failed:\n") % hname)
223 229 raise
224 230 hookfn = getattr(mod, cmd)
225 231 else:
226 232 hookfn = cmd[7:].strip()
227 233 r, raised = _pythonhook(ui, repo, name, hname, hookfn, args,
228 234 throw)
229 235 else:
230 236 r = _exthook(ui, repo, hname, cmd, args, throw)
231 237 raised = False
232 238
233 239 res[hname] = r, raised
234 240
235 241 # The stderr is fully buffered on Windows when connected to a pipe.
236 242 # A forcible flush is required to make small stderr data in the
237 243 # remote side available to the client immediately.
238 244 sys.stderr.flush()
239 245 finally:
240 246 if _redirect and oldstdout >= 0:
241 247 os.dup2(oldstdout, stdoutno)
242 248 os.close(oldstdout)
243 249
244 250 return res
General Comments 0
You need to be logged in to leave comments. Login now