changeset 37121:5be286db5fb5

procutil: move process/executable management functions to new module std* files, pipe helpers, and findexe()s are moved as well since they are likely to be used with sub processes.
author Yuya Nishihara <yuya@tcha.org>
date Sat, 24 Mar 2018 13:38:04 +0900
parents e7b517809ebc
children d4a2e0d5d042
files mercurial/util.py mercurial/utils/procutil.py
diffstat 2 files changed, 366 insertions(+), 287 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/util.py	Sat Mar 24 14:32:34 2018 +0900
+++ b/mercurial/util.py	Sat Mar 24 13:38:04 2018 +0900
@@ -22,18 +22,14 @@
 import errno
 import gc
 import hashlib
-import imp
-import io
 import itertools
 import mmap
 import os
 import platform as pyplatform
 import re as remod
 import shutil
-import signal
 import socket
 import stat
-import subprocess
 import sys
 import tempfile
 import time
@@ -52,6 +48,7 @@
 )
 from .utils import (
     dateutil,
+    procutil,
     stringutil,
 )
 
@@ -69,9 +66,6 @@
 queue = pycompat.queue
 safehasattr = pycompat.safehasattr
 socketserver = pycompat.socketserver
-stderr = pycompat.stderr
-stdin = pycompat.stdin
-stdout = pycompat.stdout
 bytesio = pycompat.bytesio
 # TODO deprecate stringio name, as it is a lie on Python 3.
 stringio = bytesio
@@ -84,21 +78,8 @@
 # workaround for win32mbcs
 _filenamebytestr = pycompat.bytestr
 
-def isatty(fp):
-    try:
-        return fp.isatty()
-    except AttributeError:
-        return False
-
-# glibc determines buffering on first write to stdout - if we replace a TTY
-# destined stdout with a pipe destined stdout (e.g. pager), we want line
-# buffering
-if isatty(stdout):
-    stdout = os.fdopen(stdout.fileno(), r'wb', 1)
-
 if pycompat.iswindows:
     from . import windows as platform
-    stdout = platform.winstdout(stdout)
 else:
     from . import posix as platform
 
@@ -110,16 +91,10 @@
 checklink = platform.checklink
 copymode = platform.copymode
 expandglobs = platform.expandglobs
-explainexit = platform.explainexit
-findexe = platform.findexe
 getfsmountpoint = platform.getfsmountpoint
 getfstype = platform.getfstype
-_gethgcmd = platform.gethgcmd
-getuser = platform.getuser
-getpid = os.getpid
 groupmembers = platform.groupmembers
 groupname = platform.groupname
-hidewindow = platform.hidewindow
 isexec = platform.isexec
 isowner = platform.isowner
 listdir = osutil.listdir
@@ -136,27 +111,17 @@
 parsepatchoutput = platform.parsepatchoutput
 pconvert = platform.pconvert
 poll = platform.poll
-popen = platform.popen
 posixfile = platform.posixfile
-quotecommand = platform.quotecommand
-readpipe = platform.readpipe
 rename = platform.rename
 removedirs = platform.removedirs
 samedevice = platform.samedevice
 samefile = platform.samefile
 samestat = platform.samestat
-setbinary = platform.setbinary
 setflags = platform.setflags
-setsignalhandler = platform.setsignalhandler
-shellquote = platform.shellquote
-shellsplit = platform.shellsplit
-spawndetached = platform.spawndetached
 split = platform.split
-sshargs = platform.sshargs
 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
 statisexec = platform.statisexec
 statislink = platform.statislink
-testpid = platform.testpid
 umask = platform.umask
 unlink = platform.unlink
 username = platform.username
@@ -165,14 +130,6 @@
     recvfds = osutil.recvfds
 except AttributeError:
     pass
-try:
-    setprocname = osutil.setprocname
-except AttributeError:
-    pass
-try:
-    unblocksignal = osutil.unblocksignal
-except AttributeError:
-    pass
 
 # Python compatibility
 
@@ -346,8 +303,6 @@
             return memoryview(sliceable)[offset:offset + length]
         return memoryview(sliceable)[offset:]
 
-closefds = pycompat.isposix
-
 _chunksize = 4096
 
 class bufferedinputpipe(object):
@@ -464,30 +419,6 @@
             return ''
         raise
 
