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