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