##// END OF EJS Templates
py3: use system strings when calling __import__...
Gregory Szorc -
r36125:c4146cf4 default
parent child Browse files
Show More
@@ -1,279 +1,279
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 encoding,
17 17 error,
18 18 extensions,
19 19 pycompat,
20 20 util,
21 21 )
22 22
23 23 def _pythonhook(ui, repo, htype, hname, funcname, args, throw):
24 24 '''call python hook. hook is callable object, looked up as
25 25 name in python module. if callable returns "true", hook
26 26 fails, else passes. if hook raises exception, treated as
27 27 hook failure. exception propagates if throw is "true".
28 28
29 29 reason for "true" meaning "hook failed" is so that
30 30 unmodified commands (e.g. mercurial.commands.update) can
31 31 be run as hooks without wrappers to convert return values.'''
32 32
33 33 if callable(funcname):
34 34 obj = funcname
35 35 funcname = pycompat.sysbytes(obj.__module__ + r"." + obj.__name__)
36 36 else:
37 37 d = funcname.rfind('.')
38 38 if d == -1:
39 39 raise error.HookLoadError(
40 40 _('%s hook is invalid: "%s" not in a module')
41 41 % (hname, funcname))
42 42 modname = funcname[:d]
43 43 oldpaths = sys.path
44 44 if util.mainfrozen():
45 45 # binary installs require sys.path manipulation
46 46 modpath, modfile = os.path.split(modname)
47 47 if modpath and modfile:
48 48 sys.path = sys.path[:] + [modpath]
49 49 modname = modfile
50 50 with demandimport.deactivated():
51 51 try:
52 obj = __import__(modname)
52 obj = __import__(pycompat.sysstr(modname))
53 53 except (ImportError, SyntaxError):
54 54 e1 = sys.exc_info()
55 55 try:
56 56 # extensions are loaded with hgext_ prefix
57 obj = __import__("hgext_%s" % modname)
57 obj = __import__(r"hgext_%s" % pycompat.sysstr(modname))
58 58 except (ImportError, SyntaxError):
59 59 e2 = sys.exc_info()
60 60 if ui.tracebackflag:
61 61 ui.warn(_('exception from first failed import '
62 62 'attempt:\n'))
63 63 ui.traceback(e1)
64 64 if ui.tracebackflag:
65 65 ui.warn(_('exception from second failed import '
66 66 'attempt:\n'))
67 67 ui.traceback(e2)
68 68
69 69 if not ui.tracebackflag:
70 70 tracebackhint = _(
71 71 'run with --traceback for stack trace')
72 72 else:
73 73 tracebackhint = None
74 74 raise error.HookLoadError(
75 75 _('%s hook is invalid: import of "%s" failed') %
76 76 (hname, modname), hint=tracebackhint)
77 77 sys.path = oldpaths
78 78 try:
79 79 for p in funcname.split('.')[1:]:
80 80 obj = getattr(obj, p)
81 81 except AttributeError:
82 82 raise error.HookLoadError(
83 83 _('%s hook is invalid: "%s" is not defined')
84 84 % (hname, funcname))
85 85 if not callable(obj):
86 86 raise error.HookLoadError(
87 87 _('%s hook is invalid: "%s" is not callable')
88 88 % (hname, funcname))
89 89
90 90 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
91 91 starttime = util.timer()
92 92
93 93 try:
94 94 r = obj(ui=ui, repo=repo, hooktype=htype, **pycompat.strkwargs(args))
95 95 except Exception as exc:
96 96 if isinstance(exc, error.Abort):
97 97 ui.warn(_('error: %s hook failed: %s\n') %
98 98 (hname, exc.args[0]))
99 99 else:
100 100 ui.warn(_('error: %s hook raised an exception: '
101 101 '%s\n') % (hname, encoding.strtolocal(str(exc))))
102 102 if throw:
103 103 raise
104 104 if not ui.tracebackflag:
105 105 ui.warn(_('(run with --traceback for stack trace)\n'))
106 106 ui.traceback()
107 107 return True, True
108 108 finally:
109 109 duration = util.timer() - starttime
110 110 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
111 111 htype, funcname, duration)
112 112 if r:
113 113 if throw:
114 114 raise error.HookAbort(_('%s hook failed') % hname)
115 115 ui.warn(_('warning: %s hook failed\n') % hname)
116 116 return r, False
117 117
118 118 def _exthook(ui, repo, htype, name, cmd, args, throw):
119 119 ui.note(_("running hook %s: %s\n") % (name, cmd))
120 120
121 121 starttime = util.timer()
122 122 env = {}
123 123
124 124 # make in-memory changes visible to external process
125 125 if repo is not None:
126 126 tr = repo.currenttransaction()
127 127 repo.dirstate.write(tr)
128 128 if tr and tr.writepending():
129 129 env['HG_PENDING'] = repo.root
130 130 env['HG_HOOKTYPE'] = htype
131 131 env['HG_HOOKNAME'] = name
132 132
133 133 for k, v in args.iteritems():
134 134 if callable(v):
135 135 v = v()
136 136 if isinstance(v, dict):
137 137 # make the dictionary element order stable across Python
138 138 # implementations
139 139 v = ('{' +
140 140 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
141 141 '}')
142 142 env['HG_' + k.upper()] = v
143 143
144 144 if repo:
145 145 cwd = repo.root
146 146 else:
147 147 cwd = pycompat.getcwd()
148 148 r = ui.system(cmd, environ=env, cwd=cwd, blockedtag='exthook-%s' % (name,))
149 149
150 150 duration = util.timer() - starttime
151 151 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
152 152 name, cmd, duration)
153 153 if r:
154 154 desc, r = util.explainexit(r)
155 155 if throw:
156 156 raise error.HookAbort(_('%s hook %s') % (name, desc))
157 157 ui.warn(_('warning: %s hook %s\n') % (name, desc))
158 158 return r
159 159
160 160 # represent an untrusted hook command
161 161 _fromuntrusted = object()
162 162
163 163 def _allhooks(ui):
164 164 """return a list of (hook-id, cmd) pairs sorted by priority"""
165 165 hooks = _hookitems(ui)
166 166 # Be careful in this section, propagating the real commands from untrusted
167 167 # sources would create a security vulnerability, make sure anything altered
168 168 # in that section uses "_fromuntrusted" as its command.
169 169 untrustedhooks = _hookitems(ui, _untrusted=True)
170 170 for name, value in untrustedhooks.items():
171 171 trustedvalue = hooks.get(name, (None, None, name, _fromuntrusted))
172 172 if value != trustedvalue:
173 173 (lp, lo, lk, lv) = trustedvalue
174 174 hooks[name] = (lp, lo, lk, _fromuntrusted)
175 175 # (end of the security sensitive section)
176 176 return [(k, v) for p, o, k, v in sorted(hooks.values())]
177 177
178 178 def _hookitems(ui, _untrusted=False):
179 179 """return all hooks items ready to be sorted"""
180 180 hooks = {}
181 181 for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
182 182 if not name.startswith('priority'):
183 183 priority = ui.configint('hooks', 'priority.%s' % name, 0)
184 184 hooks[name] = (-priority, len(hooks), name, cmd)
185 185 return hooks
186 186
187 187 _redirect = False
188 188 def redirect(state):
189 189 global _redirect
190 190 _redirect = state
191 191
192 192 def hashook(ui, htype):
193 193 """return True if a hook is configured for 'htype'"""
194 194 if not ui.callhooks:
195 195 return False
196 196 for hname, cmd in _allhooks(ui):
197 197 if hname.split('.')[0] == htype and cmd:
198 198 return True
199 199 return False
200 200
201 201 def hook(ui, repo, htype, throw=False, **args):
202 202 if not ui.callhooks:
203 203 return False
204 204
205 205 hooks = []
206 206 for hname, cmd in _allhooks(ui):
207 207 if hname.split('.')[0] == htype and cmd:
208 208 hooks.append((hname, cmd))
209 209
210 210 res = runhooks(ui, repo, htype, hooks, throw=throw, **args)
211 211 r = False
212 212 for hname, cmd in hooks:
213 213 r = res[hname][0] or r
214 214 return r
215 215
216 216 def runhooks(ui, repo, htype, hooks, throw=False, **args):
217 217 args = pycompat.byteskwargs(args)
218 218 res = {}
219 219 oldstdout = -1
220 220
221 221 try:
222 222 for hname, cmd in hooks:
223 223 if oldstdout == -1 and _redirect:
224 224 try:
225 225 stdoutno = util.stdout.fileno()
226 226 stderrno = util.stderr.fileno()
227 227 # temporarily redirect stdout to stderr, if possible
228 228 if stdoutno >= 0 and stderrno >= 0:
229 229 util.stdout.flush()
230 230 oldstdout = os.dup(stdoutno)
231 231 os.dup2(stderrno, stdoutno)
232 232 except (OSError, AttributeError):
233 233 # files seem to be bogus, give up on redirecting (WSGI, etc)
234 234 pass
235 235
236 236 if cmd is _fromuntrusted:
237 237 if throw:
238 238 raise error.HookAbort(
239 239 _('untrusted hook %s not executed') % hname,
240 240 hint = _("see 'hg help config.trusted'"))
241 241 ui.warn(_('warning: untrusted hook %s not executed\n') % hname)
242 242 r = 1
243 243 raised = False
244 244 elif callable(cmd):
245 245 r, raised = _pythonhook(ui, repo, htype, hname, cmd, args,
246 246 throw)
247 247 elif cmd.startswith('python:'):
248 248 if cmd.count(':') >= 2:
249 249 path, cmd = cmd[7:].rsplit(':', 1)
250 250 path = util.expandpath(path)
251 251 if repo:
252 252 path = os.path.join(repo.root, path)
253 253 try:
254 254 mod = extensions.loadpath(path, 'hghook.%s' % hname)
255 255 except Exception:
256 256 ui.write(_("loading %s hook failed:\n") % hname)
257 257 raise
258 258 hookfn = getattr(mod, cmd)
259 259 else:
260 260 hookfn = cmd[7:].strip()
261 261 r, raised = _pythonhook(ui, repo, htype, hname, hookfn, args,
262 262 throw)
263 263 else:
264 264 r = _exthook(ui, repo, htype, hname, cmd, args, throw)
265 265 raised = False
266 266
267 267 res[hname] = r, raised
268 268
269 269 # The stderr is fully buffered on Windows when connected to a pipe.
270 270 # A forcible flush is required to make small stderr data in the
271 271 # remote side available to the client immediately.
272 272 util.stderr.flush()
273 273 finally:
274 274 if _redirect and oldstdout >= 0:
275 275 util.stdout.flush() # write hook output to stderr fd
276 276 os.dup2(oldstdout, stdoutno)
277 277 os.close(oldstdout)
278 278
279 279 return res
General Comments 0
You need to be logged in to leave comments. Login now