|
@@ -68,14 +68,18 @@ def colorize(text, start_color, end_color='reset'):
|
68
|
68
|
|
69
|
69
|
class Hunk(object):
|
70
|
70
|
|
71
|
|
- def __init__(self, hunk_header, old_addr, new_addr):
|
72
|
|
- self._hunk_header = hunk_header
|
|
71
|
+ def __init__(self, hunk_headers, hunk_meta, old_addr, new_addr):
|
|
72
|
+ self._hunk_headers = hunk_headers
|
|
73
|
+ self._hunk_meta = hunk_meta
|
73
|
74
|
self._old_addr = old_addr # tuple (start, offset)
|
74
|
75
|
self._new_addr = new_addr # tuple (start, offset)
|
75
|
76
|
self._hunk_list = [] # list of tuple (attr, line)
|
76
|
77
|
|
77
|
|
- def get_header(self):
|
78
|
|
- return self._hunk_header
|
|
78
|
+ def get_hunk_headers(self):
|
|
79
|
+ return self._hunk_headers
|
|
80
|
+
|
|
81
|
+ def get_hunk_meta(self):
|
|
82
|
+ return self._hunk_meta
|
79
|
83
|
|
80
|
84
|
def get_old_addr(self):
|
81
|
85
|
return self._old_addr
|
|
@@ -131,8 +135,9 @@ class Diff(object):
|
131
|
135
|
self._new_path = new_path
|
132
|
136
|
self._hunks = hunks
|
133
|
137
|
|
134
|
|
- # Follow detector and the parse_hunk_header() are suppose to be overwritten
|
135
|
|
- # by derived class
|
|
138
|
+ # Follow detector and the parse_hunk_meta() are suppose to be overwritten
|
|
139
|
+ # by derived class. No is_header() anymore, all non-recognized lines are
|
|
140
|
+ # considered as headers
|
136
|
141
|
#
|
137
|
142
|
def is_old_path(self, line):
|
138
|
143
|
return False
|
|
@@ -140,10 +145,10 @@ class Diff(object):
|
140
|
145
|
def is_new_path(self, line):
|
141
|
146
|
return False
|
142
|
147
|
|
143
|
|
- def is_hunk_header(self, line):
|
|
148
|
+ def is_hunk_meta(self, line):
|
144
|
149
|
return False
|
145
|
150
|
|
146
|
|
- def parse_hunk_header(self, line):
|
|
151
|
+ def parse_hunk_meta(self, line):
|
147
|
152
|
"""Returns a 2-eliment tuple, each of them is a tuple in form of (start,
|
148
|
153
|
offset)"""
|
149
|
154
|
return False
|
|
@@ -160,9 +165,6 @@ class Diff(object):
|
160
|
165
|
def is_eof(self, line):
|
161
|
166
|
return False
|
162
|
167
|
|
163
|
|
- def is_header(self, line):
|
164
|
|
- return False
|
165
|
|
-
|
166
|
168
|
def markup_traditional(self):
|
167
|
169
|
"""Returns a generator"""
|
168
|
170
|
for line in self._headers:
|
|
@@ -172,7 +174,9 @@ class Diff(object):
|
172
|
174
|
yield self._markup_new_path(self._new_path)
|
173
|
175
|
|
174
|
176
|
for hunk in self._hunks:
|
175
|
|
- yield self._markup_hunk_header(hunk.get_header())
|
|
177
|
+ for hunk_header in hunk.get_hunk_headers():
|
|
178
|
+ yield self._markup_hunk_header(hunk_header)
|
|
179
|
+ yield self._markup_hunk_meta(hunk.get_hunk_meta())
|
176
|
180
|
for old, new, changed in hunk.mdiff():
|
177
|
181
|
if changed:
|
178
|
182
|
if not old[0]:
|
|
@@ -253,7 +257,9 @@ class Diff(object):
|
253
|
257
|
|
254
|
258
|
# yield hunks
|
255
|
259
|
for hunk in self._hunks:
|
256
|
|
- yield self._markup_hunk_header(hunk.get_header())
|
|
260
|
+ for hunk_header in hunk.get_hunk_headers():
|
|
261
|
+ yield self._markup_hunk_header(hunk_header)
|
|
262
|
+ yield self._markup_hunk_meta(hunk.get_hunk_meta())
|
257
|
263
|
for old, new, changed in hunk.mdiff():
|
258
|
264
|
if old[0]:
|
259
|
265
|
left_num = str(hunk.get_old_addr()[0] + int(old[0]) - 1)
|
|
@@ -300,6 +306,9 @@ class Diff(object):
|
300
|
306
|
return colorize(line, 'yellow')
|
301
|
307
|
|
302
|
308
|
def _markup_hunk_header(self, line):
|
|
309
|
+ return colorize(line, 'lightcyan')
|
|
310
|
+
|
|
311
|
+ def _markup_hunk_meta(self, line):
|
303
|
312
|
return colorize(line, 'lightblue')
|
304
|
313
|
|
305
|
314
|
def _markup_common(self, line):
|
|
@@ -337,19 +346,19 @@ class Udiff(Diff):
|
337
|
346
|
def is_new_path(self, line):
|
338
|
347
|
return line.startswith('+++ ')
|
339
|
348
|
|
340
|
|
- def is_hunk_header(self, line):
|
341
|
|
- return line.startswith('@@ -')
|
|
349
|
+ def is_hunk_meta(self, line):
|
|
350
|
+ return line.startswith('@@ -') or line.startswith('## -')
|
342
|
351
|
|
343
|
|
- def parse_hunk_header(self, hunk_header):
|
|
352
|
+ def parse_hunk_meta(self, hunk_meta):
|
344
|
353
|
# @@ -3,7 +3,6 @@
|
345
|
|
- a = hunk_header.split()[1].split(',') # -3 7
|
|
354
|
+ a = hunk_meta.split()[1].split(',') # -3 7
|
346
|
355
|
if len(a) > 1:
|
347
|
356
|
old_addr = (int(a[0][1:]), int(a[1]))
|
348
|
357
|
else:
|
349
|
358
|
# @@ -1 +1,2 @@
|
350
|
359
|
old_addr = (int(a[0][1:]), 0)
|
351
|
360
|
|
352
|
|
- b = hunk_header.split()[2].split(',') # +3 6
|
|
361
|
+ b = hunk_meta.split()[2].split(',') # +3 6
|
353
|
362
|
if len(b) > 1:
|
354
|
363
|
new_addr = (int(b[0][1:]), int(b[1]))
|
355
|
364
|
else:
|
|
@@ -359,7 +368,9 @@ class Udiff(Diff):
|
359
|
368
|
return (old_addr, new_addr)
|
360
|
369
|
|
361
|
370
|
def is_old(self, line):
|
362
|
|
- return line.startswith('-') and not self.is_old_path(line)
|
|
371
|
+ """Exclude header line from svn log --diff output"""
|
|
372
|
+ return line.startswith('-') and not self.is_old_path(line) and \
|
|
373
|
+ not re.match(r'^-{4,}$', line.rstrip())
|
363
|
374
|
|
364
|
375
|
def is_new(self, line):
|
365
|
376
|
return line.startswith('+') and not self.is_new_path(line)
|
|
@@ -369,26 +380,27 @@ class Udiff(Diff):
|
369
|
380
|
|
370
|
381
|
def is_eof(self, line):
|
371
|
382
|
# \ No newline at end of file
|
372
|
|
- return line.startswith('\\')
|
373
|
|
-
|
374
|
|
- def is_header(self, line):
|
375
|
|
- return re.match(r'^[^+@\\ -]', line)
|
|
383
|
+ # \ No newline at end of property
|
|
384
|
+ return line.startswith(r'\ No newline at end of')
|
376
|
385
|
|
377
|
386
|
|
378
|
387
|
class DiffParser(object):
|
379
|
388
|
|
380
|
389
|
def __init__(self, stream):
|
381
|
|
- """Detect Udiff with 3 conditions"""
|
|
390
|
+ """Detect Udiff with 3 conditions, '## ' uaually indicates svn property
|
|
391
|
+ changes in output from `svn log --diff`
|
|
392
|
+ """
|
382
|
393
|
flag = 0
|
383
|
|
- for line in stream[:20]:
|
|
394
|
+ for line in stream[:100]:
|
384
|
395
|
if line.startswith('--- '):
|
385
|
396
|
flag |= 1
|
386
|
397
|
elif line.startswith('+++ '):
|
387
|
398
|
flag |= 2
|
388
|
|
- elif line.startswith('@@ '):
|
|
399
|
+ elif line.startswith('@@ ') or line.startswith('## '):
|
389
|
400
|
flag |= 4
|
390
|
|
- if flag & 7:
|
391
|
|
- self._type = 'udiff'
|
|
401
|
+ if flag & 7:
|
|
402
|
+ self._type = 'udiff'
|
|
403
|
+ break
|
392
|
404
|
else:
|
393
|
405
|
raise RuntimeError('unknown diff type')
|
394
|
406
|
|
|
@@ -409,89 +421,49 @@ class DiffParser(object):
|
409
|
421
|
|
410
|
422
|
out_diffs = []
|
411
|
423
|
headers = []
|
412
|
|
- old_path = None
|
413
|
|
- new_path = None
|
414
|
|
- hunks = []
|
415
|
|
- hunk = None
|
416
|
424
|
|
417
|
425
|
while stream:
|
418
|
|
- # 'common' line occurs before 'old_path' is considered as header
|
419
|
|
- # too, this happens with `git log -p` and `git show <commit>`
|
420
|
|
- #
|
421
|
|
- if difflet.is_header(stream[0]) or \
|
422
|
|
- (difflet.is_common(stream[0]) and old_path is None):
|
423
|
|
- if headers and old_path:
|
424
|
|
- # Encounter a new header
|
425
|
|
- assert new_path is not None
|
426
|
|
- assert hunk is not None
|
427
|
|
- hunks.append(hunk)
|
428
|
|
- out_diffs.append(Diff(headers, old_path, new_path, hunks))
|
429
|
|
- headers = []
|
430
|
|
- old_path = None
|
431
|
|
- new_path = None
|
432
|
|
- hunks = []
|
433
|
|
- hunk = None
|
434
|
|
- else:
|
435
|
|
- headers.append(stream.pop(0))
|
436
|
|
-
|
437
|
|
- elif difflet.is_old_path(stream[0]):
|
438
|
|
- if old_path:
|
439
|
|
- # Encounter a new patch set
|
440
|
|
- assert new_path is not None
|
441
|
|
- assert hunk is not None
|
442
|
|
- hunks.append(hunk)
|
443
|
|
- out_diffs.append(Diff(headers, old_path, new_path, hunks))
|
444
|
|
- headers = []
|
445
|
|
- old_path = None
|
446
|
|
- new_path = None
|
447
|
|
- hunks = []
|
448
|
|
- hunk = None
|
449
|
|
- else:
|
450
|
|
- old_path = stream.pop(0)
|
|
426
|
+ if difflet.is_old_path(stream[0]):
|
|
427
|
+ old_path = stream.pop(0)
|
|
428
|
+ out_diffs.append(Diff(headers, old_path, None, []))
|
|
429
|
+ headers = []
|
451
|
430
|
|
452
|
431
|
elif difflet.is_new_path(stream[0]):
|
453
|
|
- assert old_path is not None
|
454
|
|
- assert new_path is None
|
455
|
432
|
new_path = stream.pop(0)
|
456
|
|
-
|
457
|
|
- elif difflet.is_hunk_header(stream[0]):
|
458
|
|
- assert old_path is not None
|
459
|
|
- assert new_path is not None
|
460
|
|
- if hunk:
|
461
|
|
- # Encounter a new hunk header
|
462
|
|
- hunks.append(hunk)
|
463
|
|
- hunk = None
|
464
|
|
- else:
|
465
|
|
- hunk_header = stream.pop(0)
|
466
|
|
- old_addr, new_addr = difflet.parse_hunk_header(hunk_header)
|
467
|
|
- hunk = Hunk(hunk_header, old_addr, new_addr)
|
468
|
|
-
|
469
|
|
- elif difflet.is_old(stream[0]) or difflet.is_new(stream[0]) or \
|
470
|
|
- difflet.is_common(stream[0]):
|
471
|
|
- assert old_path is not None
|
472
|
|
- assert new_path is not None
|
473
|
|
- assert hunk is not None
|
|
433
|
+ out_diffs[-1]._new_path = new_path
|
|
434
|
+
|
|
435
|
+ elif difflet.is_hunk_meta(stream[0]):
|
|
436
|
+ hunk_meta = stream.pop(0)
|
|
437
|
+ old_addr, new_addr = difflet.parse_hunk_meta(hunk_meta)
|
|
438
|
+ hunk = Hunk(headers, hunk_meta, old_addr, new_addr)
|
|
439
|
+ headers = []
|
|
440
|
+ out_diffs[-1]._hunks.append(hunk)
|
|
441
|
+
|
|
442
|
+ elif out_diffs and out_diffs[-1]._hunks and \
|
|
443
|
+ (difflet.is_old(stream[0]) or difflet.is_new(stream[0]) or \
|
|
444
|
+ difflet.is_common(stream[0])):
|
474
|
445
|
hunk_line = stream.pop(0)
|
475
|
|
- hunk.append(hunk_line[0], hunk_line[1:])
|
|
446
|
+ out_diffs[-1]._hunks[-1].append(hunk_line[0], hunk_line[1:])
|
476
|
447
|
|
477
|
448
|
elif difflet.is_eof(stream[0]):
|
478
|
449
|
# ignore
|
479
|
450
|
stream.pop(0)
|
480
|
451
|
|
481
|
452
|
else:
|
482
|
|
- raise RuntimeError('unknown patch format: %s' % stream[0])
|
483
|
|
-
|
484
|
|
- # The last patch
|
485
|
|
- if hunk:
|
486
|
|
- hunks.append(hunk)
|
487
|
|
- if old_path:
|
488
|
|
- if new_path:
|
489
|
|
- out_diffs.append(Diff(headers, old_path, new_path, hunks))
|
490
|
|
- else:
|
491
|
|
- raise RuntimeError('unknown patch format after "%s"' % old_path)
|
492
|
|
- elif headers:
|
493
|
|
- raise RuntimeError('unknown patch format: %s' % \
|
494
|
|
- ('\n'.join(headers)))
|
|
453
|
+ # All other non-recognized lines are considered as headers or
|
|
454
|
+ # hunk headers respectively
|
|
455
|
+ #
|
|
456
|
+ headers.append(stream.pop(0))
|
|
457
|
+
|
|
458
|
+ if headers:
|
|
459
|
+ raise RuntimeError('dangling header(s):\n%s' % ''.join(headers))
|
|
460
|
+
|
|
461
|
+ # Validate the last patch set
|
|
462
|
+ if out_diffs:
|
|
463
|
+ assert out_diffs[-1]._old_path is not None
|
|
464
|
+ assert out_diffs[-1]._new_path is not None
|
|
465
|
+ assert len(out_diffs[-1]._hunks) > 0
|
|
466
|
+ assert len(out_diffs[-1]._hunks[-1]._hunk_meta) > 0
|
495
|
467
|
|
496
|
468
|
return out_diffs
|
497
|
469
|
|