diff mercurial/utils/procutil.py @ 37459:90c5ca718781

procutil: rewrite popen() as a subprocess.Popen wrapper (issue4746) (API) os.popen() of Python 3 is not the popen() we want. First, it doesn't accept command in bytes. Second, a returned stream is always wrapped by TextIO. So we have to reimplement our popen(). Fortunately, this fixes the bug 4746 since ours returns an exit code compatible with explainexit(). .. api:: ``procutil.popen()`` no longer supports text mode I/O.
author Yuya Nishihara <yuya@tcha.org>
date Sat, 07 Apr 2018 21:09:21 +0900
parents 7f78de1c93aa
children a6c6b7beb025
line wrap: on
line diff
--- a/mercurial/utils/procutil.py	Sat Apr 07 20:50:38 2018 +0900
+++ b/mercurial/utils/procutil.py	Sat Apr 07 21:09:21 2018 +0900
@@ -58,7 +58,6 @@
 getuser = platform.getuser
 getpid = os.getpid
 hidewindow = platform.hidewindow
-popen = platform.popen
 quotecommand = platform.quotecommand
 readpipe = platform.readpipe
 setbinary = platform.setbinary
@@ -80,6 +79,49 @@
 
 closefds = pycompat.isposix
 
+class _pfile(object):
+    """File-like wrapper for a stream opened by subprocess.Popen()"""
+
+    def __init__(self, proc, fp):
+        self._proc = proc
+        self._fp = fp
+
+    def close(self):
+        # unlike os.popen(), this returns an integer in subprocess coding
+        self._fp.close()
+        return self._proc.wait()
+
+    def __iter__(self):
+        return iter(self._fp)
+
+    def __getattr__(self, attr):
+        return getattr(self._fp, attr)
+
+    def __enter__(self):
+        return self
+
+    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)
+    elif mode == 'wb':
+        return _popenwriter(cmd, bufsize)
+    raise error.ProgrammingError('unsupported mode: %r' % mode)
+
+def _popenreader(cmd, bufsize):
+    p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
+                         close_fds=closefds,
+                         stdout=subprocess.PIPE)
+    return _pfile(p, p.stdout)
+
+def _popenwriter(cmd, bufsize):
+    p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
+                         close_fds=closefds,
+                         stdin=subprocess.PIPE)
+    return _pfile(p, p.stdin)
+
 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