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