comparison mercurial/hook.py @ 44652:3cbbfd0bfc17

hook: move stdio redirection to context manager The old code was checking stdio redirection in a loop. This didn't make sense. The pattern is better expressed as a context manager IMO, so this commit refactors it to be one. Differential Revision: https://phab.mercurial-scm.org/D8338
author Gregory Szorc <gregory.szorc@gmail.com>
date Sun, 29 Mar 2020 11:58:50 -0700
parents 664e24207728
children 50416d3d4b65 09da5cf44772
comparison
equal deleted inserted replaced
44651:00e0c5c06ed5 44652:3cbbfd0bfc17
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 __future__ import absolute_import 8 from __future__ import absolute_import
9 9
10 import contextlib
10 import os 11 import os
11 import sys 12 import sys
12 13
13 from .i18n import _ 14 from .i18n import _
14 from .pycompat import getattr 15 from .pycompat import getattr
257 for hname, cmd in hooks: 258 for hname, cmd in hooks:
258 r = res[hname][0] or r 259 r = res[hname][0] or r
259 return r 260 return r
260 261
261 262
263 @contextlib.contextmanager
264 def redirect_stdio():
265 """Redirects stdout to stderr, if possible."""
266
267 oldstdout = -1
268 try:
269 if _redirect:
270 try:
271 stdoutno = procutil.stdout.fileno()
272 stderrno = procutil.stderr.fileno()
273 # temporarily redirect stdout to stderr, if possible
274 if stdoutno >= 0 and stderrno >= 0:
275 procutil.stdout.flush()
276 oldstdout = os.dup(stdoutno)
277 os.dup2(stderrno, stdoutno)
278 except (OSError, AttributeError):
279 # files seem to be bogus, give up on redirecting (WSGI, etc)
280 pass
281
282 yield
283
284 finally:
285 # The stderr is fully buffered on Windows when connected to a pipe.
286 # A forcible flush is required to make small stderr data in the
287 # remote side available to the client immediately.
288 procutil.stderr.flush()
289
290 if _redirect and oldstdout >= 0:
291 procutil.stdout.flush() # write hook output to stderr fd
292 os.dup2(oldstdout, stdoutno)
293 os.close(oldstdout)
294
295
262 def runhooks(ui, repo, htype, hooks, throw=False, **args): 296 def runhooks(ui, repo, htype, hooks, throw=False, **args):
263 args = pycompat.byteskwargs(args) 297 args = pycompat.byteskwargs(args)
264 res = {} 298 res = {}
265 oldstdout = -1 299
266 300 with redirect_stdio():
267 try:
268 for hname, cmd in hooks: 301 for hname, cmd in hooks:
269 if oldstdout == -1 and _redirect:
270 try:
271 stdoutno = procutil.stdout.fileno()
272 stderrno = procutil.stderr.fileno()
273 # temporarily redirect stdout to stderr, if possible
274 if stdoutno >= 0 and stderrno >= 0:
275 procutil.stdout.flush()
276 oldstdout = os.dup(stdoutno)
277 os.dup2(stderrno, stdoutno)
278 except (OSError, AttributeError):
279 # files seem to be bogus, give up on redirecting (WSGI, etc)
280 pass
281
282 if cmd is _fromuntrusted: 302 if cmd is _fromuntrusted:
283 if throw: 303 if throw:
284 raise error.HookAbort( 304 raise error.HookAbort(
285 _(b'untrusted hook %s not executed') % hname, 305 _(b'untrusted hook %s not executed') % hname,
286 hint=_(b"see 'hg help config.trusted'"), 306 hint=_(b"see 'hg help config.trusted'"),
310 else: 330 else:
311 r = _exthook(ui, repo, htype, hname, cmd, args, throw) 331 r = _exthook(ui, repo, htype, hname, cmd, args, throw)
312 raised = False 332 raised = False
313 333
314 res[hname] = r, raised 334 res[hname] = r, raised
315 finally:
316 # The stderr is fully buffered on Windows when connected to a pipe.
317 # A forcible flush is required to make small stderr data in the
318 # remote side available to the client immediately.
319 procutil.stderr.flush()
320
321 if _redirect and oldstdout >= 0:
322 procutil.stdout.flush() # write hook output to stderr fd
323 os.dup2(oldstdout, stdoutno)
324 os.close(oldstdout)
325 335
326 return res 336 return res