diff -r 220a220ed088 -r be930f16a52a mercurial/util.py --- a/mercurial/util.py Wed May 27 22:11:37 2015 -0700 +++ b/mercurial/util.py Sat May 30 23:55:24 2015 -0700 @@ -232,6 +232,103 @@ import subprocess closefds = os.name == 'posix' +_chunksize = 4096 + +class bufferedinputpipe(object): + """a manually buffered input pipe + + Python will not let us use buffered IO and lazy reading with 'polling' at + the same time. We cannot probe the buffer state and select will not detect + that data are ready to read if they are already buffered. + + This class let us work around that by implementing its own buffering + (allowing efficient readline) while offering a way to know if the buffer is + empty from the output (allowing collaboration of the buffer with polling). + + This class lives in the 'util' module because it makes use of the 'os' + module from the python stdlib. + """ + + def __init__(self, input): + self._input = input + self._buffer = [] + self._eof = False + + @property + def hasbuffer(self): + """True is any data is currently buffered + + This will be used externally a pre-step for polling IO. If there is + already data then no polling should be set in place.""" + return bool(self._buffer) + + @property + def closed(self): + return self._input.closed + + def fileno(self): + return self._input.fileno() + + def close(self): + return self._input.close() + + def read(self, size): + while (not self._eof) and (self._lenbuf < size): + self._fillbuffer() + return self._frombuffer(size) + + def readline(self, *args, **kwargs): + if 1 < len(self._buffer): + # this should not happen because both read and readline end with a + # _frombuffer call that collapse it. + self._buffer = [''.join(self._buffer)] + lfi = -1 + if self._buffer: + lfi = self._buffer[-1].find('\n') + while (not self._eof) and lfi < 0: + self._fillbuffer() + if self._buffer: + lfi = self._buffer[-1].find('\n') + size = lfi + 1 + if lfi < 0: # end of file + size = self._lenbuf + elif 1 < len(self._buffer): + # we need to take previous chunks into account + size += self._lenbuf - len(self._buffer[-1]) + return self._frombuffer(size) + + @property + def _lenbuf(self): + """return the current lengh of buffered data""" + return sum(len(d) for d in self._buffer) + + def _frombuffer(self, size): + """return at most 'size' data from the buffer + + The data are removed from the buffer.""" + if size == 0 or not self._buffer: + return '' + buf = self._buffer[0] + if 1 < len(self._buffer): + buf = ''.join(self._buffer) + + data = buf[:size] + buf = buf[len(data):] + if buf: + self._buffer = [buf] + else: + self._buffer = [] + return data + + def _fillbuffer(self): + """read data to the buffer""" + data = os.read(self._input.fileno(), _chunksize) + if not data: + self._eof = True + else: + # inefficient add + self._buffer.append(data) + 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