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