Browse Source

Better patch parser; support svn log --diff

Matthew Wang 11 years ago
parent
commit
20a8acacf6
10 changed files with 306 additions and 107 deletions
  1. 1
    2
      Makefile
  2. 1
    4
      cdiff
  3. 71
    99
      cdiff.py
  4. 3
    1
      tests/README
  5. 2
    1
      tests/regression.sh
  6. 53
    0
      tests/svn-log/in.diff
  7. 52
    0
      tests/svn-log/out.normal
  8. 50
    0
      tests/svn-log/out.side-by-side
  9. 50
    0
      tests/svn-log/out.w70
  10. 23
    0
      tests/test_cdiff.py

+ 1
- 2
Makefile View File

@@ -5,8 +5,7 @@ TESTPYPI = http://testpypi.python.org/pypi
5 5
 .PHONY: dogfood test clean build dist-test dist
6 6
 
7 7
 dogfood:
8
-	./cdiff.py -s
9
-	git diff | ./cdiff.py
8
+	./cdiff.py
10 9
 	git diff | ./cdiff.py -s
11 10
 
12 11
 test:

+ 1
- 4
cdiff View File

@@ -4,9 +4,6 @@
4 4
 import sys
5 5
 import cdiff
6 6
 
7
-try:
8
-    sys.exit(cdiff.main())
9
-except:
10
-    sys.exit(1)
7
+sys.exit(cdiff.main())
11 8
 
12 9
 # vim:set et sts=4 sw=4 tw=80:

+ 71
- 99
cdiff.py View File

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

+ 3
- 1
tests/README View File

@@ -1,4 +1,6 @@
1
-# To generate expected output, use following command, then review with `less -R`
1
+# To generate expected output, chdir to a subdir and use following command, then
2
+# review with `less -R`
3
+#
2 4
 ../../cdiff.py -c always in.diff > out.normal 
3 5
 ../../cdiff.py -c always -s in.diff > out.side-by-side
4 6
 ../../cdiff.py -c always -s in.diff -w70 > out.w70

+ 2
- 1
tests/regression.sh View File

@@ -30,7 +30,8 @@ function cmpOutput()
30 30
     local cdiff_opt=${3:-""}
31 31
 
32 32
     echo -n "Test option '$cdiff_opt' with input '$input' ... "
33
-    if $CDIFF $cdiff_opt $input | diff -ubq $expected_out - >& /dev/null; then
33
+    if $CDIFF $cdiff_opt $input 2>/dev/null \
34
+            | diff -ubq $expected_out - >& /dev/null; then
34 35
         pass
35 36
         return 0
36 37
     else

+ 53
- 0
tests/svn-log/in.diff View File

@@ -0,0 +1,53 @@
1
+------------------------------------------------------------------------
2
+r1235 | ymattw | 2011-09-01 17:00:02 +0800 (Thu, 01 Sep 2011) | 3 lines
3
+
4
+Interpreter fix
5
+
6
+
7
+Index: src/share/example.sh
8
+===================================================================
9
+--- src/share/example.sh	(revision 1234)
10
++++ src/share/example.sh	(revision 1235)
11
+@@ -1,3 +1,3 @@
12
+-#!/bin/sh
13
++#!/bin/bash
14
+ #
15
+ # Test program, also try run me as yroot (sudo ./example.sh)
16
+
17
+------------------------------------------------------------------------
18
+r1234 | ymattw | 2011-09-01 16:00:02 +0800 (Thu, 01 Sep 2011) | 3 lines
19
+
20
+Implement common tool to run command as headless account
21
+
22
+
23
+Index: ChangeLog
24
+===================================================================
25
+--- ChangeLog	(revision 1233)
26
++++ ChangeLog	(revision 1234)
27
+@@ -1,3 +1,6 @@
28
++Version 0.0.4
29
++    * Add prototype of perl module
30
++
31
+ Version 0.0.3
32
+     * Implement '-d' option
33
+     * Add configfile to set global debug flag
34
+Index: src/share/example.sh
35
+===================================================================
36
+--- src/share/example.sh	(revision 0)
37
++++ src/share/example.sh	(revision 1234)
38
+@@ -0,0 +1,4 @@
39
++#!/bin/sh
40
++#
41
++# Test program, also try run me as yroot (sudo ./example.sh)
42
++#
43
+
44
+Property changes on: src/share/example.sh
45
+___________________________________________________________________
46
+Added: svn:executable
47
+## -0,0 +1 ##
48
++*
49
+\ No newline at end of property
50
+Added: svn:keywords
51
+## -0,0 +1 ##
52
++Id
53
+\ No newline at end of property

