##// END OF EJS Templates
hook.runhooks: return a dict of result values...
Siddharth Agarwal -
r26738:9abc2c92 default
parent child Browse files
Show More
@@ -1,220 +1,226 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:
52 except ImportError:
53 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
53 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
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:
57 except ImportError:
58 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
58 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
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 raise error.HookLoadError(
67 raise error.HookLoadError(
68 _('%s hook is invalid (import of "%s" failed)') %
68 _('%s hook is invalid (import of "%s" failed)') %
69 (hname, modname))
69 (hname, modname))
70 sys.path = oldpaths
70 sys.path = oldpaths
71 try:
71 try:
72 for p in funcname.split('.')[1:]:
72 for p in funcname.split('.')[1:]:
73 obj = getattr(obj, p)
73 obj = getattr(obj, p)
74 except AttributeError:
74 except AttributeError:
75 raise error.HookLoadError(
75 raise error.HookLoadError(
76 _('%s hook is invalid ("%s" is not defined)')
76 _('%s hook is invalid ("%s" is not defined)')
77 % (hname, funcname))
77 % (hname, funcname))
78 if not callable(obj):
78 if not callable(obj):
79 raise error.HookLoadError(
79 raise error.HookLoadError(
80 _('%s hook is invalid ("%s" is not callable)')
80 _('%s hook is invalid ("%s" is not callable)')
81 % (hname, funcname))
81 % (hname, funcname))
82
82
83 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
83 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
84 starttime = time.time()
84 starttime = time.time()
85
85
86 try:
86 try:
87 # redirect IO descriptors to the ui descriptors so hooks
87 # redirect IO descriptors to the ui descriptors so hooks
88 # that write directly to these don't mess up the command
88 # that write directly to these don't mess up the command
89 # protocol when running through the command server
89 # protocol when running through the command server
90 old = sys.stdout, sys.stderr, sys.stdin
90 old = sys.stdout, sys.stderr, sys.stdin
91 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
91 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
92
92
93 r = obj(ui=ui, repo=repo, hooktype=name, **args)
93 r = obj(ui=ui, repo=repo, hooktype=name, **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 ui.traceback()
103 ui.traceback()
104 return True
104 return True
105 finally:
105 finally:
106 sys.stdout, sys.stderr, sys.stdin = old
106 sys.stdout, sys.stderr, sys.stdin = old
107 duration = time.time() - starttime
107 duration = time.time() - starttime
108 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
108 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
109 name, funcname, duration)
109 name, funcname, duration)
110 if r:
110 if r:
111 if throw:
111 if throw:
112 raise error.HookAbort(_('%s hook failed') % hname)
112 raise error.HookAbort(_('%s hook failed') % hname)
113 ui.warn(_('warning: %s hook failed\n') % hname)
113 ui.warn(_('warning: %s hook failed\n') % hname)
114 return r
114 return r
115
115
116 def _exthook(ui, repo, name, cmd, args, throw):
116 def _exthook(ui, repo, name, cmd, args, throw):
117 ui.note(_("running hook %s: %s\n") % (name, cmd))
117 ui.note(_("running hook %s: %s\n") % (name, cmd))
118
118
119 starttime = time.time()
119 starttime = time.time()
120 env = {}
120 env = {}
121 for k, v in args.iteritems():
121 for k, v in args.iteritems():
122 if callable(v):
122 if callable(v):
123 v = v()
123 v = v()
124 if isinstance(v, dict):
124 if isinstance(v, dict):
125 # make the dictionary element order stable across Python
125 # make the dictionary element order stable across Python
126 # implementations
126 # implementations
127 v = ('{' +
127 v = ('{' +
128 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
128 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
129 '}')
129 '}')
130 env['HG_' + k.upper()] = v
130 env['HG_' + k.upper()] = v
131
131
132 if repo:
132 if repo:
133 cwd = repo.root
133 cwd = repo.root
134 else:
134 else:
135 cwd = os.getcwd()
135 cwd = os.getcwd()
136 r = ui.system(cmd, environ=env, cwd=cwd)
136 r = ui.system(cmd, environ=env, cwd=cwd)
137
137
138 duration = time.time() - starttime
138 duration = time.time() - starttime
139 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
139 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
140 name, cmd, duration)
140 name, cmd, duration)
141 if r:
141 if r:
142 desc, r = util.explainexit(r)
142 desc, r = util.explainexit(r)
143 if throw:
143 if throw:
144 raise error.HookAbort(_('%s hook %s') % (name, desc))
144 raise error.HookAbort(_('%s hook %s') % (name, desc))
145 ui.warn(_('warning: %s hook %s\n') % (name, desc))
145 ui.warn(_('warning: %s hook %s\n') % (name, desc))
146 return r
146 return r
147
147
148 def _allhooks(ui):
148 def _allhooks(ui):
149 hooks = []
149 hooks = []
150 for name, cmd in ui.configitems('hooks'):
150 for name, cmd in ui.configitems('hooks'):
151 if not name.startswith('priority'):
151 if not name.startswith('priority'):
152 priority = ui.configint('hooks', 'priority.%s' % name, 0)
152 priority = ui.configint('hooks', 'priority.%s' % name, 0)
153 hooks.append((-priority, len(hooks), name, cmd))
153 hooks.append((-priority, len(hooks), name, cmd))
154 return [(k, v) for p, o, k, v in sorted(hooks)]
154 return [(k, v) for p, o, k, v in sorted(hooks)]
155
155
156 _redirect = False
156 _redirect = False
157 def redirect(state):
157 def redirect(state):
158 global _redirect
158 global _redirect
159 _redirect = state
159 _redirect = state
160
160
161 def hook(ui, repo, name, throw=False, **args):
161 def hook(ui, repo, name, throw=False, **args):
162 if not ui.callhooks:
162 if not ui.callhooks:
163 return False
163 return False
164
164
165 hooks = []
165 hooks = []
166 for hname, cmd in _allhooks(ui):
166 for hname, cmd in _allhooks(ui):
167 if hname.split('.')[0] == name and cmd:
167 if hname.split('.')[0] == name and cmd:
168 hooks.append((hname, cmd))
168 hooks.append((hname, cmd))
169
169
170 return runhooks(ui, repo, name, hooks, throw=throw, **args)
170 res = runhooks(ui, repo, name, hooks, throw=throw, **args)
171 r = False
172 for hname, cmd in hooks:
173 r = res[hname] or r
174 return r
171
175
172 def runhooks(ui, repo, name, hooks, throw=False, **args):
176 def runhooks(ui, repo, name, hooks, throw=False, **args):
173 r = False
177 res = {}
174 oldstdout = -1
178 oldstdout = -1
175
179
176 try:
180 try:
177 for hname, cmd in hooks:
181 for hname, cmd in hooks:
178 if oldstdout == -1 and _redirect:
182 if oldstdout == -1 and _redirect:
179 try:
183 try:
180 stdoutno = sys.__stdout__.fileno()
184 stdoutno = sys.__stdout__.fileno()
181 stderrno = sys.__stderr__.fileno()
185 stderrno = sys.__stderr__.fileno()
182 # temporarily redirect stdout to stderr, if possible
186 # temporarily redirect stdout to stderr, if possible
183 if stdoutno >= 0 and stderrno >= 0:
187 if stdoutno >= 0 and stderrno >= 0:
184 sys.__stdout__.flush()
188 sys.__stdout__.flush()
185 oldstdout = os.dup(stdoutno)
189 oldstdout = os.dup(stdoutno)
186 os.dup2(stderrno, stdoutno)
190 os.dup2(stderrno, stdoutno)
187 except (OSError, AttributeError):
191 except (OSError, AttributeError):
188 # files seem to be bogus, give up on redirecting (WSGI, etc)
192 # files seem to be bogus, give up on redirecting (WSGI, etc)
189 pass
193 pass
190
194
191 if callable(cmd):
195 if callable(cmd):
192 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
196 r = _pythonhook(ui, repo, name, hname, cmd, args, throw)
193 elif cmd.startswith('python:'):
197 elif cmd.startswith('python:'):
194 if cmd.count(':') >= 2:
198 if cmd.count(':') >= 2:
195 path, cmd = cmd[7:].rsplit(':', 1)
199 path, cmd = cmd[7:].rsplit(':', 1)
196 path = util.expandpath(path)
200 path = util.expandpath(path)
197 if repo:
201 if repo:
198 path = os.path.join(repo.root, path)
202 path = os.path.join(repo.root, path)
199 try:
203 try:
200 mod = extensions.loadpath(path, 'hghook.%s' % hname)
204 mod = extensions.loadpath(path, 'hghook.%s' % hname)
201 except Exception:
205 except Exception:
202 ui.write(_("loading %s hook failed:\n") % hname)
206 ui.write(_("loading %s hook failed:\n") % hname)
203 raise
207 raise
204 hookfn = getattr(mod, cmd)
208 hookfn = getattr(mod, cmd)
205 else:
209 else:
206 hookfn = cmd[7:].strip()
210 hookfn = cmd[7:].strip()
207 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
211 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw)
208 else:
212 else:
209 r = _exthook(ui, repo, hname, cmd, args, throw) or r
213 r = _exthook(ui, repo, hname, cmd, args, throw)
214
215 res[hname] = r
210
216
211 # The stderr is fully buffered on Windows when connected to a pipe.
217 # The stderr is fully buffered on Windows when connected to a pipe.
212 # A forcible flush is required to make small stderr data in the
218 # A forcible flush is required to make small stderr data in the
213 # remote side available to the client immediately.
219 # remote side available to the client immediately.
214 sys.stderr.flush()
220 sys.stderr.flush()
215 finally:
221 finally:
216 if _redirect and oldstdout >= 0:
222 if _redirect and oldstdout >= 0:
217 os.dup2(oldstdout, stdoutno)
223 os.dup2(oldstdout, stdoutno)
218 os.close(oldstdout)
224 os.close(oldstdout)
219
225
220 return r
226 return res
General Comments 0
You need to be logged in to leave comments. Login now