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