+ 52
- 0
tests/svn-log/out.normal View File

@@ -0,0 +1,52 @@
1
+------------------------------------------------------------------------
2
+r1235 | ymattw | 2011-09-01 17:00:02 +0800 (Thu, 01 Sep 2011) | 3 lines
3
+
4
+Interpreter fix
5
+
6
+
7
+Index: src/share/example.sh
8
+===================================================================
9
+--- src/share/example.sh	(revision 1234)
10
++++ src/share/example.sh	(revision 1235)
11
+@@ -1,3 +1,3 @@
12
+-#!/bin/sh
13
++#!/bin/bash
14
+ #
15
+ # Test program, also try run me as yroot (sudo ./example.sh)
16
+
17
+------------------------------------------------------------------------
18
+r1234 | ymattw | 2011-09-01 16:00:02 +0800 (Thu, 01 Sep 2011) | 3 lines
19
+
20
+Implement common tool to run command as headless account
21
+
22
+
23
+Index: ChangeLog
24
+===================================================================
25
+--- ChangeLog	(revision 1233)
26
++++ ChangeLog	(revision 1234)
27
+@@ -1,3 +1,6 @@
28
++Version 0.0.4
29
++    * Add prototype of perl module
30
++
31
+ Version 0.0.3
32
+     * Implement '-d' option
33
+     * Add configfile to set global debug flag
34
+Index: src/share/example.sh
35
+===================================================================
36
+--- src/share/example.sh	(revision 0)
37
++++ src/share/example.sh	(revision 1234)
38
+@@ -0,0 +1,4 @@
39
++#!/bin/sh
40
++#
41
++# Test program, also try run me as yroot (sudo ./example.sh)
42
++#
43
+
44
+Property changes on: src/share/example.sh
45
+___________________________________________________________________
46
+Added: svn:executable
47
+## -0,0 +1 ##
48
++*
49
+Added: svn:keywords
50
+## -0,0 +1 ##
51
++Id
52
+

+ 50
- 0
tests/svn-log/out.side-by-side View File

@@ -0,0 +1,50 @@
1
+------------------------------------------------------------------------
2
+r1235 | ymattw | 2011-09-01 17:00:02 +0800 (Thu, 01 Sep 2011) | 3 lines
3
+
4
+Interpreter fix
5
+
6
+
7
+Index: src/share/example.sh
8
+===================================================================
9
+--- src/share/example.sh	(revision 1234)
10
++++ src/share/example.sh	(revision 1235)
11
+@@ -1,3 +1,3 @@
12
+1 #!/bin/sh                                                                        1 #!/bin/bash
13
+2 #                                                                                2 #
14
+3 # Test program, also try run me as yroot (sudo ./example.sh)                     3 # Test program, also try run me as yroot (sudo ./example.sh)
15
+
16
+------------------------------------------------------------------------
17
+r1234 | ymattw | 2011-09-01 16:00:02 +0800 (Thu, 01 Sep 2011) | 3 lines
18
+
19
+Implement common tool to run command as headless account
20
+
21
+
22
+Index: ChangeLog
23
+===================================================================
24
+--- ChangeLog	(revision 1233)
25
++++ ChangeLog	(revision 1234)
26
+@@ -1,3 +1,6 @@
27
+                                                                                   1 Version 0.0.4
28
+                                                                                   2     * Add prototype of perl module
29
+                                                                                   3 
30
+1 Version 0.0.3                                                                    4 Version 0.0.3
31
+2     * Implement '-d' option                                                      5     * Implement '-d' option
32
+3     * Add configfile to set global debug flag                                    6     * Add configfile to set global debug flag
33
+Index: src/share/example.sh
34
+===================================================================
35
+--- src/share/example.sh	(revision 0)
36
++++ src/share/example.sh	(revision 1234)
37
+@@ -0,0 +1,4 @@
38
+                                                                                     1 #!/bin/sh
39
+                                                                                     2 #
40
+                                                                                     3 # Test program, also try run me as yroot (sudo ./example.sh)
41
+                                                                                     4 #
42
+
43
+Property changes on: src/share/example.sh
44
+___________________________________________________________________
45
+Added: svn:executable
46
+## -0,0 +1 ##
47
+                                                                                     1 *
48
+Added: svn:keywords
49
+## -0,0 +1 ##
50
+                                                                                     1 Id