-def popen2(cmd, env=None, newlines=False):
-    # Setting bufsize to -1 lets the system decide the buffer size.
-    # The default for bufsize is 0, meaning unbuffered. This leads to
-    # poor performance on Mac OS X: http://bugs.python.org/issue4194
-    p = subprocess.Popen(cmd, shell=True, bufsize=-1,
-                         close_fds=closefds,
-                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                         universal_newlines=newlines,
-                         env=env)
-    return p.stdin, p.stdout
-
-def popen3(cmd, env=None, newlines=False):
-    stdin, stdout, stderr, p = popen4(cmd, env, newlines)
-    return stdin, stdout, stderr
-
-def popen4(cmd, env=None, newlines=False, bufsize=-1):
-    p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
-                         close_fds=closefds,
-                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                         stderr=subprocess.PIPE,
-                         universal_newlines=newlines,
-                         env=env)
-    return p.stdin, p.stdout, p.stderr, p
-
 class fileobjectproxy(object):
     """A proxy around file objects that tells a watcher when events occur.
 
@@ -1500,60 +1431,6 @@
     if prop in obj.__dict__:
         del obj.__dict__[prop]
 
-def pipefilter(s, cmd):
-    '''filter string S through command CMD, returning its output'''
-    p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
-                         stdin=subprocess.PIPE, stdout=subprocess.PIPE)
-    pout, perr = p.communicate(s)
-    return pout
-
-def tempfilter(s, cmd):
-    '''filter string S through a pair of temporary files with CMD.
-    CMD is used as a template to create the real command to be run,
-    with the strings INFILE and OUTFILE replaced by the real names of
-    the temporary files generated.'''
-    inname, outname = None, None
-    try:
-        infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
-        fp = os.fdopen(infd, r'wb')
-        fp.write(s)
-        fp.close()
-        outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
-        os.close(outfd)
-        cmd = cmd.replace('INFILE', inname)
-        cmd = cmd.replace('OUTFILE', outname)
-        code = os.system(cmd)
-        if pycompat.sysplatform == 'OpenVMS' and code & 1:
-            code = 0
-        if code:
-            raise error.Abort(_("command '%s' failed: %s") %
-                              (cmd, explainexit(code)))
-        with open(outname, 'rb') as fp:
-            return fp.read()
-    finally:
-        try:
-            if inname:
-                os.unlink(inname)
-        except OSError:
-            pass
-        try:
-            if outname:
-                os.unlink(outname)
-        except OSError:
-            pass
-
-_filtertable = {
-    'tempfile:': tempfilter,
-    'pipe:': pipefilter,
-}
-
-def filter(s, cmd):
-    "filter a string through a command that transforms its input to its output"
-    for name, fn in _filtertable.iteritems():
-        if cmd.startswith(name):
-            return fn(s, cmd[len(name):].lstrip())
-    return pipefilter(s, cmd)
-
 def increasingchunks(source, min=1024, max=65536):
     '''return no less than min bytes per chunk while data remains,
     doubling min after each chunk until it reaches max'''
@@ -1644,18 +1521,8 @@
     b.reverse()
     return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
 
-def mainfrozen():
-    """return True if we are a frozen executable.
-
-    The code supports py2exe (most common, Windows only) and tools/freeze
-    (portable, not much used).
-    """
-    return (safehasattr(sys, "frozen") or # new py2exe
-            safehasattr(sys, "importers") or # old py2exe
-            imp.is_frozen(u"__main__")) # tools/freeze
-
 # the location of data files matching the source code
-if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
+if procutil.mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
     # executable version (py2exe) doesn't support __file__
     datapath = os.path.dirname(pycompat.sysexecutable)
 else:
@@ -1663,92 +1530,6 @@
 
 i18n.setdatapath(datapath)
 
-_hgexecutable = None
-
-def hgexecutable():
-    """return location of the 'hg' executable.
-
-    Defaults to $HG or 'hg' in the search path.
-    """
-    if _hgexecutable is None:
-        hg = encoding.environ.get('HG')
-        mainmod = sys.modules[r'__main__']
-        if hg:
-            _sethgexecutable(hg)
-        elif mainfrozen():
-            if getattr(sys, 'frozen', None) == 'macosx_app':
-                # Env variable set by py2app
-                _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
-            else:
-                _sethgexecutable(pycompat.sysexecutable)
-        elif (os.path.basename(
-            pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
-            _sethgexecutable(pycompat.fsencode(mainmod.__file__))
-        else:
-            exe = findexe('hg') or os.path.basename(sys.argv[0])
-            _sethgexecutable(exe)
-    return _hgexecutable
-
-def _sethgexecutable(path):
-    """set location of the 'hg' executable"""
-    global _hgexecutable
-    _hgexecutable = path
-
-def _testfileno(f, stdf):
-    fileno = getattr(f, 'fileno', None)
-    try:
-        return fileno and fileno() == stdf.fileno()
-    except io.UnsupportedOperation:
-        return False # fileno() raised UnsupportedOperation
-
-def isstdin(f):
-    return _testfileno(f, sys.__stdin__)
-
-def isstdout(f):
-    return _testfileno(f, sys.__stdout__)
-
-def shellenviron(environ=None):
-    """return environ with optional override, useful for shelling out"""
-    def py2shell(val):
-        'convert python object into string that is useful to shell'
-        if val is None or val is False:
-            return '0'
-        if val is True:
-            return '1'
-        return pycompat.bytestr(val)
-    env = dict(encoding.environ)
-    if environ:
-        env.update((k, py2shell(v)) for k, v in environ.iteritems())
-    env['HG'] = hgexecutable()
-    return env
-
-def system(cmd, environ=None, cwd=None, out=None):
-    '''enhanced shell command execution.
-    run with environment maybe modified, maybe in different dir.
-
-    if out is specified, it is assumed to be a file-like object that has a
-    write() method. stdout and stderr will be redirected to out.'''
-    try:
-        stdout.flush()
-    except Exception:
-        pass
-    cmd = quotecommand(cmd)
-    env = shellenviron(environ)
-    if out is None or isstdout(out):
-        rc = subprocess.call(cmd, shell=True, close_fds=closefds,
-                             env=env, cwd=cwd)
-    else:
-        proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
-                                env=env, cwd=cwd, stdout=subprocess.PIPE,
-                                stderr=subprocess.STDOUT)
-        for line in iter(proc.stdout.readline, ''):
-            out.write(line)
-        proc.wait()
-        rc = proc.returncode
-    if pycompat.sysplatform == 'OpenVMS' and rc & 1:
-        rc = 0
-    return rc
-
 def checksignature(func):
     '''wrap a function with code to check for calling errors'''
     def check(*args, **kwargs):
@@ -2133,21 +1914,6 @@
     function if need.'''
     return path.split(pycompat.ossep)
 
-def gui():
-    '''Are we running in a GUI?'''
-    if pycompat.isdarwin:
-        if 'SSH_CONNECTION' in encoding.environ:
-            # handle SSH access to a box where the user is logged in
-            return False
-        elif getattr(osutil, 'isgui', None):
-            # check if a CoreGraphics session is available
-            return osutil.isgui()
-        else:
-            # pure build; use a safe default
-            return True
-    else:
-        return pycompat.iswindows or encoding.environ.get("DISPLAY")
-
 def mktempcopy(name, emptyok=False, createmode=None):
     """Create a temporary file with the same contents from name
 
