|
@@ -2,8 +2,9 @@
|
2
|
2
|
# -*- coding: utf-8 -*-
|
3
|
3
|
|
4
|
4
|
"""
|
5
|
|
-Term based tool to view colored, incremental diff in unified format or side by
|
6
|
|
-side with auto pager. Requires Python (>= 2.5.0) and less.
|
|
5
|
+Term based tool to view **colored**, **incremental** diff in *git/svn/hg*
|
|
6
|
+workspace, or diff from given file or stdin, with **side by side** and **auto
|
|
7
|
+pager** support. Requires python (>= 2.5.0) and ``less``.
|
7
|
8
|
|
8
|
9
|
AUTHOR : Matthew Wang <mattwyl(@)gmail(.)com>
|
9
|
10
|
LICENSE : BSD-3
|
|
@@ -127,6 +128,38 @@ class Diff(object):
|
127
|
128
|
self._new_path = new_path
|
128
|
129
|
self._hunks = hunks
|
129
|
130
|
|
|
131
|
+ # Follow detector and the parse_hunk_header() are suppose to be overwritten
|
|
132
|
+ # by derived class
|
|
133
|
+ #
|
|
134
|
+ def is_old_path(self, line):
|
|
135
|
+ return False
|
|
136
|
+
|
|
137
|
+ def is_new_path(self, line):
|
|
138
|
+ return False
|
|
139
|
+
|
|
140
|
+ def is_hunk_header(self, line):
|
|
141
|
+ return False
|
|
142
|
+
|
|
143
|
+ def parse_hunk_header(self, line):
|
|
144
|
+ """Returns a 2-eliment tuple, each of them is a tuple in form of (start,
|
|
145
|
+ offset)"""
|
|
146
|
+ return False
|
|
147
|
+
|
|
148
|
+ def is_old(self, line):
|
|
149
|
+ return False
|
|
150
|
+
|
|
151
|
+ def is_new(self, line):
|
|
152
|
+ return False
|
|
153
|
+
|
|
154
|
+ def is_common(self, line):
|
|
155
|
+ return False
|
|
156
|
+
|
|
157
|
+ def is_eof(self, line):
|
|
158
|
+ return False
|
|
159
|
+
|
|
160
|
+ def is_header(self, line):
|
|
161
|
+ return False
|
|
162
|
+
|
130
|
163
|
def markup_traditional(self):
|
131
|
164
|
"""Returns a generator"""
|
132
|
165
|
for line in self._headers:
|
|
@@ -295,47 +328,64 @@ class Diff(object):
|
295
|
328
|
|
296
|
329
|
class Udiff(Diff):
|
297
|
330
|
|
298
|
|
- @staticmethod
|
299
|
|
- def is_old_path(line):
|
|
331
|
+ def is_old_path(self, line):
|
300
|
332
|
return line.startswith('--- ')
|
301
|
333
|
|
302
|
|
- @staticmethod
|
303
|
|
- def is_new_path(line):
|
|
334
|
+ def is_new_path(self, line):
|
304
|
335
|
return line.startswith('+++ ')
|
305
|
336
|
|
306
|
|
- @staticmethod
|
307
|
|
- def is_hunk_header(line):
|
|
337
|
+ def is_hunk_header(self, line):
|
308
|
338
|
return line.startswith('@@ -')
|
309
|
339
|
|
310
|
|
- @staticmethod
|
311
|
|
- def is_old(line):
|
312
|
|
- return line.startswith('-') and not Udiff.is_old_path(line)
|
|
340
|
+ def parse_hunk_header(self, hunk_header):
|
|
341
|
+ # @@ -3,7 +3,6 @@
|
|
342
|
+ a = hunk_header.split()[1].split(',') # -3 7
|
|
343
|
+ if len(a) > 1:
|
|
344
|
+ old_addr = (int(a[0][1:]), int(a[1]))
|
|
345
|
+ else:
|
|
346
|
+ # @@ -1 +1,2 @@
|
|
347
|
+ old_addr = (int(a[0][1:]), 0)
|
|
348
|
+
|
|
349
|
+ b = hunk_header.split()[2].split(',') # +3 6
|
|
350
|
+ if len(b) > 1:
|
|
351
|
+ new_addr = (int(b[0][1:]), int(b[1]))
|
|
352
|
+ else:
|
|
353
|
+ # @@ -0,0 +1 @@
|
|
354
|
+ new_addr = (int(b[0][1:]), 0)
|
313
|
355
|
|
314
|
|
- @staticmethod
|
315
|
|
- def is_new(line):
|
316
|
|
- return line.startswith('+') and not Udiff.is_new_path(line)
|
|
356
|
+ return (old_addr, new_addr)
|
317
|
357
|
|
318
|
|
- @staticmethod
|
319
|
|
- def is_common(line):
|
|
358
|
+ def is_old(self, line):
|
|
359
|
+ return line.startswith('-') and not self.is_old_path(line)
|
|
360
|
+
|
|
361
|
+ def is_new(self, line):
|
|
362
|
+ return line.startswith('+') and not self.is_new_path(line)
|
|
363
|
+
|
|
364
|
+ def is_common(self, line):
|
320
|
365
|
return line.startswith(' ')
|
321
|
366
|
|
322
|
|
- @staticmethod
|
323
|
|
- def is_eof(line):
|
|
367
|
+ def is_eof(self, line):
|
324
|
368
|
# \ No newline at end of file
|
325
|
369
|
return line.startswith('\\')
|
326
|
370
|
|
327
|
|
- @staticmethod
|
328
|
|
- def is_header(line):
|
|
371
|
+ def is_header(self, line):
|
329
|
372
|
return re.match(r'^[^+@\\ -]', line)
|
330
|
373
|
|
331
|
374
|
|
332
|
375
|
class DiffParser(object):
|
333
|
376
|
|
334
|
377
|
def __init__(self, stream):
|
|
378
|
+ """Detect Udiff with 3 conditions"""
|
|
379
|
+ flag = 0
|
335
|
380
|
for line in stream[:20]:
|
336
|
|
- if line.startswith('+++ '):
|
337
|
|
- self._type = 'udiff'
|
338
|
|
- break
|
|
381
|
+ if line.startswith('--- '):
|
|
382
|
+ flag |= 1
|
|
383
|
+ elif line.startswith('+++ '):
|
|
384
|
+ flag |= 2
|
|
385
|
+ elif line.startswith('@@ '):
|
|
386
|
+ flag |= 4
|
|
387
|
+ if flag & 7:
|
|
388
|
+ self._type = 'udiff'
|
339
|
389
|
else:
|
340
|
390
|
raise RuntimeError('unknown diff type')
|
341
|
391
|
|
|
@@ -344,18 +394,16 @@ class DiffParser(object):
|
344
|
394
|
except (AssertionError, IndexError):
|
345
|
395
|
raise RuntimeError('invalid patch format')
|
346
|
396
|
|
347
|
|
-
|
348
|
397
|
def get_diffs(self):
|
349
|
398
|
return self._diffs
|
350
|
399
|
|
351
|
400
|
def _parse(self, stream):
|
|
401
|
+ """parse all diff lines, construct a list of Diff objects"""
|
352
|
402
|
if self._type == 'udiff':
|
353
|
|
- return self._parse_udiff(stream)
|
|
403
|
+ difflet = Udiff(None, None, None, None)
|
354
|
404
|
else:
|
355
|
405
|
raise RuntimeError('unsupported diff format')
|
356
|
406
|
|
357
|
|
- def _parse_udiff(self, stream):
|
358
|
|
- """parse all diff lines here, construct a list of Udiff objects"""
|
359
|
407
|
out_diffs = []
|
360
|
408
|
headers = []
|
361
|
409
|
old_path = None
|
|
@@ -367,8 +415,8 @@ class DiffParser(object):
|
367
|
415
|
# 'common' line occurs before 'old_path' is considered as header
|
368
|
416
|
# too, this happens with `git log -p` and `git show <commit>`
|
369
|
417
|
#
|
370
|
|
- if Udiff.is_header(stream[0]) or \
|
371
|
|
- (Udiff.is_common(stream[0]) and old_path is None):
|
|
418
|
+ if difflet.is_header(stream[0]) or \
|
|
419
|
+ (difflet.is_common(stream[0]) and old_path is None):
|
372
|
420
|
if headers and old_path:
|
373
|
421
|
# Encounter a new header
|
374
|
422
|
assert new_path is not None
|
|
@@ -383,7 +431,7 @@ class DiffParser(object):
|
383
|
431
|
else:
|
384
|
432
|
headers.append(stream.pop(0))
|
385
|
433
|
|
386
|
|
- elif Udiff.is_old_path(stream[0]):
|
|
434
|
+ elif difflet.is_old_path(stream[0]):
|
387
|
435
|
if old_path:
|
388
|
436
|
# Encounter a new patch set
|
389
|
437
|
assert new_path is not None
|
|
@@ -398,12 +446,12 @@ class DiffParser(object):
|
398
|
446
|
else:
|
399
|
447
|
old_path = stream.pop(0)
|
400
|
448
|
|
401
|
|
- elif Udiff.is_new_path(stream[0]):
|
|
449
|
+ elif difflet.is_new_path(stream[0]):
|
402
|
450
|
assert old_path is not None
|
403
|
451
|
assert new_path is None
|
404
|
452
|
new_path = stream.pop(0)
|
405
|
453
|
|
406
|
|
- elif Udiff.is_hunk_header(stream[0]):
|
|
454
|
+ elif difflet.is_hunk_header(stream[0]):
|
407
|
455
|
assert old_path is not None
|
408
|
456
|
assert new_path is not None
|
409
|
457
|
if hunk:
|
|
@@ -411,32 +459,19 @@ class DiffParser(object):
|
411
|
459
|
hunks.append(hunk)
|
412
|
460
|
hunk = None
|
413
|
461
|
else:
|
414
|
|
- # @@ -3,7 +3,6 @@
|
415
|
462
|
hunk_header = stream.pop(0)
|
416
|
|
- a = hunk_header.split()[1].split(',') # -3 7
|
417
|
|
- if len(a) > 1:
|
418
|
|
- old_addr = (int(a[0][1:]), int(a[1]))
|
419
|
|
- else:
|
420
|
|
- # @@ -1 +1,2 @@
|
421
|
|
- old_addr = (int(a[0][1:]), 0)
|
422
|
|
-
|
423
|
|
- b = hunk_header.split()[2].split(',') # +3 6
|
424
|
|
- if len(b) > 1:
|
425
|
|
- new_addr = (int(b[0][1:]), int(b[1]))
|
426
|
|
- else:
|
427
|
|
- # @@ -0,0 +1 @@
|
428
|
|
- new_addr = (int(b[0][1:]), 0)
|
|
463
|
+ old_addr, new_addr = difflet.parse_hunk_header(hunk_header)
|
429
|
464
|
hunk = Hunk(hunk_header, old_addr, new_addr)
|
430
|
465
|
|
431
|
|
- elif Udiff.is_old(stream[0]) or Udiff.is_new(stream[0]) or \
|
432
|
|
- Udiff.is_common(stream[0]):
|
|
466
|
+ elif difflet.is_old(stream[0]) or difflet.is_new(stream[0]) or \
|
|
467
|
+ difflet.is_common(stream[0]):
|
433
|
468
|
assert old_path is not None
|
434
|
469
|
assert new_path is not None
|
435
|
470
|
assert hunk is not None
|
436
|
471
|
hunk_line = stream.pop(0)
|
437
|
472
|
hunk.append(hunk_line[0], hunk_line[1:])
|
438
|
473
|
|
439
|
|
- elif Udiff.is_eof(stream[0]):
|
|
474
|
+ elif difflet.is_eof(stream[0]):
|
440
|
475
|
# ignore
|
441
|
476
|
stream.pop(0)
|
442
|
477
|
|
|
@@ -526,11 +561,9 @@ def main():
|
526
|
561
|
supported_vcs = [check[0] for check, _ in REVISION_CONTROL]
|
527
|
562
|
|
528
|
563
|
usage = '%prog [options] [diff]'
|
529
|
|
- description= ('View colored, incremental diff in unified format or '
|
530
|
|
- 'side by side with auto pager. Read diff from diff '
|
531
|
|
- '(patch) file if given, or stdin if redirected, or '
|
532
|
|
- 'diff produced by revision tool if in a %s workspace') \
|
533
|
|
- % '/'.join(supported_vcs)
|
|
564
|
+ description= ('View colored, incremental diff in %s workspace, or diff '
|
|
565
|
+ 'from given file or stdin, with side by side and auto '
|
|
566
|
+ 'pager support') % '/'.join(supported_vcs)
|
534
|
567
|
|
535
|
568
|
parser = optparse.OptionParser(usage=usage, description=description,
|
536
|
569
|
version='%%prog %s' % __version__)
|