Mercurial > public > mercurial-scm > hg
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 |