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