/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
1
# Copyright (C) 2005 by Canonical Ltd
2
#
3
# Authors:
4
#   Johan Rydberg <jrydberg@gnu.org>
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
# Remaing to do is to figure out if get_graph should return a simple
21
# map, or a graph object of some kind.
22
23
24
"""Versioned text file storage api."""
25
26
27
class VersionedFile(object):
28
    """Versioned text file storage.
29
    
30
    A versioned file manages versions of line-based text files,
31
    keeping track of the originating version for each line.
32
33
    To clients the "lines" of the file are represented as a list of
34
    strings. These strings will typically have terminal newline
35
    characters, but this is not required.  In particular files commonly
36
    do not have a newline at the end of the file.
37
38
    Texts are identified by a version-id string.
39
    """
40
41
    def versions(self):
42
        """Return a unsorted list of versions."""
43
        raise NotImplementedError(self.versions)
44
45
    def has_version(self, version_id):
46
        """Returns whether version is present."""
47
        raise NotImplementedError(self.has_version)
48
49
    def add_lines(self, version_id, parents, lines):
50
        """Add a single text on top of the versioned file.
51
52
        Must raise RevisionAlreadyPresent if the new version is
53
        already present in file history.
54
55
        Must raise RevisionNotPresent if any of the given parents are
56
        not present in file history."""
57
        raise NotImplementedError(self.add_text)
58
59
    def clone_text(self, new_version_id, old_version_id, parents,
60
                   transaction):
61
        """Add an identical text to old_version_id as new_version_id.
62
63
        Must raise RevisionNotPresent if the old version or any of the
64
        parents are not present in file history.
65
66
        Must raise RevisionAlreadyPresent if the new version is
67
        already present in file history."""
68
        raise NotImplementedError(self.clone_text)
69
70
    def get_text(self, version_id):
71
        """Return version contents as a text string.
72
73
        Raises RevisionNotPresent if version is not present in
74
        file history.
75
        """
76
        return ''.join(self.get_lines(version_id))
77
    get_string = get_text
78
79
    def get_lines(self, version_id):
80
        """Return version contents as a sequence of lines.
81
82
        Raises RevisionNotPresent if version is not present in
83
        file history.
84
        """
85
        raise NotImplementedError(self.get_lines)
86
87
    def get_ancestry(self, version_ids):
88
        """Return a list of all ancestors of given version(s). This
89
        will not include the null revision.
90
91
        Must raise RevisionNotPresent if any of the given versions are
92
        not present in file history."""
93
        if isinstance(version_ids, basestring):
94
            version_ids = [version_ids]
95
        raise NotImplementedError(self.get_ancestry)
96
        
97
    def get_graph(self, version_id):
98
        """Return a graph.
99
100
        Must raise RevisionNotPresent if version is not present in
101
        file history."""
102
        raise NotImplementedError(self.get_graph)
103
104
    def get_parents(self, version_id):
105
        """Return version names for parents of a version.
106
107
        Must raise RevisionNotPresent if version is not present in
108
        file history.
109
        """
110
        raise NotImplementedError(self.get_parents)
111
112
    def annotate_iter(self, version_id):
113
        """Yield list of (version-id, line) pairs for the specified
114
        version.
115
116
        Must raise RevisionNotPresent if any of the given versions are
117
        not present in file history.
118
        """
119
        raise NotImplementedError(self.annotate_iter)
120
121
    def annotate(self, version_id):
122
        return list(self.annotate_iter(version_id))
123
124
    def join(self, other, version_ids, transaction, pb=None):
125
        """Integrate versions from other into this versioned file.
126
127
        If version_ids is None all versions from other should be
128
        incorporated into this versioned file.
129
130
        Must raise RevisionNotPresent if any of the specified versions
131
        are not present in the other files history."""
132
        raise NotImplementedError(self.join)
133
134
    def walk(self, version_ids=None):
135
        """Walk the versioned file as a weave-like structure, for
136
        versions relative to version_ids.  Yields sequence of (lineno,
137
        insert, deletes, text) for each relevant line.
138
139
        Must raise RevisionNotPresent if any of the specified versions
140
        are not present in the file history.
141
142
        :param version_ids: the version_ids to walk with respect to. If not
143
                            supplied the entire weave-like structure is walked.
144
        """
145
        raise NotImplementedError(self.walk)
146
147
    def plan_merge(self, ver_a, ver_b):
148
        """Return pseudo-annotation indicating how the two versions merge.
149
150
        This is computed between versions a and b and their common
151
        base.
152
153
        Weave lines present in none of them are skipped entirely.
154
        """
155
        inc_a = set(self.inclusions([ver_a]))
156
        inc_b = set(self.inclusions([ver_b]))
157
        inc_c = inc_a & inc_b
158
159
        for lineno, insert, deleteset, line in self.walk():
160
            if deleteset & inc_c:
161
                # killed in parent; can't be in either a or b
162
                # not relevant to our work
163
                yield 'killed-base', line
164
            elif insert in inc_c:
165
                # was inserted in base
166
                killed_a = bool(deleteset & inc_a)
167
                killed_b = bool(deleteset & inc_b)
168
                if killed_a and killed_b:
169
                    yield 'killed-both', line
170
                elif killed_a:
171
                    yield 'killed-a', line
172
                elif killed_b:
173
                    yield 'killed-b', line
174
                else:
175
                    yield 'unchanged', line
176
            elif insert in inc_a:
177
                if deleteset & inc_a:
178
                    yield 'ghost-a', line
