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