##// END OF EJS Templates
hooks: be even more forgiven of non-fd descriptors (issue3711)...
Matt Mackall -
r17964:2c638967 stable
parent child Browse files
Show More
@@ -1,189 +1,189 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
9 import os, sys
10 import extensions, util
10 import extensions, util
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 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
22 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
23 obj = funcname
23 obj = funcname
24 if not util.safehasattr(obj, '__call__'):
24 if not util.safehasattr(obj, '__call__'):
25 d = funcname.rfind('.')
25 d = funcname.rfind('.')
26 if d == -1:
26 if d == -1:
27 raise util.Abort(_('%s hook is invalid ("%s" not in '
27 raise util.Abort(_('%s hook is invalid ("%s" not in '
28 'a module)') % (hname, funcname))
28 'a module)') % (hname, funcname))
29 modname = funcname[:d]
29 modname = funcname[:d]
30 oldpaths = sys.path
30 oldpaths = sys.path
31 if util.mainfrozen():
31 if util.mainfrozen():
32 # binary installs require sys.path manipulation
32 # binary installs require sys.path manipulation
33 modpath, modfile = os.path.split(modname)
33 modpath, modfile = os.path.split(modname)
34 if modpath and modfile:
34 if modpath and modfile:
35 sys.path = sys.path[:] + [modpath]
35 sys.path = sys.path[:] + [modpath]
36 modname = modfile
36 modname = modfile
37 try:
37 try:
38 obj = __import__(modname)
38 obj = __import__(modname)
39 except ImportError:
39 except ImportError:
40 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
40 e1 = sys.exc_type, sys.exc_value, sys.exc_traceback
41 try:
41 try:
42 # extensions are loaded with hgext_ prefix
42 # extensions are loaded with hgext_ prefix
43 obj = __import__("hgext_%s" % modname)
43 obj = __import__("hgext_%s" % modname)
44 except ImportError:
44 except ImportError:
45 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
45 e2 = sys.exc_type, sys.exc_value, sys.exc_traceback
46 if ui.tracebackflag:
46 if ui.tracebackflag:
47 ui.warn(_('exception from first failed import attempt:\n'))
47 ui.warn(_('exception from first failed import attempt:\n'))
48 ui.traceback(e1)
48 ui.traceback(e1)
49 if ui.tracebackflag:
49 if ui.tracebackflag:
50 ui.warn(_('exception from second failed import attempt:\n'))
50 ui.warn(_('exception from second failed import attempt:\n'))
51 ui.traceback(e2)
51 ui.traceback(e2)
52 raise util.Abort(_('%s hook is invalid '
52 raise util.Abort(_('%s hook is invalid '
53 '(import of "%s" failed)') %
53 '(import of "%s" failed)') %
54 (hname, modname))
54 (hname, modname))
55 sys.path = oldpaths
55 sys.path = oldpaths
56 try:
56 try:
57 for p in funcname.split('.')[1:]:
57 for p in funcname.split('.')[1:]:
58 obj = getattr(obj, p)
58 obj = getattr(obj, p)
59 except AttributeError:
59 except AttributeError:
60 raise util.Abort(_('%s hook is invalid '
60 raise util.Abort(_('%s hook is invalid '
61 '("%s" is not defined)') %
61 '("%s" is not defined)') %
62 (hname, funcname))
62 (hname, funcname))
63 if not util.safehasattr(obj, '__call__'):
63 if not util.safehasattr(obj, '__call__'):
64 raise util.Abort(_('%s hook is invalid '
64 raise util.Abort(_('%s hook is invalid '
65 '("%s" is not callable)') %
65 '("%s" is not callable)') %
66 (hname, funcname))
66 (hname, funcname))
67 try:
67 try:
68 try:
68 try:
69 # redirect IO descriptors to the ui descriptors so hooks
69 # redirect IO descriptors to the ui descriptors so hooks
70 # that write directly to these don't mess up the command
70 # that write directly to these don't mess up the command
71 # protocol when running through the command server
71 # protocol when running through the command server
72 old = sys.stdout, sys.stderr, sys.stdin
72 old = sys.stdout, sys.stderr, sys.stdin
73 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
73 sys.stdout, sys.stderr, sys.stdin = ui.fout, ui.ferr, ui.fin
74
74
75 r = obj(ui=ui, repo=repo, hooktype=name, **args)
75 r = obj(ui=ui, repo=repo, hooktype=name, **args)
76 except KeyboardInterrupt:
76 except KeyboardInterrupt:
77 raise
77 raise
78 except Exception, exc:
78 except Exception, exc:
79 if isinstance(exc, util.Abort):
79 if isinstance(exc, util.Abort):
80 ui.warn(_('error: %s hook failed: %s\n') %
80 ui.warn(_('error: %s hook failed: %s\n') %
81 (hname, exc.args[0]))
81 (hname, exc.args[0]))
82 else:
82 else:
83 ui.warn(_('error: %s hook raised an exception: '
83 ui.warn(_('error: %s hook raised an exception: '
84 '%s\n') % (hname, exc))
84 '%s\n') % (hname, exc))
85 if throw:
85 if throw:
86 raise
86 raise
87 ui.traceback()
87 ui.traceback()
88 return True
88 return True
89 finally:
89 finally:
90 sys.stdout, sys.stderr, sys.stdin = old
90 sys.stdout, sys.stderr, sys.stdin = old
91 if r:
91 if r:
92 if throw:
92 if throw:
93 raise util.Abort(_('%s hook failed') % hname)
93 raise util.Abort(_('%s hook failed') % hname)
94 ui.warn(_('warning: %s hook failed\n') % hname)
94 ui.warn(_('warning: %s hook failed\n') % hname)
95 return r
95 return r
96
96
97 def _exthook(ui, repo, name, cmd, args, throw):
97 def _exthook(ui, repo, name, cmd, args, throw):
98 ui.note(_("running hook %s: %s\n") % (name, cmd))
98 ui.note(_("running hook %s: %s\n") % (name, cmd))
99
99
100 env = {}
100 env = {}
101 for k, v in args.iteritems():
101 for k, v in args.iteritems():
102 if util.safehasattr(v, '__call__'):
102 if util.safehasattr(v, '__call__'):
103 v = v()
103 v = v()
104 if isinstance(v, dict):
104 if isinstance(v, dict):
105 # make the dictionary element order stable across Python
105 # make the dictionary element order stable across Python
106 # implementations
106 # implementations
107 v = ('{' +
107 v = ('{' +
108 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
108 ', '.join('%r: %r' % i for i in sorted(v.iteritems())) +
109 '}')
109 '}')
110 env['HG_' + k.upper()] = v
110 env['HG_' + k.upper()] = v
111
111
112 if repo:
112 if repo:
113 cwd = repo.root
113 cwd = repo.root
114 else:
114 else:
115 cwd = os.getcwd()
115 cwd = os.getcwd()
116 if 'HG_URL' in env and env['HG_URL'].startswith('remote:http'):
116 if 'HG_URL' in env and env['HG_URL'].startswith('remote:http'):
117 r = util.system(cmd, environ=env, cwd=cwd, out=ui)
117 r = util.system(cmd, environ=env, cwd=cwd, out=ui)
118 else:
118 else:
119 r = util.system(cmd, environ=env, cwd=cwd, out=ui.fout)
119 r = util.system(cmd, environ=env, cwd=cwd, out=ui.fout)
120 if r:
120 if r:
121 desc, r = util.explainexit(r)
121 desc, r = util.explainexit(r)
122 if throw:
122 if throw:
123 raise util.Abort(_('%s hook %s') % (name, desc))
123 raise util.Abort(_('%s hook %s') % (name, desc))
124 ui.warn(_('warning: %s hook %s\n') % (name, desc))
124 ui.warn(_('warning: %s hook %s\n') % (name, desc))
125 return r
125 return r
126
126
127 def _allhooks(ui):
127 def _allhooks(ui):
128 hooks = []
128 hooks = []
129 for name, cmd in ui.configitems('hooks'):
129 for name, cmd in ui.configitems('hooks'):
130 if not name.startswith('priority'):
130 if not name.startswith('priority'):
131 priority = ui.configint('hooks', 'priority.%s' % name, 0)
131 priority = ui.configint('hooks', 'priority.%s' % name, 0)
132 hooks.append((-priority, len(hooks), name, cmd))
132 hooks.append((-priority, len(hooks), name, cmd))
133 return [(k, v) for p, o, k, v in sorted(hooks)]
133 return [(k, v) for p, o, k, v in sorted(hooks)]
134
134
135 _redirect = False
135 _redirect = False
136 def redirect(state):
136 def redirect(state):
137 global _redirect
137 global _redirect
138 _redirect = state
138 _redirect = state
139
139
140 def hook(ui, repo, name, throw=False, **args):
140 def hook(ui, repo, name, throw=False, **args):
141 if not ui.callhooks:
141 if not ui.callhooks:
142 return False
142 return False
143
143
144 r = False
144 r = False
145 oldstdout = -1
145 oldstdout = -1
146
146
147 try:
147 try:
148 for hname, cmd in _allhooks(ui):
148 for hname, cmd in _allhooks(ui):
149 if hname.split('.')[0] != name or not cmd:
149 if hname.split('.')[0] != name or not cmd:
150 continue
150 continue
151
151
152 if oldstdout == -1 and _redirect:
152 if oldstdout == -1 and _redirect:
153 try:
153 try:
154 stdoutno = sys.__stdout__.fileno()
154 stdoutno = sys.__stdout__.fileno()
155 stderrno = sys.__stderr__.fileno()
155 stderrno = sys.__stderr__.fileno()
156 # temporarily redirect stdout to stderr, if possible
156 # temporarily redirect stdout to stderr, if possible
157 if stdoutno >= 0 and stderrno >= 0:
157 if stdoutno >= 0 and stderrno >= 0:
158 sys.__stdout__.flush()
158 sys.__stdout__.flush()
159 oldstdout = os.dup(stdoutno)
159 oldstdout = os.dup(stdoutno)
160 os.dup2(stderrno, stdoutno)
160 os.dup2(stderrno, stdoutno)
161 except AttributeError:
161 except (OSError, AttributeError):
162 # __stdout__/__stderr__ has no fileno(), not a real file
162 # files seem to be bogus, give up on redirecting (WSGI, etc)
163 pass
163 pass
164
164
165 if util.safehasattr(cmd, '__call__'):
165 if util.safehasattr(cmd, '__call__'):
166 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
166 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
167 elif cmd.startswith('python:'):
167 elif cmd.startswith('python:'):
168 if cmd.count(':') >= 2:
168 if cmd.count(':') >= 2:
169 path, cmd = cmd[7:].rsplit(':', 1)
169 path, cmd = cmd[7:].rsplit(':', 1)
170 path = util.expandpath(path)
170 path = util.expandpath(path)
171 if repo:
171 if repo:
172 path = os.path.join(repo.root, path)
172 path = os.path.join(repo.root, path)
173 try:
173 try:
174 mod = extensions.loadpath(path, 'hghook.%s' % hname)
174 mod = extensions.loadpath(path, 'hghook.%s' % hname)
175 except Exception:
175 except Exception:
176 ui.write(_("loading %s hook failed:\n") % hname)
176 ui.write(_("loading %s hook failed:\n") % hname)
177 raise
177 raise
178 hookfn = getattr(mod, cmd)
178 hookfn = getattr(mod, cmd)
179 else:
179 else:
180 hookfn = cmd[7:].strip()
180 hookfn = cmd[7:].strip()
181 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
181 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
182 else:
182 else:
183 r = _exthook(ui, repo, hname, cmd, args, throw) or r
183 r = _exthook(ui, repo, hname, cmd, args, throw) or r
184 finally:
184 finally:
185 if _redirect and oldstdout >= 0:
185 if _redirect and oldstdout >= 0:
186 os.dup2(oldstdout, stdoutno)
186 os.dup2(oldstdout, stdoutno)
187 os.close(oldstdout)
187 os.close(oldstdout)
188
188
189 return r
189 return r
General Comments 0
You need to be logged in to leave comments. Login now