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