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