--- a/mercurial/utils/procutil.py Sat Oct 05 10:29:34 2019 -0400
+++ b/mercurial/utils/procutil.py Sun Oct 06 09:45:02 2019 -0400
@@ -34,12 +34,14 @@
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 (or unbuffered, on Windows)
@@ -52,6 +54,7 @@
if pycompat.iswindows:
from .. import windows as platform
+
stdout = platform.winstdout(stdout)
else:
from .. import posix as platform
@@ -83,6 +86,7 @@
closefds = pycompat.isposix
+
def explainexit(code):
"""return a message describing a subprocess status
(codes from kill are negative - not os.system/wait encoding)"""
@@ -90,6 +94,7 @@
return _("exited with status %d") % code
return _("killed by signal %d") % -code
+
class _pfile(object):
"""File-like wrapper for a stream opened by subprocess.Popen()"""
@@ -114,6 +119,7 @@
def __exit__(self, exc_type, exc_value, exc_tb):
self.close()
+
def popen(cmd, mode='rb', bufsize=-1):
if mode == 'rb':
return _popenreader(cmd, bufsize)
@@ -121,52 +127,77 @@
return _popenwriter(cmd, bufsize)
raise error.ProgrammingError('unsupported mode: %r' % mode)
+
def _popenreader(cmd, bufsize):
- p = subprocess.Popen(tonativestr(quotecommand(cmd)),
- shell=True, bufsize=bufsize,
- close_fds=closefds,
- stdout=subprocess.PIPE)
+ p = subprocess.Popen(
+ tonativestr(quotecommand(cmd)),
+ shell=True,
+ bufsize=bufsize,
+ close_fds=closefds,
+ stdout=subprocess.PIPE,
+ )
return _pfile(p, p.stdout)
+
def _popenwriter(cmd, bufsize):
- p = subprocess.Popen(tonativestr(quotecommand(cmd)),
- shell=True, bufsize=bufsize,
- close_fds=closefds,
- stdin=subprocess.PIPE)
+ p = subprocess.Popen(
+ tonativestr(quotecommand(cmd)),
+ shell=True,
+ bufsize=bufsize,
+ close_fds=closefds,
+ stdin=subprocess.PIPE,
+ )
return _pfile(p, p.stdin)
+
def popen2(cmd, env=None):
# 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(tonativestr(cmd),
- shell=True, bufsize=-1,
- close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- env=tonativeenv(env))
+ p = subprocess.Popen(
+ tonativestr(cmd),
+ shell=True,
+ bufsize=-1,
+ close_fds=closefds,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ env=tonativeenv(env),
+ )
return p.stdin, p.stdout
+
def popen3(cmd, env=None):
stdin, stdout, stderr, p = popen4(cmd, env)
return stdin, stdout, stderr
+
def popen4(cmd, env=None, bufsize=-1):
- p = subprocess.Popen(tonativestr(cmd),
- shell=True, bufsize=bufsize,
- close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- env=tonativeenv(env))
+ p = subprocess.Popen(
+ tonativestr(cmd),
+ shell=True,
+ bufsize=bufsize,
+ close_fds=closefds,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ env=tonativeenv(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(tonativestr(cmd),
- shell=True, close_fds=closefds,
- stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ p = subprocess.Popen(
+ tonativestr(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,
@@ -186,8 +217,9 @@
if pycompat.sysplatform == 'OpenVMS' and code & 1:
code = 0
if code:
- raise error.Abort(_("command '%s' failed: %s") %
- (cmd, explainexit(code)))
+ raise error.Abort(
+ _("command '%s' failed: %s") % (cmd, explainexit(code))
+ )
with open(outname, 'rb') as fp:
return fp.read()
finally:
@@ -202,30 +234,37 @@
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 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(r"__main__")) # tools/freeze
+ return (
+ pycompat.safehasattr(sys, "frozen")
+ or pycompat.safehasattr(sys, "importers") # new py2exe
+ or imp.is_frozen(r"__main__") # old py2exe
+ ) # tools/freeze
+
_hgexecutable = None
+
def hgexecutable():
"""return location of the 'hg' executable.
@@ -242,32 +281,43 @@
_sethgexecutable(encoding.environ['EXECUTABLEPATH'])
else:
_sethgexecutable(pycompat.sysexecutable)
- elif (not pycompat.iswindows and os.path.basename(
- pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
+ elif (
+ not pycompat.iswindows
+ and os.path.basename(
+ pycompat.fsencode(getattr(mainmod, '__file__', ''))
+ )
+ == 'hg'
+ ):
_sethgexecutable(pycompat.fsencode(mainmod.__file__))
else:
- _sethgexecutable(findexe('hg') or
- os.path.basename(pycompat.sysargv[0]))
+ _sethgexecutable(
+ findexe('hg') or os.path.basename(pycompat.sysargv[0])
+ )
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
+ return False # fileno() raised UnsupportedOperation
+
def isstdin(f):
return _testfileno(f, sys.__stdin__)
+
def isstdout(f):
return _testfileno(f, sys.__stdout__)
+
def protectstdio(uin, uout):
"""Duplicate streams and redirect original if (uin, uout) are stdio
@@ -292,6 +342,7 @@
fout = os.fdopen(newfd, r'wb')
return fin, fout
+
def restorestdio(uin, uout, fin, fout):
"""Restore (uin, uout) streams from possibly duplicated (fin, fout)"""
uout.flush()
@@ -300,8 +351,10 @@
os.dup2(f.fileno(), uif.fileno())
f.close()
+
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:
@@ -309,28 +362,34 @@
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
+
if pycompat.iswindows:
+
def shelltonative(cmd, env):
return platform.shelltocmdexe(cmd, shellenviron(env))
tonativestr = encoding.strfromlocal
else:
+
def shelltonative(cmd, env):
return cmd
tonativestr = pycompat.identity
+
def tonativeenv(env):
'''convert the environment from bytes to strings suitable for Popen(), etc.
'''
return pycompat.rapply(tonativestr, env)
+
def system(cmd, environ=None, cwd=None, out=None):
'''enhanced shell command execution.
run with environment maybe modified, maybe in different dir.
@@ -344,17 +403,23 @@
cmd = quotecommand(cmd)
env = shellenviron(environ)
if out is None or isstdout(out):
- rc = subprocess.call(tonativestr(cmd),
- shell=True, close_fds=closefds,
- env=tonativeenv(env),
- cwd=pycompat.rapply(tonativestr, cwd))
+ rc = subprocess.call(
+ tonativestr(cmd),
+ shell=True,
+ close_fds=closefds,
+ env=tonativeenv(env),
+ cwd=pycompat.rapply(tonativestr, cwd),
+ )
else:
- proc = subprocess.Popen(tonativestr(cmd),
- shell=True, close_fds=closefds,
- env=tonativeenv(env),
- cwd=pycompat.rapply(tonativestr, cwd),
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
+ proc = subprocess.Popen(
+ tonativestr(cmd),
+ shell=True,
+ close_fds=closefds,
+ env=tonativeenv(env),
+ cwd=pycompat.rapply(tonativestr, cwd),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
for line in iter(proc.stdout.readline, ''):
out.write(line)
proc.wait()
@@ -363,6 +428,7 @@
rc = 0
return rc
+
def gui():
'''Are we running in a GUI?'''
if pycompat.isdarwin:
@@ -378,6 +444,7 @@
else:
return pycompat.iswindows or encoding.environ.get("DISPLAY")
+
def hgcmd():
"""Return the command used to execute current hg
@@ -393,6 +460,7 @@
return [pycompat.sysexecutable]
return _gethgcmd()
+
def rundetached(args, condfn):
"""Execute the argument list in a detached process.
@@ -410,8 +478,10 @@
# 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:
@@ -419,8 +489,7 @@
try:
pid = spawndetached(args)
while not condfn():
- if ((pid in terminated or not testpid(pid))
- and not condfn()):
+ if (pid in terminated or not testpid(pid)) and not condfn():
return -1
time.sleep(0.1)
return pid
@@ -428,6 +497,7 @@
if prevhandler is not None:
signal.signal(signal.SIGCHLD, prevhandler)
+
@contextlib.contextmanager
def uninterruptible(warn):
"""Inhibit SIGINT handling on a region of code.
@@ -461,6 +531,7 @@
if shouldbail:
raise KeyboardInterrupt
+
if pycompat.iswindows:
# no fork on Windows, but we can create a detached process
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
@@ -472,18 +543,27 @@
_creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
def runbgcommand(
- script, env, shell=False, stdout=None, stderr=None, ensurestart=True):
+ script, env, shell=False, stdout=None, stderr=None, ensurestart=True
+ ):
'''Spawn a command without waiting for it to finish.'''
# we can't use close_fds *and* redirect stdin. I'm not sure that we
# need to because the detached process has no console connection.
subprocess.Popen(
tonativestr(script),
- shell=shell, env=tonativeenv(env), close_fds=True,
- creationflags=_creationflags, stdout=stdout,
- stderr=stderr)
+ shell=shell,
+ env=tonativeenv(env),
+ close_fds=True,
+ creationflags=_creationflags,
+ stdout=stdout,
+ stderr=stderr,
+ )
+
+
else:
+
def runbgcommand(
- cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True):
+ cmd, env, shell=False, stdout=None, stderr=None, ensurestart=True
+ ):
'''Spawn a command without waiting for it to finish.'''
# double-fork to completely detach from the parent process
# based on http://code.activestate.com/recipes/278731
@@ -496,7 +576,7 @@
if os.WIFEXITED(status):
returncode = os.WEXITSTATUS(status)
else:
- returncode = -os.WTERMSIG(status)
+ returncode = -(os.WTERMSIG(status))
if returncode != 0:
# The child process's return code is 0 on success, an errno
# value on failure, or 255 if we don't have a valid errno
@@ -507,8 +587,10 @@
# doesn't seem worth adding that complexity here, though.)
if returncode == 255:
returncode = errno.EINVAL
- raise OSError(returncode, 'error running %r: %s' %
- (cmd, os.strerror(returncode)))
+ raise OSError(
+ returncode,
+ 'error running %r: %s' % (cmd, os.strerror(returncode)),
+ )
return
returncode = 255
@@ -525,11 +607,17 @@
# connect stdin to devnull to make sure the subprocess can't
# muck up that stream for mercurial.
subprocess.Popen(
- cmd, shell=shell, env=env, close_fds=True,
- stdin=stdin, stdout=stdout, stderr=stderr)
+ cmd,
+ shell=shell,
+ env=env,
+ close_fds=True,
+ stdin=stdin,
+ stdout=stdout,
+ stderr=stderr,
+ )
returncode = 0
except EnvironmentError as ex:
- returncode = (ex.errno & 0xff)
+ returncode = ex.errno & 0xFF
if returncode == 0:
# This shouldn't happen, but just in case make sure the
# return code is never 0 here.