179
                else:
180
                    # new in A; not in B
181
                    yield 'new-a', line
182
            elif insert in inc_b:
183
                if deleteset & inc_b:
184
                    yield 'ghost-b', line
185
                else:
186
                    yield 'new-b', line
187
            else:
188
                # not in either revision
189
                yield 'irrelevant', line
190
191
        yield 'unchanged', ''           # terminator
192
1563.2.2 by Robert Collins
merge from bzr.dev
193
    def weave_merge(self, plan, a_marker='<<<<<<< \n', b_marker='>>>>>>> \n'):
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
194
        lines_a = []
195
        lines_b = []
196
        ch_a = ch_b = False
197
        # TODO: Return a structured form of the conflicts (e.g. 2-tuples for
198
        # conflicted regions), rather than just inserting the markers.
199
        # 
200
        # TODO: Show some version information (e.g. author, date) on 
201
        # conflicted regions.
202
        for state, line in plan:
203
            if state == 'unchanged' or state == 'killed-both':
204
                # resync and flush queued conflicts changes if any
205
                if not lines_a and not lines_b:
206
                    pass
207
                elif ch_a and not ch_b:
208
                    # one-sided change:                    
209
                    for l in lines_a: yield l
210
                elif ch_b and not ch_a:
211
                    for l in lines_b: yield l
212
                elif lines_a == lines_b:
213
                    for l in lines_a: yield l
214
                else:
1563.2.2 by Robert Collins
merge from bzr.dev
215
                    yield a_marker
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
216
                    for l in lines_a: yield l
217
                    yield '=======\n'
218
                    for l in lines_b: yield l
1563.2.2 by Robert Collins
merge from bzr.dev
219
                    yield b_marker
1563.2.1 by Robert Collins
Merge in a variation of the versionedfile api from versioned-file.
220
221
                del lines_a[:]
222
                del lines_b[:]
223
                ch_a = ch_b = False
224
                
225
            if state == 'unchanged':
226
                if line:
227
                    yield line
228
            elif state == 'killed-a':
229
                ch_a = True
230
                lines_b.append(line)
231
            elif state == 'killed-b':
232
                ch_b = True
233
                lines_a.append(line)
234
            elif state == 'new-a':
235
                ch_a = True
236
                lines_a.append(line)
237
            elif state == 'new-b':
238
                ch_b = True
239
                lines_b.append(line)
240
            else:
241
                assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
242
                                 'killed-both'), \
243
                       state
244
245
246
def plan_merge(file, version_a, version_b):
247
    """Return pseudo-annotation indicating how the two versions merge.
248
    
249
    This is computed between versions a and b and their common
250
    base.
251
252
    Weave lines present in none of them are skipped entirely.
253
    """
254
    inc_a = set(file.get_ancestry([version_a]))
255
    inc_b = set(file.get_ancestry([version_b]))
256
    inc_c = inc_a & inc_b
257
258
    for lineno, insert, deleteset, line in file.walk([version_a, version_b]):
259
        if deleteset & inc_c:
260
            # killed in parent; can't be in either a or b
261
            # not relevant to our work
262
            yield 'killed-base', line
263
        elif insert in inc_c:
264
            # was inserted in base
265
            killed_a = bool(deleteset & inc_a)
266
            killed_b = bool(deleteset & inc_b)
267
            if killed_a and killed_b:
268
                yield 'killed-both', line
269
            elif killed_a:
270
                yield 'killed-a', line
271
            elif killed_b:
272
                yield 'killed-b', line
273
            else:
274
                yield 'unchanged', line
275
        elif insert in inc_a:
276
            if deleteset & inc_a:
277
                yield 'ghost-a', line
278
            else:
279
                # new in A; not in B
280
                yield 'new-a', line
281
        elif insert in inc_b:
282
            if deleteset & inc_b:
283
                yield 'ghost-b', line
284
            else:
285
                yield 'new-b', line
286
        else:
287
            # not in either revision
288
            yield 'irrelevant', line
289
290
    yield 'unchanged', ''           # terminator
291
292
293
def weave_merge(plan):
294
    """Yield merged sequence of lines based on merge plan."""
295
296
    lines_a = []
297
    lines_b = []
298
    ch_a = ch_b = False
299
300
    for state, line in plan:
301
        if state == 'unchanged' or state == 'killed-both':
302
            # resync and flush queued conflicts changes if any
303
            if not lines_a and not lines_b:
304
                pass
305
            elif ch_a and not ch_b:
306
                # one-sided change:                    
307
                for l in lines_a: yield l
308
            elif ch_b and not ch_a:
309
                for l in lines_b: yield l
310
            elif lines_a == lines_b:
311
                for l in lines_a: yield l
312
            else:
313
                yield '<<<<<<<\n'
314
                for l in lines_a: yield l
315
                yield '=======\n'
316
                for l in lines_b: yield l
317
                yield '>>>>>>>\n'
318
319
            del lines_a[:]
320
            del lines_b[:]
321
            ch_a = ch_b = False
322
                
323
        if state == 'unchanged':
324
            if line:
325
                yield line
326
        elif state == 'killed-a':
327
            ch_a = True
328
            lines_b.append(line)
329
        elif state == 'killed-b':
330
            ch_b = True
331
            lines_a.append(line)
332
        elif state == 'new-a':
333
            ch_a = True
334
            lines_a.append(line)
335
        elif state == 'new-b':
336
            ch_b = True
337
            lines_b.append(line)
338
        else:
339
            assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
340
                             'killed-both'), state