22 from .utils import ( |
22 from .utils import ( |
23 procutil, |
23 procutil, |
24 stringutil, |
24 stringutil, |
25 ) |
25 ) |
26 |
26 |
|
27 |
27 def pythonhook(ui, repo, htype, hname, funcname, args, throw): |
28 def pythonhook(ui, repo, htype, hname, funcname, args, throw): |
28 '''call python hook. hook is callable object, looked up as |
29 '''call python hook. hook is callable object, looked up as |
29 name in python module. if callable returns "true", hook |
30 name in python module. if callable returns "true", hook |
30 fails, else passes. if hook raises exception, treated as |
31 fails, else passes. if hook raises exception, treated as |
31 hook failure. exception propagates if throw is "true". |
32 hook failure. exception propagates if throw is "true". |
60 # extensions are loaded with hgext_ prefix |
62 # extensions are loaded with hgext_ prefix |
61 obj = __import__(r"hgext_%s" % pycompat.sysstr(modname)) |
63 obj = __import__(r"hgext_%s" % pycompat.sysstr(modname)) |
62 except (ImportError, SyntaxError): |
64 except (ImportError, SyntaxError): |
63 e2 = sys.exc_info() |
65 e2 = sys.exc_info() |
64 if ui.tracebackflag: |
66 if ui.tracebackflag: |
65 ui.warn(_('exception from first failed import ' |
67 ui.warn( |
66 'attempt:\n')) |
68 _( |
|
69 'exception from first failed import ' |
|
70 'attempt:\n' |
|
71 ) |
|
72 ) |
67 ui.traceback(e1) |
73 ui.traceback(e1) |
68 if ui.tracebackflag: |
74 if ui.tracebackflag: |
69 ui.warn(_('exception from second failed import ' |
75 ui.warn( |
70 'attempt:\n')) |
76 _( |
|
77 'exception from second failed import ' |
|
78 'attempt:\n' |
|
79 ) |
|
80 ) |
71 ui.traceback(e2) |
81 ui.traceback(e2) |
72 |
82 |
73 if not ui.tracebackflag: |
83 if not ui.tracebackflag: |
74 tracebackhint = _( |
84 tracebackhint = _( |
75 'run with --traceback for stack trace') |
85 'run with --traceback for stack trace' |
|
86 ) |
76 else: |
87 else: |
77 tracebackhint = None |
88 tracebackhint = None |
78 raise error.HookLoadError( |
89 raise error.HookLoadError( |
79 _('%s hook is invalid: import of "%s" failed') % |
90 _('%s hook is invalid: import of "%s" failed') |
80 (hname, modname), hint=tracebackhint) |
91 % (hname, modname), |
|
92 hint=tracebackhint, |
|
93 ) |
81 sys.path = oldpaths |
94 sys.path = oldpaths |
82 try: |
95 try: |
83 for p in funcname.split('.')[1:]: |
96 for p in funcname.split('.')[1:]: |
84 obj = getattr(obj, p) |
97 obj = getattr(obj, p) |
85 except AttributeError: |
98 except AttributeError: |
86 raise error.HookLoadError( |
99 raise error.HookLoadError( |
87 _('%s hook is invalid: "%s" is not defined') |
100 _('%s hook is invalid: "%s" is not defined') % (hname, funcname) |
88 % (hname, funcname)) |
101 ) |
89 if not callable(obj): |
102 if not callable(obj): |
90 raise error.HookLoadError( |
103 raise error.HookLoadError( |
91 _('%s hook is invalid: "%s" is not callable') |
104 _('%s hook is invalid: "%s" is not callable') |
92 % (hname, funcname)) |
105 % (hname, funcname) |
|
106 ) |
93 |
107 |
94 ui.note(_("calling hook %s: %s\n") % (hname, funcname)) |
108 ui.note(_("calling hook %s: %s\n") % (hname, funcname)) |
95 starttime = util.timer() |
109 starttime = util.timer() |
96 |
110 |
97 try: |
111 try: |
98 r = obj(ui=ui, repo=repo, hooktype=htype, **pycompat.strkwargs(args)) |
112 r = obj(ui=ui, repo=repo, hooktype=htype, **pycompat.strkwargs(args)) |
99 except Exception as exc: |
113 except Exception as exc: |
100 if isinstance(exc, error.Abort): |
114 if isinstance(exc, error.Abort): |
101 ui.warn(_('error: %s hook failed: %s\n') % |
115 ui.warn(_('error: %s hook failed: %s\n') % (hname, exc.args[0])) |
102 (hname, exc.args[0])) |
|
103 else: |
116 else: |
104 ui.warn(_('error: %s hook raised an exception: ' |
117 ui.warn( |
105 '%s\n') % (hname, stringutil.forcebytestr(exc))) |
118 _('error: %s hook raised an exception: ' '%s\n') |
|
119 % (hname, stringutil.forcebytestr(exc)) |
|
120 ) |
106 if throw: |
121 if throw: |
107 raise |
122 raise |
108 if not ui.tracebackflag: |
123 if not ui.tracebackflag: |
109 ui.warn(_('(run with --traceback for stack trace)\n')) |
124 ui.warn(_('(run with --traceback for stack trace)\n')) |
110 ui.traceback() |
125 ui.traceback() |
111 return True, True |
126 return True, True |
112 finally: |
127 finally: |
113 duration = util.timer() - starttime |
128 duration = util.timer() - starttime |
114 ui.log('pythonhook', 'pythonhook-%s: %s finished in %0.2f seconds\n', |
129 ui.log( |
115 htype, funcname, duration) |
130 'pythonhook', |
|
131 'pythonhook-%s: %s finished in %0.2f seconds\n', |
|
132 htype, |
|
133 funcname, |
|
134 duration, |
|
135 ) |
116 if r: |
136 if r: |
117 if throw: |
137 if throw: |
118 raise error.HookAbort(_('%s hook failed') % hname) |
138 raise error.HookAbort(_('%s hook failed') % hname) |
119 ui.warn(_('warning: %s hook failed\n') % hname) |
139 ui.warn(_('warning: %s hook failed\n') % hname) |
120 return r, False |
140 return r, False |
|
141 |
121 |
142 |
122 def _exthook(ui, repo, htype, name, cmd, args, throw): |
143 def _exthook(ui, repo, htype, name, cmd, args, throw): |
123 starttime = util.timer() |
144 starttime = util.timer() |
124 env = {} |
145 env = {} |
125 |
146 |
152 else: |
173 else: |
153 cwd = encoding.getcwd() |
174 cwd = encoding.getcwd() |
154 r = ui.system(cmd, environ=env, cwd=cwd, blockedtag='exthook-%s' % (name,)) |
175 r = ui.system(cmd, environ=env, cwd=cwd, blockedtag='exthook-%s' % (name,)) |
155 |
176 |
156 duration = util.timer() - starttime |
177 duration = util.timer() - starttime |
157 ui.log('exthook', 'exthook-%s: %s finished in %0.2f seconds\n', |
178 ui.log( |
158 name, cmd, duration) |
179 'exthook', |
|
180 'exthook-%s: %s finished in %0.2f seconds\n', |
|
181 name, |
|
182 cmd, |
|
183 duration, |
|
184 ) |
159 if r: |
185 if r: |
160 desc = procutil.explainexit(r) |
186 desc = procutil.explainexit(r) |
161 if throw: |
187 if throw: |
162 raise error.HookAbort(_('%s hook %s') % (name, desc)) |
188 raise error.HookAbort(_('%s hook %s') % (name, desc)) |
163 ui.warn(_('warning: %s hook %s\n') % (name, desc)) |
189 ui.warn(_('warning: %s hook %s\n') % (name, desc)) |
164 return r |
190 return r |
165 |
191 |
|
192 |
166 # represent an untrusted hook command |
193 # represent an untrusted hook command |
167 _fromuntrusted = object() |
194 _fromuntrusted = object() |
|
195 |
168 |
196 |
169 def _allhooks(ui): |
197 def _allhooks(ui): |
170 """return a list of (hook-id, cmd) pairs sorted by priority""" |
198 """return a list of (hook-id, cmd) pairs sorted by priority""" |
171 hooks = _hookitems(ui) |
199 hooks = _hookitems(ui) |
172 # Be careful in this section, propagating the real commands from untrusted |
200 # Be careful in this section, propagating the real commands from untrusted |
179 (lp, lo, lk, lv) = trustedvalue |
207 (lp, lo, lk, lv) = trustedvalue |
180 hooks[name] = (lp, lo, lk, _fromuntrusted) |
208 hooks[name] = (lp, lo, lk, _fromuntrusted) |
181 # (end of the security sensitive section) |
209 # (end of the security sensitive section) |
182 return [(k, v) for p, o, k, v in sorted(hooks.values())] |
210 return [(k, v) for p, o, k, v in sorted(hooks.values())] |
183 |
211 |
|
212 |
184 def _hookitems(ui, _untrusted=False): |
213 def _hookitems(ui, _untrusted=False): |
185 """return all hooks items ready to be sorted""" |
214 """return all hooks items ready to be sorted""" |
186 hooks = {} |
215 hooks = {} |
187 for name, cmd in ui.configitems('hooks', untrusted=_untrusted): |
216 for name, cmd in ui.configitems('hooks', untrusted=_untrusted): |
188 if name.startswith('priority.') or name.startswith('tonative.'): |
217 if name.startswith('priority.') or name.startswith('tonative.'): |
190 |
219 |
191 priority = ui.configint('hooks', 'priority.%s' % name, 0) |
220 priority = ui.configint('hooks', 'priority.%s' % name, 0) |
192 hooks[name] = (-priority, len(hooks), name, cmd) |
221 hooks[name] = (-priority, len(hooks), name, cmd) |
193 return hooks |
222 return hooks |
194 |
223 |
|
224 |
195 _redirect = False |
225 _redirect = False |
|
226 |
|
227 |
196 def redirect(state): |
228 def redirect(state): |
197 global _redirect |
229 global _redirect |
198 _redirect = state |
230 _redirect = state |
|
231 |
199 |
232 |
200 def hashook(ui, htype): |
233 def hashook(ui, htype): |
201 """return True if a hook is configured for 'htype'""" |
234 """return True if a hook is configured for 'htype'""" |
202 if not ui.callhooks: |
235 if not ui.callhooks: |
203 return False |
236 return False |
204 for hname, cmd in _allhooks(ui): |
237 for hname, cmd in _allhooks(ui): |
205 if hname.split('.')[0] == htype and cmd: |
238 if hname.split('.')[0] == htype and cmd: |
206 return True |
239 return True |
207 return False |
240 return False |
208 |
241 |
|
242 |
209 def hook(ui, repo, htype, throw=False, **args): |
243 def hook(ui, repo, htype, throw=False, **args): |
210 if not ui.callhooks: |
244 if not ui.callhooks: |
211 return False |
245 return False |
212 |
246 |
213 hooks = [] |
247 hooks = [] |
218 res = runhooks(ui, repo, htype, hooks, throw=throw, **args) |
252 res = runhooks(ui, repo, htype, hooks, throw=throw, **args) |
219 r = False |
253 r = False |
220 for hname, cmd in hooks: |
254 for hname, cmd in hooks: |
221 r = res[hname][0] or r |
255 r = res[hname][0] or r |
222 return r |
256 return r |
|
257 |
223 |
258 |
224 def runhooks(ui, repo, htype, hooks, throw=False, **args): |
259 def runhooks(ui, repo, htype, hooks, throw=False, **args): |
225 args = pycompat.byteskwargs(args) |
260 args = pycompat.byteskwargs(args) |
226 res = {} |
261 res = {} |
227 oldstdout = -1 |
262 oldstdout = -1 |
243 |
278 |
244 if cmd is _fromuntrusted: |
279 if cmd is _fromuntrusted: |
245 if throw: |
280 if throw: |
246 raise error.HookAbort( |
281 raise error.HookAbort( |
247 _('untrusted hook %s not executed') % hname, |
282 _('untrusted hook %s not executed') % hname, |
248 hint = _("see 'hg help config.trusted'")) |
283 hint=_("see 'hg help config.trusted'"), |
|
284 ) |
249 ui.warn(_('warning: untrusted hook %s not executed\n') % hname) |
285 ui.warn(_('warning: untrusted hook %s not executed\n') % hname) |
250 r = 1 |
286 r = 1 |
251 raised = False |
287 raised = False |
252 elif callable(cmd): |
288 elif callable(cmd): |
253 r, raised = pythonhook(ui, repo, htype, hname, cmd, args, |
289 r, raised = pythonhook(ui, repo, htype, hname, cmd, args, throw) |
254 throw) |
|
255 elif cmd.startswith('python:'): |
290 elif cmd.startswith('python:'): |
256 if cmd.count(':') >= 2: |
291 if cmd.count(':') >= 2: |
257 path, cmd = cmd[7:].rsplit(':', 1) |
292 path, cmd = cmd[7:].rsplit(':', 1) |
258 path = util.expandpath(path) |
293 path = util.expandpath(path) |
259 if repo: |
294 if repo: |
264 ui.write(_("loading %s hook failed:\n") % hname) |
299 ui.write(_("loading %s hook failed:\n") % hname) |
265 raise |
300 raise |
266 hookfn = getattr(mod, cmd) |
301 hookfn = getattr(mod, cmd) |
267 else: |
302 else: |
268 hookfn = cmd[7:].strip() |
303 hookfn = cmd[7:].strip() |
269 r, raised = pythonhook(ui, repo, htype, hname, hookfn, args, |
304 r, raised = pythonhook( |
270 throw) |
305 ui, repo, htype, hname, hookfn, args, throw |
|
306 ) |
271 else: |
307 else: |
272 r = _exthook(ui, repo, htype, hname, cmd, args, throw) |
308 r = _exthook(ui, repo, htype, hname, cmd, args, throw) |
273 raised = False |
309 raised = False |
274 |
310 |
275 res[hname] = r, raised |
311 res[hname] = r, raised |