/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.9.2 by Aaron Bentley
Get single-parent comparison working
1
from difflib import SequenceMatcher
2
0.9.3 by Aaron Bentley
Get three-parent comparisions under test
3
0.9.1 by Aaron Bentley
Get trivial case passing
4
class MultiParent(object):
5
0.9.2 by Aaron Bentley
Get single-parent comparison working
6
    def __init__(self, hunks=None):
7
        if hunks is not None:
8
            self.hunks = hunks
9
        else:
10
            self.hunks = []
11
12
    def __repr__(self):
13
        return "MultiParent(%r)" % self.hunks
14
15
    def __eq__(self, other):
16
        if self.__class__ is not other.__class__:
17
            return False
18
        return (self.hunks == other.hunks)
0.9.1 by Aaron Bentley
Get trivial case passing
19
20
    @staticmethod
21
    def from_lines(text, parents=()):
0.9.2 by Aaron Bentley
Get single-parent comparison working
22
        def compare(parent):
23
            return SequenceMatcher(None, parent, text).get_matching_blocks()
24
        parent_comparisons = [compare(p) for p in parents]
25
        cur_line = 0
26
        new_text = NewText([])
27
        parent_text = []
28
        block_iter = [iter(i) for i in parent_comparisons]
29
        diff = MultiParent([])
30
        def next_block(p):
31
            try:
32
                return block_iter[p].next()
33
            except StopIteration:
34
                return None
35
        cur_block = [next_block(p) for p, i in enumerate(block_iter)]
36
        while cur_line < len(text):
37
            best_match = None
38
            for p, block in enumerate(cur_block):
39
                if block is None:
40
                    continue
41
                i, j, n = block
42
                while j + n < cur_line:
43
                    block = cur_block[p] = next_block(p)
44
                    if block is None:
45
                        break
46
                    i, j, n = block
47
                if block is None:
48
                    continue
49
                if j > cur_line:
50
                    continue
51
                offset = cur_line - j
52
                i += offset
53
                j = cur_line
54
                n -= offset
55
                if n == 0:
56
                    continue
57
                if best_match is None or n > best_match.num_lines:
58
                    best_match = ParentText(p, i, j, n)
59
            if best_match is None:
60
                new_text.lines.append(text[cur_line])
61
                cur_line += 1
62
            else:
63
                if len(new_text.lines) > 0:
64
                    diff.hunks.append(new_text)
65
                    new_text = NewText([])
66
                diff.hunks.append(best_match)
67
                cur_line += best_match.num_lines
68
        if len(new_text.lines) > 0:
69
            diff.hunks.append(new_text)
0.9.1 by Aaron Bentley
Get trivial case passing
70
        return diff
71
72
    @classmethod
73
    def from_texts(cls, text, parents=()):
74
        return cls.from_lines(text.splitlines(True),
75
                              [p.splitlines(True) for p in parents])
76
0.9.4 by Aaron Bentley
Start supporting serialization
77
    def to_patch(self):
78
        for hunk in self.hunks:
79
            for line in hunk.to_patch():
80
                yield line
81
0.9.9 by Aaron Bentley
Much progress on non-naive text reconstruction
82
    def range_iterator(self):
83
        start = 0
84
        for hunk in self.hunks:
85
            if isinstance(hunk, NewText):
86
                kind = 'new'
87
                end = start + len(hunk.lines)
88
                data = hunk.lines
89
            else:
90
                kind = 'parent'
91
                start = hunk.child_pos
92
                end = start + hunk.num_lines
93
                data = (hunk.parent, hunk.parent_pos, hunk.parent_pos +
94
                        hunk.num_lines)
95
            yield start, end, kind, data
96
            start = end
97
0.9.1 by Aaron Bentley
Get trivial case passing
98
99
class NewText(object):
100
101
    def __init__(self, lines):
102
        self.lines = lines
103
104
    def __eq__(self, other):
105
        if self.__class__ is not other.__class__:
106
            return False
107
        return (other.lines == self.lines)
0.9.2 by Aaron Bentley
Get single-parent comparison working
108
109
    def __repr__(self):
110
        return 'NewText(%r)' % self.lines
