Просмотр исходного кода

Merge branch 'block-select-line-buffered'

Matthew Wang 11 лет назад
Родитель
Сommit
d44d437758
1 измененных файлов: 46 добавлений и 34 удалений
  1. 46
    34
      cdiff.py

+ 46
- 34
cdiff.py Просмотреть файл

@@ -23,10 +23,16 @@ import sys
23 23
 if sys.hexversion < 0x02050000:
24 24
     raise SystemExit("*** Requires python >= 2.5.0")    # pragma: no cover
25 25
 
26
+# Python < 2.6 does not have next()
27
+try:
28
+    next
29
+except NameError:
30
+    def next(obj): return obj.next()
31
+
26 32
 import re
27 33
 import signal
28 34
 import subprocess
29
-import fcntl
35
+import select
30 36
 import os
31 37
 import difflib
32 38
 
@@ -226,44 +232,37 @@ class PatchStream(object):
226 232
 
227 233
 
228 234
 class PatchStreamForwarder(object):
229
-    """A non-block stream forwarder.  Note input stream is non-seekable, and
230
-    upstream has eaten some lines.
235
+    """A blocking stream forwarder use `select` and line buffered mode.  Feed
236
+    input stream to a diff format translator and read output stream from it.
237
+    Note input stream is non-seekable, and upstream has eaten some lines.
231 238
     """
232 239
     def __init__(self, istream, translator):
233 240
         assert isinstance(istream, PatchStream)
234
-        self._istream = istream
235
-        self._translator = translator
236
-
237
-        self._set_non_block(self._translator.stdin)
238
-        self._set_non_block(self._translator.stdout)
239
-
240
-        self._forward_until_block()
241
+        assert isinstance(translator, subprocess.Popen)
242
+        self._istream = iter(istream)
243
+        self._in = translator.stdin
244
+        self._out = translator.stdout
241 245
 
242
-    def _set_non_block(self, hdl):
243
-        fd = hdl.fileno()
244
-        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
245
-        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
246
+    def _can_read(self, timeout=0):
247
+        return select.select([self._out.fileno()], [], [], timeout)[0]
246 248
 
247
-    def _forward_until_block(self):
248
-        for line in self._istream:
249
-            try:
250
-                self._translator.stdin.write(line.encode('utf-8'))
251
-            except IOError:
252
-                break       # EAGAIN
253
-        else:
254
-            self._translator.stdin.close()
249
+    def _forward_line(self):
250
+        try:
251
+            line = next(self._istream)
252
+            self._in.write(line.encode('utf-8'))
253
+        except StopIteration:
254
+            self._in.close()
255 255
 
256 256
     def __iter__(self):
257 257
         while True:
258
-            try:
259
-                line = self._translator.stdout.readline()
260
-                if not line:
258
+            if self._can_read():
259
+                line = self._out.readline()
260
+                if line:
261
+                    yield line
262
+                else:
261 263
                     return
262
-                yield line
263
-            except IOError:
264
-                if not self._translator.stdin.closed:
265
-                    self._forward_until_block()
266
-                continue    # EAGAIN
264
+            elif not self._in.closed:
265
+                self._forward_line()
267 266
 
268 267
 
269 268
 class DiffParser(object):
@@ -283,9 +282,10 @@ class DiffParser(object):
283 282
             #
284 283
             self._type = 'context'
285 284
             try:
285
+                # Use line buffered mode so that to readline() in block mode
286 286
                 self._translator = subprocess.Popen(
287 287
                     ['filterdiff', '--format=unified'], stdin=subprocess.PIPE,
288
-                    stdout=subprocess.PIPE)
288
+                    stdout=subprocess.PIPE, bufsize=1)
289 289
             except OSError:
290 290
                 raise SystemExit('*** Context diff support depends on '
291 291
                                  'filterdiff')
@@ -590,14 +590,26 @@ class DiffMarker(object):
590 590
 
591 591
 
592 592
 def markup_to_pager(stream, opts):
593
+    """Pipe unified diff stream to pager (less).
594
+
595
+    Note: have to create pager Popen object before the translator Popen object
596
+    in PatchStreamForwarder, otherwise the `stdin=subprocess.PIPE` would cause
597
+    trouble to the translator pipe (select() never see EOF after input stream
598
+    ended), most likely python bug 12607 (http://bugs.python.org/issue12607)
599
+    which was fixed in python 2.7.3.
600
+
601
+    See issue #30 (https://github.com/ymattw/cdiff/issues/30) for more
602
+    information.
603
+    """
604
+    # Args stolen from git source: github.com/git/git/blob/master/pager.c
605
+    pager = subprocess.Popen(
606
+        ['less', '-FRSX'], stdin=subprocess.PIPE, stdout=sys.stdout)
607
+
593 608
     diffs = DiffParser(stream).get_diff_generator()
594 609
     marker = DiffMarker()
595 610
     color_diff = marker.markup(diffs, side_by_side=opts.side_by_side,
596 611
                                width=opts.width)
597 612
 
598
-    # Args stolen from git source: github.com/git/git/blob/master/pager.c
599
-    pager = subprocess.Popen(
600
-        ['less', '-FRSX'], stdin=subprocess.PIPE, stdout=sys.stdout)
601 613
     for line in color_diff:
602 614
         pager.stdin.write(line.encode('utf-8'))
603 615