|
@@ -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
|
|