##// 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 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 import time
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 demandimport,
17 17 error,
18 18 extensions,
19 19 util,
20 20 )
21 21
22 22 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
23 23 '''call python hook. hook is callable object, looked up as
24 24 name in python module. if callable returns "true", hook
25 25 fails, else passes. if hook raises exception, treated as
26 26 hook failure. exception propagates if throw is "true".
27 27
28 28 reason for "true" meaning "hook failed" is so that
29 29 unmodified commands (e.g. mercurial.commands.update) can
30 30 be run as hooks without wrappers to convert return values.'''
31 31
32 32 if callable(funcname):
33 33 obj = funcname
34 34 funcname = obj.__module__ + "." + obj.__name__
35 35 else:
36 36 d = funcname.rfind('.')
37 37 if d == -1:
38 38 raise error.HookLoadError(
39 39 _('%s hook is invalid ("%s" not in a module)')
40 40 % (hname, funcname))
41 41 modname = funcname[:d]
42 42 oldpaths = sys.path
43 43 if util.mainfrozen():
44 44 # binary installs require sys.path manipulation
45 45 modpath, modfile = os.path.split(modname)
46 46 if modpath and modfile:
47 47 sys.path = sys.path[:] + [modpath]
48 48 modname = modfile
49 49 with demandimport.deactivated():
50 50 try:
51 51 obj = __import__(modname)
52 52 except ImportError:
53 53 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
54 54 try:
55 55 # extensions are loaded with hgext_ prefix
56 56 obj = __import__("hgext_%s" % modname)
57 57 except ImportError:
58 58 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
59 59 if ui.tracebackflag:
60 60 ui.warn(_('exception from first failed import '
61 61 'attempt:\n'))
62 62 ui.traceback(e1)
63 63 if ui.tracebackflag:
64 64 ui.warn(_('exception from second failed import '
65 65 'attempt:\n'))
66 66 ui.traceback(e2)
67 67 raise error.HookLoadError(
68 68 _('%s hook is invalid (import of "%s" failed)') %
69 69 (hname, modname))
70 70 sys.path = oldpaths
71 71 try:
72 72 for p in funcname.split('.')[1:]:
73 73 obj = getattr(obj, p)
74 74 except AttributeError:
75 75 raise error.HookLoadError(
76 76 _('%s hook is invalid ("%s" is not defined)')
77 77 % (hname, funcname))
78 78 if not callable(obj):
79 79 raise error.HookLoadError(
80 80 _('%s hook is invalid ("%s" is not callable)')
81 81 % (hname, funcname))
82 82
83 83 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
84 84 starttime = time.time()
85 85
86 86 try:
87 87 # redirect IO descriptors to the ui descriptors so hooks
88 88 # that write directly to these don't mess up the command
89 89 # protocol when running through the command server
90 90 old = sys.stdout, sys.stderr, sys.stdin
91 91 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
92 92
93 93 r = obj(ui=ui, repo=repo, hooktype=name, **args)
94 94 except Exception as exc:
95 95 if isinstance(exc, error.Abort):
96 96 ui.warn(_('error: %s hook failed: %s\n') %
97 97 (hname, exc.args[0]))
98 98 else:
99 99 ui.warn(_('error: %s hook raised an exception: '
100 100 '%s\n') % (hname, exc))
101 101 if throw:
102 102 raise
103 103 ui.traceback()
104 104 return True
105 105 finally:
106 106 sys.stdout, sys.stderr, sys.stdin = old
107 107 duration = time.time() - starttime
108 108 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n',
109 109 name, funcname, duration)
110 110 if r:
111 111 if throw:
112 112 raise error.HookAbort(_('%s hook failed') % hname)
113 113 ui.warn(_('warning: %s hook failed\n') % hname)
114 114 return r
115 115
116 116 def _exthook(ui, repo, name, cmd, args, throw):
117 117 ui.note(_("running hook %s: %s\n") % (name, cmd))
118 118
119 119 starttime = time.time()
120 120 env = {}
121 121 for k, v in args.iteritems():
122 122 if callable(v):
123 123 v = v()
124 124 if isinstance(v, dict):
125 125 # make the dictionary element order stable across Python
126 126 # implementations
127 127 v = ('{' +
128 128 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
129 129 '}')
130 130 env['HG_' + k.upper()] = v
131 131
132 132 if repo:
133 133 cwd = repo.root
134 134 else:
135 135 cwd = os.getcwd()
136 136 r = ui.system(cmd, environ=env, cwd=cwd)
137 137
138 138 duration = time.time() - starttime
139 139 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n',
140 140 name, cmd, duration)
141 141 if r:
142 142 desc, r = util.explainexit(r)
143 143 if throw:
144 144 raise error.HookAbort(_('%s hook %s') % (name, desc))
145 145 ui.warn(_('warning: %s hook %s\n') % (name, desc))
146 146 return r
147 147
148 148 def _allhooks(ui):
149 149 hooks = []
150 150 for name, cmd in ui.configitems('hooks'):
151 151 if not name.startswith('priority'):
152 152 priority = ui.configint('hooks', 'priority.%s' % name, 0)
153 153 hooks.append((-priority, len(hooks), name, cmd))
154 154 return [(k, v) for p, o, k, v in sorted(hooks)]
155 155
156 156 _redirect = False
157 157 def redirect(state):
158 158 global _redirect
159 159 _redirect = state
160 160
161 161 def hook(ui, repo, name, throw=False, **args):
162 162 if not ui.callhooks:
163 163 return False
164 164
165 165 hooks = []
166 166 for hname, cmd in _allhooks(ui):
167 167 if hname.split('.')[0] == name and cmd:
168 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 176 def runhooks(ui, repo, name, hooks, throw=False, **args):
173 r = False
177 res = {}
174 178 oldstdout = -1
175 179
176 180 try:
177 181 for hname, cmd in hooks:
178 182 if oldstdout == -1 and _redirect:
179 183 try:
180 184 stdoutno = sys.__stdout__.fileno()
181 185 stderrno = sys.__stderr__.fileno()
182 186 # temporarily redirect stdout to stderr, if possible
183 187 if stdoutno >= 0 and stderrno >= 0:
184 188 sys.__stdout__.flush()
185 189 oldstdout = os.dup(stdoutno)
186 190 os.dup2(stderrno, stdoutno)
187 191 except (OSError, AttributeError):
188 192 # files seem to be bogus, give up on redirecting (WSGI, etc)
189 193 pass
190 194
191 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 197 elif cmd.startswith('python:'):
194 198 if cmd.count(':') >= 2:
195 199 path, cmd = cmd[7:].rsplit(':', 1)
196 200 path = util.expandpath(path)
197 201 if repo:
198 202 path = os.path.join(repo.root, path)
199 203 try:
200 204 mod = extensions.loadpath(path, 'hghook.%s' % hname)
201 205 except Exception:
202 206 ui.write(_("loading %s hook failed:\n") % hname)
203 207 raise
204 208 hookfn = getattr(mod, cmd)
205 209 else:
206 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 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 217 # The stderr is fully buffered on Windows when connected to a pipe.
212 218 # A forcible flush is required to make small stderr data in the
213 219 # remote side available to the client immediately.
214 220 sys.stderr.flush()
215 221 finally:
216 222 if _redirect and oldstdout >= 0:
217 223 os.dup2(oldstdout, stdoutno)
218 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