@@ -2716,56 +2482,6 @@
 def expandpath(path):
     return os.path.expanduser(os.path.expandvars(path))
 
-def hgcmd():
-    """Return the command used to execute current hg
-
-    This is different from hgexecutable() because on Windows we want
-    to avoid things opening new shell windows like batch files, so we
-    get either the python call or current executable.
-    """
-    if mainfrozen():
-        if getattr(sys, 'frozen', None) == 'macosx_app':
-            # Env variable set by py2app
-            return [encoding.environ['EXECUTABLEPATH']]
-        else:
-            return [pycompat.sysexecutable]
-    return _gethgcmd()
-
-def rundetached(args, condfn):
-    """Execute the argument list in a detached process.
-
-    condfn is a callable which is called repeatedly and should return
-    True once the child process is known to have started successfully.
-    At this point, the child process PID is returned. If the child
-    process fails to start or finishes before condfn() evaluates to
-    True, return -1.
-    """
-    # Windows case is easier because the child process is either
-    # successfully starting and validating the condition or exiting
-    # on failure. We just poll on its PID. On Unix, if the child
-    # process fails to start, it will be left in a zombie state until
-    # the parent wait on it, which we cannot do since we expect a long
-    # running process on success. Instead we listen for SIGCHLD telling
-    # us our child process terminated.
-    terminated = set()
-    def handler(signum, frame):
-        terminated.add(os.wait())
-    prevhandler = None
-    SIGCHLD = getattr(signal, 'SIGCHLD', None)
-    if SIGCHLD is not None:
-        prevhandler = signal.signal(SIGCHLD, handler)
-    try:
-        pid = spawndetached(args)
-        while not condfn():
-            if ((pid in terminated or not testpid(pid))
-                and not condfn()):
-                return -1
-            time.sleep(0.1)
-        return pid
-    finally:
-        if prevhandler is not None:
-            signal.signal(signal.SIGCHLD, prevhandler)
-
 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
     """Return the result of interpolating items in the mapping into string s.
 
@@ -3257,7 +2973,7 @@
                 yield line % (fnmax, fnln, func)
 
 def debugstacktrace(msg='stacktrace', skip=0,
-                    f=stderr, otherf=stdout, depth=0):
+                    f=procutil.stderr, otherf=procutil.stdout, depth=0):
     '''Writes a message to f (stderr) with a nicely formatted stacktrace.
     Skips the 'skip' entries closest to the call, then show 'depth' entries.
     By default it will flush stdout first.
@@ -4076,6 +3792,50 @@
 parsedate = _deprecatedfunc(dateutil.parsedate, '4.6')
 matchdate = _deprecatedfunc(dateutil.matchdate, '4.6')
 
+stderr = procutil.stderr
+stdin = procutil.stdin
+stdout = procutil.stdout
+explainexit = procutil.explainexit
+findexe = procutil.findexe
+getuser = procutil.getuser
+getpid = procutil.getpid
+hidewindow = procutil.hidewindow
+popen = procutil.popen
+quotecommand = procutil.quotecommand
+readpipe = procutil.readpipe
+setbinary = procutil.setbinary
+setsignalhandler = procutil.setsignalhandler
+shellquote = procutil.shellquote
+shellsplit = procutil.shellsplit
+spawndetached = procutil.spawndetached
+sshargs = procutil.sshargs
+testpid = procutil.testpid
+try:
+    setprocname = procutil.setprocname
+except AttributeError:
+    pass
+try:
+    unblocksignal = procutil.unblocksignal
+except AttributeError:
+    pass
+closefds = procutil.closefds
+isatty = procutil.isatty
+popen2 = procutil.popen2
+popen3 = procutil.popen3
+popen4 = procutil.popen4
+pipefilter = procutil.pipefilter
+tempfilter = procutil.tempfilter
+filter = procutil.filter
+mainfrozen = procutil.mainfrozen
+hgexecutable = procutil.hgexecutable
+isstdin = procutil.isstdin
+isstdout = procutil.isstdout
+shellenviron = procutil.shellenviron
+system = procutil.system
+gui = procutil.gui
+hgcmd = procutil.hgcmd
+rundetached = procutil.rundetached
+
 escapedata = _deprecatedfunc(stringutil.escapedata, '4.6')
 binary = _deprecatedfunc(stringutil.binary, '4.6')
 stringmatcher = _deprecatedfunc(stringutil.stringmatcher, '4.6')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/utils/procutil.py	Sat Mar 24 13:38:04 2018 +0900
@@ -0,0 +1,319 @@
+# procutil.py - utility for managing processes and executable environment
+#
+#  Copyright 2005 K. Thananchayan <thananck@yahoo.com>
+#  Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+#  Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import imp
+import io
+import os
+import signal
+import subprocess
+import sys
+import tempfile
+import time
+
+from ..i18n import _
+
+from .. import (
+    encoding,
+    error,
+    policy,
+    pycompat,
+)
+
+osutil = policy.importmod(r'osutil')
+
+stderr = pycompat.stderr
+stdin = pycompat.stdin
+stdout = pycompat.stdout
+
+def isatty(fp):
+    try:
+        return fp.isatty()
+    except AttributeError:
+        return False
+
+# glibc determines buffering on first write to stdout - if we replace a TTY
+# destined stdout with a pipe destined stdout (e.g. pager), we want line
+# buffering
+if isatty(stdout):
+    stdout = os.fdopen(stdout.fileno(), r'wb', 1)
+
+if pycompat.iswindows:
+    from .. import windows as platform
+    stdout = platform.winstdout(stdout)
+else:
+    from .. import posix as platform
+
+explainexit = platform.explainexit
+findexe = platform.findexe
+_gethgcmd = platform.gethgcmd
+getuser = platform.getuser
+getpid = os.getpid
+hidewindow = platform.hidewindow
+popen = platform.popen
+quotecommand = platform.quotecommand
+readpipe = platform.readpipe
+setbinary = platform.setbinary
+setsignalhandler = platform.setsignalhandler
+shellquote = platform.shellquote
+shellsplit = platform.shellsplit
+spawndetached = platform.spawndetached
+sshargs = platform.sshargs
+testpid = platform.testpid
+
+try:
+    setprocname = osutil.setprocname
+except AttributeError:
+    pass
+try:
+    unblocksignal = osutil.unblocksignal
+except AttributeError:
+    pass
+
+closefds = pycompat.isposix
+
+def popen2(cmd, env=None, newlines=False):
+    # Setting bufsize to -1 lets the system decide the buffer size.
+    # The default for bufsize is 0, meaning unbuffered. This leads to
+    # poor performance on Mac OS X: http://bugs.python.org/issue4194
+    p = subprocess.Popen(cmd, shell=True, bufsize=-1,
+                         close_fds=closefds,
+                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                         universal_newlines=newlines,
+                         env=env)
+    return p.stdin, p.stdout
+
+def popen3(cmd, env=None, newlines=False):
+    stdin, stdout, stderr, p = popen4(cmd, env, newlines)
+    return stdin, stdout, stderr
+
+def popen4(cmd, env=None, newlines=False, bufsize=-1):
+    p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
+                         close_fds=closefds,
+                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                         stderr=subprocess.PIPE,
+                         universal_newlines=newlines,
+                         env=env)
+    return p.stdin, p.stdout, p.stderr, p
+
+def pipefilter(s, cmd):
+    '''filter string S through command CMD, returning its output'''
+    p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
+                         stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+    pout, perr = p.communicate(s)
+    return pout
+
+def tempfilter(s, cmd):
+    '''filter string S through a pair of temporary files with CMD.
+    CMD is used as a template to create the real command to be run,
+    with the strings INFILE and OUTFILE replaced by the real names of
+    the temporary files generated.'''
+    inname, outname = None, None
+    try:
+        infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
+        fp = os.fdopen(infd, r'wb')
+        fp.write(s)
+        fp.close()
+        outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
+        os.close(outfd)
+        cmd = cmd.replace('INFILE', inname)
+        cmd = cmd.replace('OUTFILE', outname)
+        code = os.system(cmd)
+        if pycompat.sysplatform == 'OpenVMS' and code & 1:
+            code = 0
+        if code:
+            raise error.Abort(_("command '%s' failed: %s") %
+                              (cmd, explainexit(code)))
+        with open(outname, 'rb') as fp:
+            return fp.read()
+    finally:
+        try:
+            if inname:
+                os.unlink(inname)
+        except OSError:
+            pass
+        try:
+            if outname:
+                os.unlink(outname)
+        except OSError:
+            pass
+
+_filtertable = {
+    'tempfile:': tempfilter,
+    'pipe:': pipefilter,
+}
+
+def filter(s, cmd):
+    "filter a string through a command that transforms its input to its output"
+    for name, fn in _filtertable.iteritems():
+        if cmd.startswith(name):
+            return fn(s, cmd[len(name):].lstrip())
+    return pipefilter(s, cmd)
+
+def mainfrozen():
+    """return True if we are a frozen executable.
+
+    The code supports py2exe (most common, Windows only) and tools/freeze
+    (portable, not much used).
+    """
+    return (pycompat.safehasattr(sys, "frozen") or # new py2exe
+            pycompat.safehasattr(sys, "importers") or # old py2exe
+            imp.is_frozen(u"__main__")) # tools/freeze
+
+_hgexecutable = None
+
+def hgexecutable():
+    """return location of the 'hg' executable.
+
+    Defaults to $HG or 'hg' in the search path.
+    """
+    if _hgexecutable is None:
+        hg = encoding.environ.get('HG')
+        mainmod = sys.modules[r'__main__']
+        if hg:
+            _sethgexecutable(hg)
+        elif mainfrozen():
+            if getattr(sys, 'frozen', None) == 'macosx_app':
+                # Env variable set by py2app
+                _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
+            else:
+                _sethgexecutable(pycompat.sysexecutable)
+        elif (os.path.basename(
+            pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
+            _sethgexecutable(pycompat.fsencode(mainmod.__file__))
+        else:
+            exe = findexe('hg') or os.path.basename(sys.argv[0])
+            _sethgexecutable(exe)
+    return _hgexecutable
+
+def _sethgexecutable(path):
+    """set location of the 'hg' executable"""
+    global _hgexecutable
+    _hgexecutable = path
+
+def _testfileno(f, stdf):
+    fileno = getattr(f, 'fileno', None)
+    try:
+        return fileno and fileno() == stdf.fileno()
+    except io.UnsupportedOperation:
+        return False # fileno() raised UnsupportedOperation
+
+def isstdin(f):
+    return _testfileno(f, sys.__stdin__)
+
+def isstdout(f):
+    return _testfileno(f, sys.__stdout__)
+
+def shellenviron(environ=None):
+    """return environ with optional override, useful for shelling out"""
+    def py2shell(val):
+        'convert python object into string that is useful to shell'
+        if val is None or val is False:
+            return '0'
+        if val is True:
+            return '1'
+        return pycompat.bytestr(val)
+    env = dict(encoding.environ)
+    if environ:
+        env.update((k, py2shell(v)) for k, v in environ.iteritems())
+    env['HG'] = hgexecutable()
+    return env
+
+def system(cmd, environ=None, cwd=None, out=None):
+    '''enhanced shell command execution.
+    run with environment maybe modified, maybe in different dir.
+
+    if out is specified, it is assumed to be a file-like object that has a
+    write() method. stdout and stderr will be redirected to out.'''
+    try:
+        stdout.flush()
+    except Exception:
+        pass
+    cmd = quotecommand(cmd)
+    env = shellenviron(environ)
+    if out is None or isstdout(out):
+        rc = subprocess.call(cmd, shell=True, close_fds=closefds,
+                             env=env, cwd=cwd)
+    else:
+        proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
+                                env=env, cwd=cwd, stdout=subprocess.PIPE,
+                                stderr=subprocess.STDOUT)
+        for line in iter(proc.stdout.readline, ''):
+            out.write(line)
+        proc.wait()
+        rc = proc.returncode
+    if pycompat.sysplatform == 'OpenVMS' and rc & 1:
+        rc = 0
+    return rc
+
+def gui():
+    '''Are we running in a GUI?'''
+    if pycompat.isdarwin:
+        if 'SSH_CONNECTION' in encoding.environ:
+            # handle SSH access to a box where the user is logged in
+            return False
+        elif getattr(osutil, 'isgui', None):
+            # check if a CoreGraphics session is available
+            return osutil.isgui()
+        else:
+            # pure build; use a safe default
+            return True
+    else:
+        return pycompat.iswindows or encoding.environ.get("DISPLAY")
+
+def hgcmd():
+    """Return the command used to execute current hg
+
+    This is different from hgexecutable() because on Windows we want
+    to avoid things opening new shell windows like batch files, so we
+    get either the python call or current executable.
+    """
+    if mainfrozen():
+        if getattr(sys, 'frozen', None) == 'macosx_app':
+            # Env variable set by py2app
+            return [encoding.environ['EXECUTABLEPATH']]
+        else:
+            return [pycompat.sysexecutable]
+    return _gethgcmd()
+
+def rundetached(args, condfn):
+    """Execute the argument list in a detached process.
+
+    condfn is a callable which is called repeatedly and should return
+    True once the child process is known to have started successfully.
+    At this point, the child process PID is returned. If the child
+    process fails to start or finishes before condfn() evaluates to
+    True, return -1.
+    """
+    # Windows case is easier because the child process is either
+    # successfully starting and validating the condition or exiting
+    # on failure. We just poll on its PID. On Unix, if the child
+    # process fails to start, it will be left in a zombie state until
+    # the parent wait on it, which we cannot do since we expect a long
+    # running process on success. Instead we listen for SIGCHLD telling
+    # us our child process terminated.
+    terminated = set()
+    def handler(signum, frame):
+        terminated.add(os.wait())
+    prevhandler = None
+    SIGCHLD = getattr(signal, 'SIGCHLD', None)
+    if SIGCHLD is not None:
+        prevhandler = signal.signal(SIGCHLD, handler)
+    try:
+        pid = spawndetached(args)
+        while not condfn():
+            if ((pid in terminated or not testpid(pid))
+                and not condfn()):
+                return -1
+            time.sleep(0.1)
+        return pid
+    finally:
+        if prevhandler is not None:
+            signal.signal(signal.SIGCHLD, prevhandler)