~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/mdiff.py

  • Committer: Martin Pool
  • Date: 2005-09-30 05:15:03 UTC
  • mto: (1185.14.2)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: mbp@sourcefrog.net-20050930051503-9c049325215ddd1c
- fix up Branch.open_downlevel for Transport

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
 
26
26
# TODO: maybe work on files not strings?
27
27
 
 
28
# FIXME: doesn't work properly on files without trailing newlines
28
29
 
29
 
import difflib, sys, struct
 
30
import difflib
 
31
import struct
 
32
import sys
 
33
import unittest
30
34
from cStringIO import StringIO
31
35
 
 
36
 
 
37
def linesplit(a):
 
38
    """Split into two lists: content and line positions.
 
39
 
 
40
    This returns (al, ap).
 
41
 
 
42
    al[i] is the string content of line i of the file, including its
 
43
    newline (if any).
 
44
 
 
45
    ap[i] is the byte position in the file where that line starts.
 
46
 
 
47
    ap[-1] is the byte position of the end of the file (i.e. the
 
48
    length of the file.)
 
49
 
 
50
    This transformation allows us to do a line-based diff and then map
 
51
    back to byte positions.
 
52
    """
 
53
 
 
54
    al, ap = [], []
 
55
    last = 0
 
56
 
 
57
    n = a.find("\n") + 1
 
58
    while n > 0:
 
59
        ap.append(last)
 
60
        al.append(a[last:n])
 
61
        last = n
 
62
        n = a.find("\n", n) + 1
 
63
 
 
64
    if last < len(a):
 
65
        al.append(a[last:])
 
66
        ap.append(last)
 
67
 
 
68
    # position at the end
 
69
    ap.append(len(a))
 
70
 
 
71
    return (al, ap)
 
72
 
 
73
 
32
74
def diff(a, b):
33
 
    d = difflib.SequenceMatcher(None, a, b)
 
75
    # TODO: Use different splits, perhaps rsync-like, for binary files?
 
76
    
 
77
    (al, ap) = linesplit(a)
 
78
    (bl, bp) = linesplit(b)
 
79
 
 
80
    d = difflib.SequenceMatcher(None, al, bl)
34
81
    
35
82
    ## sys.stderr.write('  ~ real_quick_ratio: %.4f\n' % d.real_quick_ratio())
36
83
    
38
85
        if o == 'equal': continue
39
86
        # a[m:n] should be replaced by b[s:t]
40
87
        if s == t:
41
 
            yield m, n, ''
 
88
            yield ap[m], ap[n], ''
42
89
        else:
43
 
            yield m, n, b[s:t]
 
90
            yield ap[m], ap[n], ''.join(bl[s:t])
44
91
 
45
92
 
46
93
def tobinary(ops):
94
141
 
95
142
 
96
143
 
 
144
class TestDiffPatch(unittest.TestCase):
 
145
    def doDiffPatch(self, old, new):
 
146
        diff = bdiff(old, new)
 
147
        result = bpatch(old, diff)
 
148
        self.assertEquals(new, result)
 
149
 
 
150
 
 
151
    def testSimpleDiff(self):
 
152
        """Simply add a line at the end"""
 
153
        self.doDiffPatch('a\nb\n', 'a\nb\nc\n')
 
154
        
 
155
 
 
156
        
 
157
    def testTrailingLine(self):
 
158
        """Test diff that adds an unterminated line.
 
159
 
 
160
        (Old versions didn't do this properly.)"""
 
161
        self.doDiffPatch('a\nb\nc\n',
 
162
                         'a\nb\nc\nd')
 
163
 
 
164
 
 
165
if __name__ == '__main__':
 
166
    unittest.main()