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