|
@@ -2,9 +2,9 @@
|
2
|
2
|
# -*- coding: utf-8 -*-
|
3
|
3
|
|
4
|
4
|
"""
|
5
|
|
-Term based tool to view **colored**, **incremental** diff in Git/Mercurial/Svn
|
6
|
|
-workspace or from stdin, with **side by side** and **auto pager** support.
|
7
|
|
-Requires python (>= 2.5.0) and ``less``.
|
|
5
|
+Term based tool to view *colored*, *incremental* diff in a *Git/Mercurial/Svn*
|
|
6
|
+workspace or from stdin, with *side by side* and *auto pager* support. Requires
|
|
7
|
+python (>= 2.5.0) and ``less``.
|
8
|
8
|
"""
|
9
|
9
|
|
10
|
10
|
META_INFO = {
|
|
@@ -14,7 +14,7 @@ META_INFO = {
|
14
|
14
|
'email' : 'mattwyl(@)gmail(.)com',
|
15
|
15
|
'url' : 'https://github.com/ymattw/cdiff',
|
16
|
16
|
'keywords' : 'colored incremental side-by-side diff',
|
17
|
|
- 'description' : ('View colored, incremental diff in workspace or from '
|
|
17
|
+ 'description' : ('View colored, incremental diff in a workspace or from '
|
18
|
18
|
'stdin, with side by side and auto pager support')
|
19
|
19
|
}
|
20
|
20
|
|
|
@@ -212,12 +212,13 @@ class Diff(object):
|
212
|
212
|
wrap_char = colorize('>', 'lightmagenta')
|
213
|
213
|
|
214
|
214
|
def _normalize(line):
|
215
|
|
- return line.replace('\t', ' '*8).replace('\n', '').replace('\r', '')
|
|
215
|
+ return line.replace(
|
|
216
|
+ '\t', ' ' * 8).replace('\n', '').replace('\r', '')
|
216
|
217
|
|
217
|
218
|
def _fit_with_marker(text, markup_fn, width, pad=False):
|
218
|
219
|
"""Wrap or pad input pure text, then markup"""
|
219
|
220
|
if len(text) > width:
|
220
|
|
- return markup_fn(text[:width-1]) + wrap_char
|
|
221
|
+ return markup_fn(text[:(width - 1)]) + wrap_char
|
221
|
222
|
elif pad:
|
222
|
223
|
pad_len = width - len(text)
|
223
|
224
|
return '%s%*s' % (markup_fn(text), pad_len, '')
|
|
@@ -247,9 +248,9 @@ class Diff(object):
|
247
|
248
|
text = text[1:]
|
248
|
249
|
else:
|
249
|
250
|
# FIXME: utf-8 wchar might break the rule here, e.g.
|
250
|
|
- # u'\u554a' takes double width of a single letter, also this
|
251
|
|
- # depends on your terminal font. I guess audience of this
|
252
|
|
- # tool never put that kind of symbol in their code :-)
|
|
251
|
+ # u'\u554a' takes double width of a single letter, also
|
|
252
|
+ # this depends on your terminal font. I guess audience of
|
|
253
|
+ # this tool never put that kind of symbol in their code :-)
|
253
|
254
|
#
|
254
|
255
|
out.append(text[0])
|
255
|
256
|
count += 1
|
|
@@ -284,7 +285,7 @@ class Diff(object):
|
284
|
285
|
left_num_fmt = colorize('%%(left_num)%ds' % num_width, 'yellow')
|
285
|
286
|
right_num_fmt = colorize('%%(right_num)%ds' % num_width, 'yellow')
|
286
|
287
|
line_fmt = left_num_fmt + ' %(left)s ' + COLORS['reset'] + \
|
287
|
|
- right_num_fmt + ' %(right)s\n'
|
|
288
|
+ right_num_fmt + ' %(right)s\n'
|
288
|
289
|
|
289
|
290
|
# yield header, old path and new path
|
290
|
291
|
for line in self._headers:
|
|
@@ -315,7 +316,8 @@ class Diff(object):
|
315
|
316
|
if not old[0]:
|
316
|
317
|
left = '%*s' % (width, ' ')
|
317
|
318
|
right = right.lstrip('\x00+').rstrip('\x01')
|
318
|
|
- right = _fit_with_marker(right, self._markup_new, width)
|
|
319
|
+ right = _fit_with_marker(
|
|
320
|
+ right, self._markup_new, width)
|
319
|
321
|
elif not new[0]:
|
320
|
322
|
left = left.lstrip('\x00-').rstrip('\x01')
|
321
|
323
|
left = _fit_with_marker(left, self._markup_old, width)
|
|
@@ -324,7 +326,8 @@ class Diff(object):
|
324
|
326
|
left = _fit_with_marker_mix(left, 'red', width, 1)
|
325
|
327
|
right = _fit_with_marker_mix(right, 'green', width)
|
326
|
328
|
else:
|
327
|
|
- left = _fit_with_marker(left, self._markup_common, width, 1)
|
|
329
|
+ left = _fit_with_marker(
|
|
330
|
+ left, self._markup_common, width, 1)
|
328
|
331
|
right = _fit_with_marker(right, self._markup_common, width)
|
329
|
332
|
yield line_fmt % {
|
330
|
333
|
'left_num': left_num,
|
|
@@ -378,8 +381,8 @@ class Udiff(Diff):
|
378
|
381
|
return line.startswith('+++ ')
|
379
|
382
|
|
380
|
383
|
def is_hunk_meta(self, line):
|
381
|
|
- """Minimal valid hunk meta is like '@@ -1 +1 @@', note extra chars might
|
382
|
|
- occur after the ending @@, e.g. in git log
|
|
384
|
+ """Minimal valid hunk meta is like '@@ -1 +1 @@', note extra chars
|
|
385
|
+ might occur after the ending @@, e.g. in git log
|
383
|
386
|
"""
|
384
|
387
|
return (line.startswith('@@ -') and line.find(' @@') >= 8) or \
|
385
|
388
|
(line.startswith('## -') and line.find(' ##') >= 8)
|
|
@@ -410,7 +413,7 @@ class Udiff(Diff):
|
410
|
413
|
'----' likely to see in diff from yaml file
|
411
|
414
|
"""
|
412
|
415
|
return line.startswith('-') and not self.is_old_path(line) and \
|
413
|
|
- not re.match(r'^-{5,}$', line.rstrip())
|
|
416
|
+ not re.match(r'^-{5,}$', line.rstrip())
|
414
|
417
|
|
415
|
418
|
def is_new(self, line):
|
416
|
419
|
return line.startswith('+') and not self.is_new_path(line)
|
|
@@ -529,17 +532,19 @@ class DiffParser(object):
|
529
|
532
|
headers = []
|
530
|
533
|
diff._hunks.append(hunk)
|
531
|
534
|
|
532
|
|
- elif len(diff._hunks) > 0 and (difflet.is_old(line) or \
|
533
|
|
- difflet.is_new(line) or difflet.is_common(line)):
|
|
535
|
+ elif len(diff._hunks) > 0 and (difflet.is_old(line) or
|
|
536
|
+ difflet.is_new(line) or
|
|
537
|
+ difflet.is_common(line)):
|
534
|
538
|
diff._hunks[-1].append(difflet.parse_hunk_line(line))
|
535
|
539
|
|
536
|
540
|
elif difflet.is_eof(line):
|
537
|
541
|
# ignore
|
538
|
542
|
pass
|
539
|
543
|
|
540
|
|
- elif difflet.is_only_in_dir(line) or difflet.is_binary_differ(line):
|
541
|
|
- # 'Only in foo:' and 'Binary files ... differ' are considered as
|
542
|
|
- # separate diffs, so yield current diff, then this line
|
|
544
|
+ elif difflet.is_only_in_dir(line) or \
|
|
545
|
+ difflet.is_binary_differ(line):
|
|
546
|
+ # 'Only in foo:' and 'Binary files ... differ' are considered
|
|
547
|
+ # as separate diffs, so yield current diff, then this line
|
543
|
548
|
#
|
544
|
549
|
if diff._old_path and diff._new_path and len(diff._hunks) > 0:
|
545
|
550
|
# Current diff is comppletely constructed
|
|
@@ -594,11 +599,11 @@ class DiffMarkup(object):
|
594
|
599
|
def markup_to_pager(stream, opts):
|
595
|
600
|
markup = DiffMarkup(stream)
|
596
|
601
|
color_diff = markup.markup(side_by_side=opts.side_by_side,
|
597
|
|
- width=opts.width)
|
|
602
|
+ width=opts.width)
|
598
|
603
|
|
599
|
604
|
# Args stolen from git source: github.com/git/git/blob/master/pager.c
|
600
|
|
- pager = subprocess.Popen(['less', '-FRSX'],
|
601
|
|
- stdin=subprocess.PIPE, stdout=sys.stdout)
|
|
605
|
+ pager = subprocess.Popen(
|
|
606
|
+ ['less', '-FRSX'], stdin=subprocess.PIPE, stdout=sys.stdout)
|
602
|
607
|
try:
|
603
|
608
|
for line in color_diff:
|
604
|
609
|
pager.stdin.write(line.encode('utf-8'))
|
|
@@ -623,7 +628,7 @@ def revision_control_diff(args):
|
623
|
628
|
for _, ops in VCS_INFO.items():
|
624
|
629
|
if check_command_status(ops['probe']):
|
625
|
630
|
return subprocess.Popen(
|
626
|
|
- ops['diff'] + args, stdout=subprocess.PIPE).stdout
|
|
631
|
+ ops['diff'] + args, stdout=subprocess.PIPE).stdout
|
627
|
632
|
|
628
|
633
|
|
629
|
634
|
def revision_control_log(args):
|
|
@@ -631,7 +636,7 @@ def revision_control_log(args):
|
631
|
636
|
for _, ops in VCS_INFO.items():
|
632
|
637
|
if check_command_status(ops['probe']):
|
633
|
638
|
return subprocess.Popen(
|
634
|
|
- ops['log'] + args, stdout=subprocess.PIPE).stdout
|
|
639
|
+ ops['log'] + args, stdout=subprocess.PIPE).stdout
|
635
|
640
|
|
636
|
641
|
|
637
|
642
|
def decode(line):
|
|
@@ -648,17 +653,21 @@ def main():
|
648
|
653
|
supported_vcs = sorted(VCS_INFO.keys())
|
649
|
654
|
|
650
|
655
|
usage = """%prog [options] [file|dir ...]"""
|
651
|
|
- parser = optparse.OptionParser(usage=usage,
|
652
|
|
- description=META_INFO['description'],
|
653
|
|
- version='%%prog %s' % META_INFO['version'])
|
654
|
|
- parser.add_option('-s', '--side-by-side', action='store_true',
|
655
|
|
- help='enable side-by-side mode')
|
656
|
|
- parser.add_option('-w', '--width', type='int', default=80, metavar='N',
|
657
|
|
- help='set text width (side-by-side mode only), default is 80')
|
658
|
|
- parser.add_option('-l', '--log', action='store_true',
|
659
|
|
- help='show log with changes from revision control')
|
660
|
|
- parser.add_option('-c', '--color', default='auto', metavar='X',
|
661
|
|
- help='colorize mode "auto" (default), "always", or "never"')
|
|
656
|
+ parser = optparse.OptionParser(
|
|
657
|
+ usage=usage, description=META_INFO['description'],
|
|
658
|
+ version='%%prog %s' % META_INFO['version'])
|
|
659
|
+ parser.add_option(
|
|
660
|
+ '-s', '--side-by-side', action='store_true',
|
|
661
|
+ help='enable side-by-side mode')
|
|
662
|
+ parser.add_option(
|
|
663
|
+ '-w', '--width', type='int', default=80, metavar='N',
|
|
664
|
+ help='set text width for side-by-side mode, default is 80')
|
|
665
|
+ parser.add_option(
|
|
666
|
+ '-l', '--log', action='store_true',
|
|
667
|
+ help='show log with changes from revision control')
|
|
668
|
+ parser.add_option(
|
|
669
|
+ '-c', '--color', default='auto', metavar='X',
|
|
670
|
+ help="""colorize mode 'auto' (default), 'always', or 'never'""")
|
662
|
671
|
opts, args = parser.parse_args()
|
663
|
672
|
|
664
|
673
|
if opts.log:
|
|
@@ -683,7 +692,8 @@ def main():
|
683
|
692
|
if stream.is_empty():
|
684
|
693
|
return 0
|
685
|
694
|
|
686
|
|
- if opts.color == 'always' or (opts.color == 'auto' and sys.stdout.isatty()):
|
|
695
|
+ if opts.color == 'always' or \
|
|
696
|
+ (opts.color == 'auto' and sys.stdout.isatty()):
|
687
|
697
|
try:
|
688
|
698
|
markup_to_pager(stream, opts)
|
689
|
699
|
except IOError:
|
|
@@ -704,4 +714,4 @@ def main():
|
704
|
714
|
if __name__ == '__main__':
|
705
|
715
|
sys.exit(main())
|
706
|
716
|
|
707
|
|
-# vim:set et sts=4 sw=4 tw=80:
|
|
717
|
+# vim:set et sts=4 sw=4 tw=79:
|