##// END OF EJS Templates
procutil: split if condition...
Manuel Jacob -
r45640:00cdac66 default
parent child Browse files
Show More
@@ -1,719 +1,721 b''
1 1 # procutil.py - utility for managing processes and executable environment
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import contextlib
13 13 import errno
14 14 import io
15 15 import os
16 16 import signal
17 17 import subprocess
18 18 import sys
19 19 import threading
20 20 import time
21 21
22 22 from ..i18n import _
23 23 from ..pycompat import (
24 24 getattr,
25 25 open,
26 26 )
27 27
28 28 from .. import (
29 29 encoding,
30 30 error,
31 31 policy,
32 32 pycompat,
33 33 )
34 34
35 35 # Import like this to keep import-checker happy
36 36 from ..utils import resourceutil
37 37
38 38 osutil = policy.importmod('osutil')
39 39
40 40 if pycompat.iswindows:
41 41 from .. import windows as platform
42 42 else:
43 43 from .. import posix as platform
44 44
45 45
46 46 def isatty(fp):
47 47 try:
48 48 return fp.isatty()
49 49 except AttributeError:
50 50 return False
51 51
52 52
53 53 class LineBufferedWrapper(object):
54 54 def __init__(self, orig):
55 55 self.orig = orig
56 56
57 57 def __getattr__(self, attr):
58 58 return getattr(self.orig, attr)
59 59
60 60 def write(self, s):
61 61 orig = self.orig
62 62 res = orig.write(s)
63 63 if s.endswith(b'\n'):
64 64 orig.flush()
65 65 return res
66 66
67 67
68 68 io.BufferedIOBase.register(LineBufferedWrapper)
69 69
70 70
71 71 def make_line_buffered(stream):
72 72 if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
73 73 # On Python 3, buffered streams can be expected to subclass
74 74 # BufferedIOBase. This is definitively the case for the streams
75 75 # initialized by the interpreter. For unbuffered streams, we don't need
76 76 # to emulate line buffering.
77 77 return stream
78 78 if isinstance(stream, LineBufferedWrapper):
79 79 return stream
80 80 return LineBufferedWrapper(stream)
81 81
82 82
83 83 if pycompat.ispy3:
84 84 # TODO: .buffer might not exist if std streams were replaced; we'll need
85 85 # a silly wrapper to make a bytes stream backed by a unicode one.
86 86 stdin = sys.stdin.buffer
87 87 stdout = sys.stdout.buffer
88 88 stderr = sys.stderr.buffer
89 89 else:
90 90 stdin = sys.stdin
91 91 stdout = sys.stdout
92 92 stderr = sys.stderr
93 93
94 94 if pycompat.iswindows:
95 95 stdout = platform.winstdout(stdout)
96 96
97 97 # glibc determines buffering on first write to stdout - if we replace a TTY
98 98 # destined stdout with a pipe destined stdout (e.g. pager), we want line
99 99 # buffering.
100 100 if isatty(stdout):
101 if pycompat.ispy3 or pycompat.iswindows:
101 if pycompat.ispy3:
102 102 # Python 3 implements its own I/O streams.
103 103 # The standard library doesn't offer line-buffered binary streams.
104 stdout = make_line_buffered(stdout)
105 elif pycompat.iswindows:
104 106 # Python 2 uses the I/O streams provided by the C library.
105 107 # The Windows C runtime library doesn't support line buffering.
106 108 stdout = make_line_buffered(stdout)
107 109 else:
108 110 stdout = os.fdopen(stdout.fileno(), 'wb', 1)
109 111
110 112
111 113 findexe = platform.findexe
112 114 _gethgcmd = platform.gethgcmd
113 115 getuser = platform.getuser
114 116 getpid = os.getpid
115 117 hidewindow = platform.hidewindow
116 118 readpipe = platform.readpipe
117 119 setbinary = platform.setbinary
118 120 setsignalhandler = platform.setsignalhandler
119 121 shellquote = platform.shellquote
120 122 shellsplit = platform.shellsplit
121 123 spawndetached = platform.spawndetached
122 124 sshargs = platform.sshargs
123 125 testpid = platform.testpid
124 126
125 127 try:
126 128 setprocname = osutil.setprocname
127 129 except AttributeError:
128 130 pass
129 131 try:
130 132 unblocksignal = osutil.unblocksignal
131 133 except AttributeError:
132 134 pass
133 135
134 136 closefds = pycompat.isposix
135 137
136 138
137 139 def explainexit(code):
138 140 """return a message describing a subprocess status
139 141 (codes from kill are negative - not os.system/wait encoding)"""
140 142 if code >= 0:
141 143 return _(b"exited with status %d") % code
142 144 return _(b"killed by signal %d") % -code
143 145
144 146
145 147 class _pfile(object):
146 148 """File-like wrapper for a stream opened by subprocess.Popen()"""
147 149
148 150 def __init__(self, proc, fp):
149 151 self._proc = proc
150 152 self._fp = fp
151 153
152 154 def close(self):
153 155 # unlike os.popen(), this returns an integer in subprocess coding
154 156 self._fp.close()
155 157 return self._proc.wait()
156 158
157 159 def __iter__(self):
158 160 return iter(self._fp)
159 161
160 162 def __getattr__(self, attr):
161 163 return getattr(self._fp, attr)
162 164
163 165 def __enter__(self):
164 166 return self
165 167
166 168 def __exit__(self, exc_type, exc_value, exc_tb):
167 169 self.close()
168 170
169 171
170 172 def popen(cmd, mode=b'rb', bufsize=-1):
171 173 if mode == b'rb':
172 174 return _popenreader(cmd, bufsize)
173 175 elif mode == b'wb':
174 176 return _popenwriter(cmd, bufsize)
175 177 raise error.ProgrammingError(b'unsupported mode: %r' % mode)
176 178
177 179
178 180 def _popenreader(cmd, bufsize):
179 181 p = subprocess.Popen(
180 182 tonativestr(cmd),
181 183 shell=True,
182 184 bufsize=bufsize,
183 185 close_fds=closefds,
184 186 stdout=subprocess.PIPE,
185 187 )
186 188 return _pfile(p, p.stdout)
187 189
188 190
189 191 def _popenwriter(cmd, bufsize):
190 192 p = subprocess.Popen(
191 193 tonativestr(cmd),
192 194 shell=True,
193 195 bufsize=bufsize,
194 196 close_fds=closefds,
195 197 stdin=subprocess.PIPE,
196 198 )
197 199 return _pfile(p, p.stdin)
198 200
199 201
200 202 def popen2(cmd, env=None):
201 203 # Setting bufsize to -1 lets the system decide the buffer size.
202 204 # The default for bufsize is 0, meaning unbuffered. This leads to
203 205 # poor performance on Mac OS X: http://bugs.python.org/issue4194
204 206 p = subprocess.Popen(
205 207 tonativestr(cmd),
206 208 shell=True,
207 209 bufsize=-1,
208 210 close_fds=closefds,
209 211 stdin=subprocess.PIPE,
210 212 stdout=subprocess.PIPE,
211 213 env=tonativeenv(env),
212 214 )
213 215 return p.stdin, p.stdout
214 216
215 217
216 218 def popen3(cmd, env=None):
217 219 stdin, stdout, stderr, p = popen4(cmd, env)
218 220 return stdin, stdout, stderr
219 221
220 222
221 223 def popen4(cmd, env=None, bufsize=-1):
222 224 p = subprocess.Popen(
223 225 tonativestr(cmd),
224 226 shell=True,
225 227 bufsize=bufsize,
226 228 close_fds=closefds,
227 229 stdin=subprocess.PIPE,
228 230 stdout=subprocess.PIPE,
229 231 stderr=subprocess.PIPE,
230 232 env=tonativeenv(env),
231 233 )
232 234 return p.stdin, p.stdout, p.stderr, p
233 235
234 236
235 237 def pipefilter(s, cmd):
236 238 '''filter string S through command CMD, returning its output'''
237 239 p = subprocess.Popen(
238 240 tonativestr(cmd),
239 241 shell=True,
240 242 close_fds=closefds,
241 243 stdin=subprocess.PIPE,
242 244 stdout=subprocess.PIPE,
243 245 )
244 246 pout, perr = p.communicate(s)
245 247 return pout
246 248
247 249
248 250 def tempfilter(s, cmd):
249 251 '''filter string S through a pair of temporary files with CMD.
250 252 CMD is used as a template to create the real command to be run,
251 253 with the strings INFILE and OUTFILE replaced by the real names of
252 254 the temporary files generated.'''
253 255 inname, outname = None, None
254 256 try:
255 257 infd, inname = pycompat.mkstemp(prefix=b'hg-filter-in-')
256 258 fp = os.fdopen(infd, 'wb')
257 259 fp.write(s)
258 260 fp.close()
259 261 outfd, outname = pycompat.mkstemp(prefix=b'hg-filter-out-')
260 262 os.close(outfd)
261 263 cmd = cmd.replace(b'INFILE', inname)
262 264 cmd = cmd.replace(b'OUTFILE', outname)
263 265 code = system(cmd)
264 266 if pycompat.sysplatform == b'OpenVMS' and code & 1:
265 267 code = 0
266 268 if code:
267 269 raise error.Abort(
268 270 _(b"command '%s' failed: %s") % (cmd, explainexit(code))
269 271 )
270 272 with open(outname, b'rb') as fp:
271 273 return fp.read()
272 274 finally:
273 275 try:
274 276 if inname:
275 277 os.unlink(inname)
276 278 except OSError:
277 279 pass
278 280 try:
279 281 if outname:
280 282 os.unlink(outname)
281 283 except OSError:
282 284 pass
283 285
284 286
285 287 _filtertable = {
286 288 b'tempfile:': tempfilter,
287 289 b'pipe:': pipefilter,
288 290 }
289 291
290 292
291 293 def filter(s, cmd):
292 294 """filter a string through a command that transforms its input to its
293 295 output"""
294 296 for name, fn in pycompat.iteritems(_filtertable):
295 297 if cmd.startswith(name):
296 298 return fn(s, cmd[len(name) :].lstrip())
297 299 return pipefilter(s, cmd)
298 300
299 301
300 302 _hgexecutable = None
301 303
302 304
303 305 def hgexecutable():
304 306 """return location of the 'hg' executable.
305 307
306 308 Defaults to $HG or 'hg' in the search path.
307 309 """
308 310 if _hgexecutable is None:
309 311 hg = encoding.environ.get(b'HG')
310 312 mainmod = sys.modules['__main__']
311 313 if hg:
312 314 _sethgexecutable(hg)
313 315 elif resourceutil.mainfrozen():
314 316 if getattr(sys, 'frozen', None) == 'macosx_app':
315 317 # Env variable set by py2app
316 318 _sethgexecutable(encoding.environ[b'EXECUTABLEPATH'])
317 319 else:
318 320 _sethgexecutable(pycompat.sysexecutable)
319 321 elif (
320 322 not pycompat.iswindows
321 323 and os.path.basename(getattr(mainmod, '__file__', '')) == 'hg'
322 324 ):
323 325 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
324 326 else:
325 327 _sethgexecutable(
326 328 findexe(b'hg') or os.path.basename(pycompat.sysargv[0])
327 329 )
328 330 return _hgexecutable
329 331
330 332
331 333 def _sethgexecutable(path):
332 334 """set location of the 'hg' executable"""
333 335 global _hgexecutable
334 336 _hgexecutable = path
335 337
336 338
337 339 def _testfileno(f, stdf):
338 340 fileno = getattr(f, 'fileno', None)
339 341 try:
340 342 return fileno and fileno() == stdf.fileno()
341 343 except io.UnsupportedOperation:
342 344 return False # fileno() raised UnsupportedOperation
343 345
344 346
345 347 def isstdin(f):
346 348 return _testfileno(f, sys.__stdin__)
347 349
348 350
349 351 def isstdout(f):
350 352 return _testfileno(f, sys.__stdout__)
351 353
352 354
353 355 def protectstdio(uin, uout):
354 356 """Duplicate streams and redirect original if (uin, uout) are stdio
355 357
356 358 If uin is stdin, it's redirected to /dev/null. If uout is stdout, it's
357 359 redirected to stderr so the output is still readable.
358 360
359 361 Returns (fin, fout) which point to the original (uin, uout) fds, but
360 362 may be copy of (uin, uout). The returned streams can be considered
361 363 "owned" in that print(), exec(), etc. never reach to them.
362 364 """
363 365 uout.flush()
364 366 fin, fout = uin, uout
365 367 if _testfileno(uin, stdin):
366 368 newfd = os.dup(uin.fileno())
367 369 nullfd = os.open(os.devnull, os.O_RDONLY)
368 370 os.dup2(nullfd, uin.fileno())
369 371 os.close(nullfd)
370 372 fin = os.fdopen(newfd, 'rb')
371 373 if _testfileno(uout, stdout):
372 374 newfd = os.dup(uout.fileno())
373 375 os.dup2(stderr.fileno(), uout.fileno())
374 376 fout = os.fdopen(newfd, 'wb')
375 377 return fin, fout
376 378
377 379
378 380 def restorestdio(uin, uout, fin, fout):
379 381 """Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
380 382 uout.flush()
381 383 for f, uif in [(fin, uin), (fout, uout)]:
382 384 if f is not uif:
383 385 os.dup2(f.fileno(), uif.fileno())
384 386 f.close()
385 387
386 388
387 389 def shellenviron(environ=None):
388 390 """return environ with optional override, useful for shelling out"""
389 391
390 392 def py2shell(val):
391 393 """convert python object into string that is useful to shell"""
392 394 if val is None or val is False:
393 395 return b'0'
394 396 if val is True:
395 397 return b'1'
396 398 return pycompat.bytestr(val)
397 399
398 400 env = dict(encoding.environ)
399 401 if environ:
400 402 env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
401 403 env[b'HG'] = hgexecutable()
402 404 return env
403 405
404 406
405 407 if pycompat.iswindows:
406 408
407 409 def shelltonative(cmd, env):
408 410 return platform.shelltocmdexe( # pytype: disable=module-attr
409 411 cmd, shellenviron(env)
410 412 )
411 413
412 414 tonativestr = encoding.strfromlocal
413 415 else:
414 416
415 417 def shelltonative(cmd, env):
416 418 return cmd
417 419
418 420 tonativestr = pycompat.identity
419 421
420 422
421 423 def tonativeenv(env):
422 424 '''convert the environment from bytes to strings suitable for Popen(), etc.
423 425 '''
424 426 return pycompat.rapply(tonativestr, env)
425 427
426 428
427 429 def system(cmd, environ=None, cwd=None, out=None):
428 430 '''enhanced shell command execution.
429 431 run with environment maybe modified, maybe in different dir.
430 432
431 433 if out is specified, it is assumed to be a file-like object that has a
432 434 write() method. stdout and stderr will be redirected to out.'''
433 435 try:
434 436 stdout.flush()
435 437 except Exception:
436 438 pass
437 439 env = shellenviron(environ)
438 440 if out is None or isstdout(out):
439 441 rc = subprocess.call(
440 442 tonativestr(cmd),
441 443 shell=True,
442 444 close_fds=closefds,
443 445 env=tonativeenv(env),
444 446 cwd=pycompat.rapply(tonativestr, cwd),
445 447 )
446 448 else:
447 449 proc = subprocess.Popen(
448 450 tonativestr(cmd),
449 451 shell=True,
450 452 close_fds=closefds,
451 453 env=tonativeenv(env),
452 454 cwd=pycompat.rapply(tonativestr, cwd),
453 455 stdout=subprocess.PIPE,
454 456 stderr=subprocess.STDOUT,
455 457 )
456 458 for line in iter(proc.stdout.readline, b''):
457 459 out.write(line)
458 460 proc.wait()
459 461 rc = proc.returncode
460 462 if pycompat.sysplatform == b'OpenVMS' and rc & 1:
461 463 rc = 0
462 464 return rc
463 465
464 466
465 467 _is_gui = None
466 468
467 469
468 470 def _gui():
469 471 '''Are we running in a GUI?'''
470 472 if pycompat.isdarwin:
471 473 if b'SSH_CONNECTION' in encoding.environ:
472 474 # handle SSH access to a box where the user is logged in
473 475 return False
474 476 elif getattr(osutil, 'isgui', None):
475 477 # check if a CoreGraphics session is available
476 478 return osutil.isgui()
477 479 else:
478 480 # pure build; use a safe default
479 481 return True
480 482 else:
481 483 return pycompat.iswindows or encoding.environ.get(b"DISPLAY")
482 484
483 485
484 486 def gui():
485 487 global _is_gui
486 488 if _is_gui is None:
487 489 _is_gui = _gui()
488 490 return _is_gui
489 491
490 492
491 493 def hgcmd():
492 494 """Return the command used to execute current hg
493 495
494 496 This is different from hgexecutable() because on Windows we want
495 497 to avoid things opening new shell windows like batch files, so we
496 498 get either the python call or current executable.
497 499 """
498 500 if resourceutil.mainfrozen():
499 501 if getattr(sys, 'frozen', None) == 'macosx_app':
500 502 # Env variable set by py2app
501 503 return [encoding.environ[b'EXECUTABLEPATH']]
502 504 else:
503 505 return [pycompat.sysexecutable]
504 506 return _gethgcmd()
505 507
506 508
507 509 def rundetached(args, condfn):
508 510 """Execute the argument list in a detached process.
509 511
510 512 condfn is a callable which is called repeatedly and should return
511 513 True once the child process is known to have started successfully.
512 514 At this point, the child process PID is returned. If the child
513 515 process fails to start or finishes before condfn() evaluates to
514 516 True, return -1.
515 517 """
516 518 # Windows case is easier because the child process is either
517 519 # successfully starting and validating the condition or exiting
518 520 # on failure. We just poll on its PID. On Unix, if the child
519 521 # process fails to start, it will be left in a zombie state until
520 522 # the parent wait on it, which we cannot do since we expect a long
521 523 # running process on success. Instead we listen for SIGCHLD telling
522 524 # us our child process terminated.
523 525 terminated = set()
524 526
525 527 def handler(signum, frame):
526 528 terminated.add(os.wait())
527 529
528 530 prevhandler = None
529 531 SIGCHLD = getattr(signal, 'SIGCHLD', None)
530 532 if SIGCHLD is not None:
531 533 prevhandler = signal.signal(SIGCHLD, handler)
532 534 try:
533 535 pid = spawndetached(args)
534 536 while not condfn():
535 537 if (pid in terminated or not testpid(pid)) and not condfn():
536 538 return -1
537 539 time.sleep(0.1)
538 540 return pid
539 541 finally:
540 542 if prevhandler is not None:
541 543 signal.signal(signal.SIGCHLD, prevhandler)
542 544
543 545
544 546 @contextlib.contextmanager
545 547 def uninterruptible(warn):
546 548 """Inhibit SIGINT handling on a region of code.
547 549
548 550 Note that if this is called in a non-main thread, it turns into a no-op.
549 551
550 552 Args:
551 553 warn: A callable which takes no arguments, and returns True if the
552 554 previous signal handling should be restored.
553 555 """
554 556
555 557 oldsiginthandler = [signal.getsignal(signal.SIGINT)]
556 558 shouldbail = []
557 559
558 560 def disabledsiginthandler(*args):
559 561 if warn():
560 562 signal.signal(signal.SIGINT, oldsiginthandler[0])
561 563 del oldsiginthandler[0]
562 564 shouldbail.append(True)
563 565
564 566 try:
565 567 try:
566 568 signal.signal(signal.SIGINT, disabledsiginthandler)
567 569 except ValueError:
568 570 # wrong thread, oh well, we tried
569 571 del oldsiginthandler[0]
570 572 yield
571 573 finally:
572 574 if oldsiginthandler:
573 575 signal.signal(signal.SIGINT, oldsiginthandler[0])
574 576 if shouldbail:
575 577 raise KeyboardInterrupt
576 578
577 579
578 580 if pycompat.iswindows:
579 581 # no fork on Windows, but we can create a detached process
580 582 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
581 583 # No stdlib constant exists for this value
582 584 DETACHED_PROCESS = 0x00000008
583 585 # Following creation flags might create a console GUI window.
584 586 # Using subprocess.CREATE_NEW_CONSOLE might helps.
585 587 # See https://phab.mercurial-scm.org/D1701 for discussion
586 588 _creationflags = (
587 589 DETACHED_PROCESS
588 590 | subprocess.CREATE_NEW_PROCESS_GROUP # pytype: disable=module-attr
589 591 )
590 592
591 593 def runbgcommand(
592 594 script,
593 595 env,
594 596 shell=False,
595 597 stdout=None,
596 598 stderr=None,
597 599 ensurestart=True,
598 600 record_wait=None,
599 601 ):
600 602 '''Spawn a command without waiting for it to finish.'''
601 603 # we can't use close_fds *and* redirect stdin. I'm not sure that we
602 604 # need to because the detached process has no console connection.
603 605 p = subprocess.Popen(
604 606 tonativestr(script),
605 607 shell=shell,
606 608 env=tonativeenv(env),
607 609 close_fds=True,
608 610 creationflags=_creationflags,
609 611 stdout=stdout,
610 612 stderr=stderr,
611 613 )
612 614 if record_wait is not None:
613 615 record_wait(p.wait)
614 616
615 617
616 618 else:
617 619
618 620 def runbgcommand(
619 621 cmd,
620 622 env,
621 623 shell=False,
622 624 stdout=None,
623 625 stderr=None,
624 626 ensurestart=True,
625 627 record_wait=None,
626 628 ):
627 629 '''Spawn a command without waiting for it to finish.
628 630
629 631
630 632 When `record_wait` is not None, the spawned process will not be fully
631 633 detached and the `record_wait` argument will be called with a the
632 634 `Subprocess.wait` function for the spawned process. This is mostly
633 635 useful for developers that need to make sure the spawned process
634 636 finished before a certain point. (eg: writing test)'''
635 637 if pycompat.isdarwin:
636 638 # avoid crash in CoreFoundation in case another thread
637 639 # calls gui() while we're calling fork().
638 640 gui()
639 641
640 642 # double-fork to completely detach from the parent process
641 643 # based on http://code.activestate.com/recipes/278731
642 644 if record_wait is None:
643 645 pid = os.fork()
644 646 if pid:
645 647 if not ensurestart:
646 648 # Even though we're not waiting on the child process,
647 649 # we still must call waitpid() on it at some point so
648 650 # it's not a zombie/defunct. This is especially relevant for
649 651 # chg since the parent process won't die anytime soon.
650 652 # We use a thread to make the overhead tiny.
651 653 def _do_wait():
652 654 os.waitpid(pid, 0)
653 655
654 656 t = threading.Thread(target=_do_wait)
655 657 t.daemon = True
656 658 t.start()
657 659 return
658 660 # Parent process
659 661 (_pid, status) = os.waitpid(pid, 0)
660 662 if os.WIFEXITED(status):
661 663 returncode = os.WEXITSTATUS(status)
662 664 else:
663 665 returncode = -(os.WTERMSIG(status))
664 666 if returncode != 0:
665 667 # The child process's return code is 0 on success, an errno
666 668 # value on failure, or 255 if we don't have a valid errno
667 669 # value.
668 670 #
669 671 # (It would be slightly nicer to return the full exception info
670 672 # over a pipe as the subprocess module does. For now it
671 673 # doesn't seem worth adding that complexity here, though.)
672 674 if returncode == 255:
673 675 returncode = errno.EINVAL
674 676 raise OSError(
675 677 returncode,
676 678 b'error running %r: %s'
677 679 % (cmd, os.strerror(returncode)),
678 680 )
679 681 return
680 682
681 683 returncode = 255
682 684 try:
683 685 if record_wait is None:
684 686 # Start a new session
685 687 os.setsid()
686 688
687 689 stdin = open(os.devnull, b'r')
688 690 if stdout is None:
689 691 stdout = open(os.devnull, b'w')
690 692 if stderr is None:
691 693 stderr = open(os.devnull, b'w')
692 694
693 695 # connect stdin to devnull to make sure the subprocess can't
694 696 # muck up that stream for mercurial.
695 697 p = subprocess.Popen(
696 698 cmd,
697 699 shell=shell,
698 700 env=env,
699 701 close_fds=True,
700 702 stdin=stdin,
701 703 stdout=stdout,
702 704 stderr=stderr,
703 705 )
704 706 if record_wait is not None:
705 707 record_wait(p.wait)
706 708 returncode = 0
707 709 except EnvironmentError as ex:
708 710 returncode = ex.errno & 0xFF
709 711 if returncode == 0:
710 712 # This shouldn't happen, but just in case make sure the
711 713 # return code is never 0 here.
712 714 returncode = 255
713 715 except Exception:
714 716 returncode = 255
715 717 finally:
716 718 # mission accomplished, this child needs to exit and not
717 719 # continue the hg process here.
718 720 if record_wait is None:
719 721 os._exit(returncode)
General Comments 0
You need to be logged in to leave comments. Login now