111
0.9.4 by Aaron Bentley
Start supporting serialization
112
    def to_patch(self):
113
        yield 'i %d\n' % len(self.lines)
114
        for line in self.lines:
115
            yield line
116
        yield '\n'
117
0.9.2 by Aaron Bentley
Get single-parent comparison working
118
119
class ParentText(object):
120
121
    def __init__(self, parent, parent_pos, child_pos, num_lines):
122
        self.parent = parent
123
        self.parent_pos = parent_pos
124
        self.child_pos = child_pos
125
        self.num_lines = num_lines
126
127
    def __repr__(self):
128
        return 'ParentText(%(parent)r, %(parent_pos)r, %(child_pos)r,'\
129
            ' %(num_lines)r)' % self.__dict__
130
131
    def __eq__(self, other):
132
        if self.__class__ != other.__class__:
133
            return False
134
        return (self.__dict__ == other.__dict__)
0.9.4 by Aaron Bentley
Start supporting serialization
135
136
    def to_patch(self):
137
        yield 'c %(parent)d %(parent_pos)d %(child_pos)d %(num_lines)d\n'\
138
            % self.__dict__
0.9.8 by Aaron Bentley
get add_version working
139
140
141
class MultiVersionedFile(object):
142
143
    def __init__(self):
144
        self._diffs = {}
145
        self._lines = {}
146
        self._parents = {}
147
148
    def add_version(self, lines, version_id, parent_ids):
149
        parent_lines = [self._lines[p] for p in parent_ids]
150
        diff = MultiParent.from_lines(lines, parent_lines)
151
        self.add_diff(diff, version_id, parent_ids)
152
        self._lines[version_id] = lines
153
154
    def add_diff(self, diff, version_id, parent_ids):
155
        self._diffs[version_id] = diff
156
        self._parents[version_id] = parent_ids
157
158
    def clear_cache(self):
159
        self._lines.clear()
0.9.9 by Aaron Bentley
Much progress on non-naive text reconstruction
160
161
    def get_line_list(self, version_ids):
162
        return [self.cache_version(v) for v in version_ids]
163
164
    def cache_version(self, version_id):
165
        try:
166
            return self._lines[version_id]
167
        except KeyError:
168
            pass
169
        diff = self._diffs[version_id]
170
        lines = []
171
        reconstructor = _Reconstructor(self._diffs, self._lines, self._parents)
172
        for hunk in diff.hunks:
173
            if isinstance(hunk, NewText):
174
                lines.extend(hunk.lines)
175
            else:
176
                reconstructor.reconstruct(lines, hunk, version_id)
177
        self._lines[version_id] = lines
178
        return lines
179
180
181
class _Reconstructor(object):
182
183
    def __init__(self, diffs, lines, parents):
184
185
        self.diffs = diffs
186
        self.lines = lines
187
        self.parents = parents
188
        self.cursor = {}
189
190
    def reconstruct(self, lines, parent_text, version_id):
191
        parent_id = self.parents[version_id][parent_text.parent]
192
        end = parent_text.parent_pos + parent_text.num_lines
193
        return self._reconstruct(lines, parent_id, parent_text.parent_pos, end)
194
195
    def _reconstruct(self, lines, req_version_id, req_start, req_end):
196
        pending_reqs = [(req_version_id, req_start, req_end)]
197
        while len(pending_reqs) > 0:
198
            req_version_id, req_start, req_end = pending_reqs.pop()
199
            try:
200
                start, end, kind, data, iterator = self.cursor[req_version_id]
201
            except KeyError:
202
                iterator = self.diffs[req_version_id].range_iterator()
203
                start, end, kind, data = iterator.next()
204
            while end < req_start:
205
                start, end, kind, data = iterator.next()
206
            self.cursor[req_version_id] = start, end, kind, data, iterator
207
            if kind == 'new':
208
                lines.extend(data[req_start - start: (req_end - start)])
209
            else:
210
                parent, parent_start, parent_end = data
211
                version_id = self.parents[req_version_id][parent]
212
                sub_start = parent_start + req_start - start
213
                sub_end = parent_end + req_end - end
214
                pending_reqs.append((version_id, sub_start, sub_end))