##// END OF EJS Templates
hook: split config reading further...
Pierre-Yves David -
r28937:3112c5e1 default
parent child Browse files
Show More
@@ -1,244 +1,250 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 import time
12 import time
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 demandimport,
16 demandimport,
17 error,
17 error,
18 extensions,
18 extensions,
19 util,
19 util,
20 )
20 )
21
21
22 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
22 def _pythonhook(ui, repo, name, 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 = obj.__module__ + "." + 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 = time.time()
90 starttime = time.time()
91
91
92 try:
92 try:
93 # redirect IO descriptors to the ui descriptors so hooks
93 # redirect IO descriptors to the ui descriptors so hooks
94 # that write directly to these don't mess up the command
94 # that write directly to these don't mess up the command
95 # protocol when running through the command server
95 # protocol when running through the command server
96 old = sys.stdout, sys.stderr, sys.stdin
96 old = sys.stdout, sys.stderr, sys.stdin
97 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
97 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
98
98
99 r = obj(ui=ui, repo=repo, hooktype=name, **args)
99 r = obj(ui=ui, repo=repo, hooktype=name, **args)
100 except Exception as exc:
100 except Exception as exc:
101 if isinstance(exc, error.Abort):
101 if isinstance(exc, error.Abort):
102 ui.warn(_('error: %s hook failed: %s\n') %
102 ui.warn(_('error: %s hook failed: %s\n') %
103 (hname, exc.args[0]))
103 (hname, exc.args[0]))
104 else:
104 else:
105 ui.warn(_('error: %s hook raised an exception: '
105 ui.warn(_('error: %s hook raised an exception: '
106 '%s\n') % (hname, exc))
106 '%s\n') % (hname, exc))
107 if throw:
107 if throw:
108 raise
108 raise
109 if not ui.tracebackflag:
109 if not ui.tracebackflag:
110 ui.warn(_('(run with --traceback for stack trace)\n'))
110 ui.warn(_('(run with --traceback for stack trace)\n'))
111 ui.traceback()
111 ui.traceback()
112 return True, True
112 return True, True
113 finally:
113 finally:
114 sys.stdout, sys.stderr, sys.stdin = old
114 sys.stdout, sys.stderr, sys.stdin = old
115 duration = time.time() - starttime
115 duration = time.time() - starttime
116 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
116 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
117 name, funcname, duration)
117 name, funcname, duration)
118 if r:
118 if r:
119 if throw:
119 if throw:
120 raise error.HookAbort(_('%s hook failed') % hname)
120 raise error.HookAbort(_('%s hook failed') % hname)
121 ui.warn(_('warning: %s hook failed\n') % hname)
121 ui.warn(_('warning: %s hook failed\n') % hname)
122 return r, False
122 return r, False
123
123
124 def _exthook(ui, repo, name, cmd, args, throw):
124 def _exthook(ui, repo, name, cmd, args, throw):
125 ui.note(_("running hook %s: %s\n") % (name, cmd))
125 ui.note(_("running hook %s: %s\n") % (name, cmd))
126
126
127 starttime = time.time()
127 starttime = time.time()
128 env = {}
128 env = {}
129
129
130 # make in-memory changes visible to external process
130 # make in-memory changes visible to external process
131 if repo is not None:
131 if repo is not None:
132 tr = repo.currenttransaction()
132 tr = repo.currenttransaction()
133 repo.dirstate.write(tr)
133 repo.dirstate.write(tr)
134 if tr and tr.writepending():
134 if tr and tr.writepending():
135 env['HG_PENDING'] = repo.root
135 env['HG_PENDING'] = repo.root
136
136
137 for k, v in args.iteritems():
137 for k, v in args.iteritems():
138 if callable(v):
138 if callable(v):
139 v = v()
139 v = v()
140 if isinstance(v, dict):
140 if isinstance(v, dict):
141 # make the dictionary element order stable across Python
141 # make the dictionary element order stable across Python
142 # implementations
142 # implementations
143 v = ('{' +
143 v = ('{' +
144 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
144 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
145 '}')
145 '}')
146 env['HG_' + k.upper()] = v
146 env['HG_' + k.upper()] = v
147
147
148 if repo:
148 if repo:
149 cwd = repo.root
149 cwd = repo.root
150 else:
150 else:
151 cwd = os.getcwd()
151 cwd = os.getcwd()
152 r = ui.system(cmd, environ=env, cwd=cwd)
152 r = ui.system(cmd, environ=env, cwd=cwd)
153
153
154 duration = time.time() - starttime
154 duration = time.time() - starttime
155 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
155 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
156 name, cmd, duration)
156 name, cmd, duration)
157 if r:
157 if r:
158 desc, r = util.explainexit(r)
158 desc, r = util.explainexit(r)
159 if throw:
159 if throw:
160 raise error.HookAbort(_('%s hook %s') % (name, desc))
160 raise error.HookAbort(_('%s hook %s') % (name, desc))
161 ui.warn(_('warning: %s hook %s\n') % (name, desc))
161 ui.warn(_('warning: %s hook %s\n') % (name, desc))
162 return r
162 return r
163
163
164 def _allhooks(ui):
164 def _allhooks(ui):
165 """return a list of (hook-id, cmd) pairs sorted by priority"""
166 hooks = _hookitems(ui)
167 return [(k, v) for p, o, k, v in sorted(hooks.values())]
168
169 def _hookitems(ui):
170 """return all hooks items ready to be sorted"""
165 hooks = {}
171 hooks = {}
166 for name, cmd in ui.configitems('hooks'):
172 for name, cmd in ui.configitems('hooks'):
167 if not name.startswith('priority'):
173 if not name.startswith('priority'):
168 priority = ui.configint('hooks', 'priority.%s' % name, 0)
174 priority = ui.configint('hooks', 'priority.%s' % name, 0)
169 hooks[name] = (-priority, len(hooks), name, cmd)
175 hooks[name] = (-priority, len(hooks), name, cmd)
170 return [(k, v) for p, o, k, v in sorted(hooks.values())]
176 return hooks
171
177
172 _redirect = False
178 _redirect = False
173 def redirect(state):
179 def redirect(state):
174 global _redirect
180 global _redirect
175 _redirect = state
181 _redirect = state
176
182
177 def hook(ui, repo, name, throw=False, **args):
183 def hook(ui, repo, name, throw=False, **args):
178 if not ui.callhooks:
184 if not ui.callhooks:
179 return False
185 return False
180
186
181 hooks = []
187 hooks = []
182 for hname, cmd in _allhooks(ui):
188 for hname, cmd in _allhooks(ui):
183 if hname.split('.')[0] == name and cmd:
189 if hname.split('.')[0] == name and cmd:
184 hooks.append((hname, cmd))
190 hooks.append((hname, cmd))
185
191
186 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
192 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
187 r = False
193 r = False
188 for hname, cmd in hooks:
194 for hname, cmd in hooks:
189 r = res[hname][0] or r
195 r = res[hname][0] or r
190 return r
196 return r
191
197
192 def runhooks(ui, repo, name, hooks, throw=False, **args):
198 def runhooks(ui, repo, name, hooks, throw=False, **args):
193 res = {}
199 res = {}
194 oldstdout = -1
200 oldstdout = -1
195
201
196 try:
202 try:
197 for hname, cmd in hooks:
203 for hname, cmd in hooks:
198 if oldstdout == -1 and _redirect:
204 if oldstdout == -1 and _redirect:
199 try:
205 try:
200 stdoutno = sys.__stdout__.fileno()
206 stdoutno = sys.__stdout__.fileno()
201 stderrno = sys.__stderr__.fileno()
207 stderrno = sys.__stderr__.fileno()
202 # temporarily redirect stdout to stderr, if possible
208 # temporarily redirect stdout to stderr, if possible
203 if stdoutno >= 0 and stderrno >= 0:
209 if stdoutno >= 0 and stderrno >= 0:
204 sys.__stdout__.flush()
210 sys.__stdout__.flush()
205 oldstdout = os.dup(stdoutno)
211 oldstdout = os.dup(stdoutno)
206 os.dup2(stderrno, stdoutno)
212 os.dup2(stderrno, stdoutno)
207 except (OSError, AttributeError):
213 except (OSError, AttributeError):
208 # files seem to be bogus, give up on redirecting (WSGI, etc)
214 # files seem to be bogus, give up on redirecting (WSGI, etc)
209 pass
215 pass
210
216
211 if callable(cmd):
217 if callable(cmd):
212 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
218 r, raised = _pythonhook(ui, repo, name, hname, cmd, args, throw)
213 elif cmd.startswith('python:'):
219 elif cmd.startswith('python:'):
214 if cmd.count(':') >= 2:
220 if cmd.count(':') >= 2:
215 path, cmd = cmd[7:].rsplit(':', 1)
221 path, cmd = cmd[7:].rsplit(':', 1)
216 path = util.expandpath(path)
222 path = util.expandpath(path)
217 if repo:
223 if repo:
218 path = os.path.join(repo.root, path)
224 path = os.path.join(repo.root, path)
219 try:
225 try:
220 mod = extensions.loadpath(path, 'hghook.%s' % hname)
226 mod = extensions.loadpath(path, 'hghook.%s' % hname)
221 except Exception:
227 except Exception:
222 ui.write(_("loading %s hook failed:\n") % hname)
228 ui.write(_("loading %s hook failed:\n") % hname)
223 raise
229 raise
224 hookfn = getattr(mod, cmd)
230 hookfn = getattr(mod, cmd)
225 else:
231 else:
226 hookfn = cmd[7:].strip()
232 hookfn = cmd[7:].strip()
227 r, raised = _pythonhook(ui, repo, name, hname, hookfn, args,
233 r, raised = _pythonhook(ui, repo, name, hname, hookfn, args,
228 throw)
234 throw)
229 else:
235 else:
230 r = _exthook(ui, repo, hname, cmd, args, throw)
236 r = _exthook(ui, repo, hname, cmd, args, throw)
231 raised = False
237 raised = False
232
238
233 res[hname] = r, raised
239 res[hname] = r, raised
234
240
235 # The stderr is fully buffered on Windows when connected to a pipe.
241 # The stderr is fully buffered on Windows when connected to a pipe.
236 # A forcible flush is required to make small stderr data in the
242 # A forcible flush is required to make small stderr data in the
237 # remote side available to the client immediately.
243 # remote side available to the client immediately.
238 sys.stderr.flush()
244 sys.stderr.flush()
239 finally:
245 finally:
240 if _redirect and oldstdout >= 0:
246 if _redirect and oldstdout >= 0:
241 os.dup2(oldstdout, stdoutno)
247 os.dup2(oldstdout, stdoutno)
242 os.close(oldstdout)
248 os.close(oldstdout)
243
249
244 return res
250 return res
General Comments 0
You need to be logged in to leave comments. Login now