+ 50
- 0
tests/svn-log/out.w70 View File

@@ -0,0 +1,50 @@
1
+------------------------------------------------------------------------
2
+r1235 | ymattw | 2011-09-01 17:00:02 +0800 (Thu, 01 Sep 2011) | 3 lines
3
+
4
+Interpreter fix
5
+
6
+
7
+Index: src/share/example.sh
8
+===================================================================
9
+--- src/share/example.sh	(revision 1234)
10
++++ src/share/example.sh	(revision 1235)
11
+@@ -1,3 +1,3 @@
12
+1 #!/bin/sh                                                              1 #!/bin/bash
13
+2 #                                                                      2 #
14
+3 # Test program, also try run me as yroot (sudo ./example.sh)           3 # Test program, also try run me as yroot (sudo ./example.sh)
15
+
16
+------------------------------------------------------------------------
17
+r1234 | ymattw | 2011-09-01 16:00:02 +0800 (Thu, 01 Sep 2011) | 3 lines
18
+
19
+Implement common tool to run command as headless account
20
+
21
+
22
+Index: ChangeLog
23
+===================================================================
24
+--- ChangeLog	(revision 1233)
25
++++ ChangeLog	(revision 1234)
26
+@@ -1,3 +1,6 @@
27
+                                                                         1 Version 0.0.4
28
+                                                                         2     * Add prototype of perl module
29
+                                                                         3 
30
+1 Version 0.0.3                                                          4 Version 0.0.3
31
+2     * Implement '-d' option                                            5     * Implement '-d' option
32
+3     * Add configfile to set global debug flag                          6     * Add configfile to set global debug flag
33
+Index: src/share/example.sh
34
+===================================================================
35
+--- src/share/example.sh	(revision 0)
36
++++ src/share/example.sh	(revision 1234)
37
+@@ -0,0 +1,4 @@
38
+                                                                           1 #!/bin/sh
39
+                                                                           2 #
40
+                                                                           3 # Test program, also try run me as yroot (sudo ./example.sh)
41
+                                                                           4 #
42
+
43
+Property changes on: src/share/example.sh
44
+___________________________________________________________________
45
+Added: svn:executable
46
+## -0,0 +1 ##
47
+                                                                           1 *
48
+Added: svn:keywords
49
+## -0,0 +1 ##
50
+                                                                           1 Id

+ 23
- 0
tests/test_cdiff.py View File

@@ -0,0 +1,23 @@
1
+#!/usr/bin/env python
2
+# -*- coding: utf-8 -*-
3
+
4
+"""Unit test for cdiff"""
5
+
6
+import sys
7
+if sys.hexversion < 0x02050000:
8
+    raise SystemExit("*** Requires python >= 2.5.0")
9
+
10
+sys.path.insert(0, '..')
11
+
12
+import unittest
13
+import cdiff
14
+
15
+class TestCdiff(unittest.TestCase):
16
+
17
+    def test_foo(self):
18
+        self.assertTrue(1)
19
+
20
+if __name__ == '__main__':
21
+    unittest.main()
22
+
23
+# vim:set et sts=4 sw=4 tw=80: