1
# Copyright (C) 2005 by Canonical Ltd
4
# Johan Rydberg <jrydberg@gnu.org>
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.
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.
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
20
# Remaing to do is to figure out if get_graph should return a simple
21
# map, or a graph object of some kind.
24
"""Versioned text file storage api."""
27
class VersionedFile(object):
28
"""Versioned text file storage.
30
A versioned file manages versions of line-based text files,
31
keeping track of the originating version for each line.
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.
38
Texts are identified by a version-id string.
42
"""Return a unsorted list of versions."""
43
raise NotImplementedError(self.versions)
45
def has_version(self, version_id):
46
"""Returns whether version is present."""
47
raise NotImplementedError(self.has_version)
49
def add_lines(self, version_id, parents, lines):
50
"""Add a single text on top of the versioned file.
52
Must raise RevisionAlreadyPresent if the new version is
53
already present in file history.
55
Must raise RevisionNotPresent if any of the given parents are
56
not present in file history."""
57
raise NotImplementedError(self.add_lines)
59
def clear_cache(self):
60
"""Remove any data cached in the versioned file object."""
62
def clone_text(self, new_version_id, old_version_id, parents):
63
"""Add an identical text to old_version_id as new_version_id.
65
Must raise RevisionNotPresent if the old version or any of the
66
parents are not present in file history.
68
Must raise RevisionAlreadyPresent if the new version is
69
already present in file history."""
70
raise NotImplementedError(self.clone_text)
72
def get_text(self, version_id):
73
"""Return version contents as a text string.
75
Raises RevisionNotPresent if version is not present in
78
return ''.join(self.get_lines(version_id))
81
def get_lines(self, version_id):
82
"""Return version contents as a sequence of lines.
84
Raises RevisionNotPresent if version is not present in
87
raise NotImplementedError(self.get_lines)
89
def get_ancestry(self, version_ids):
90
"""Return a list of all ancestors of given version(s). This
91
will not include the null revision.
93
Must raise RevisionNotPresent if any of the given versions are
94
not present in file history."""
95
if isinstance(version_ids, basestring):
96
version_ids = [version_ids]
97
raise NotImplementedError(self.get_ancestry)
99
def get_graph(self, version_id):
102
Must raise RevisionNotPresent if version is not present in
104
raise NotImplementedError(self.get_graph)
106
def get_parents(self, version_id):
107
"""Return version names for parents of a version.
109
Must raise RevisionNotPresent if version is not present in
112
raise NotImplementedError(self.get_parents)
114
def annotate_iter(self, version_id):
115
"""Yield list of (version-id, line) pairs for the specified
118
Must raise RevisionNotPresent if any of the given versions are
119
not present in file history.
121
raise NotImplementedError(self.annotate_iter)
123
def annotate(self, version_id):
124
return list(self.annotate_iter(version_id))
126
def join(self, other, pb=None, msg=None, version_ids=None):
127
"""Integrate versions from other into this versioned file.
129
If version_ids is None all versions from other should be
130
incorporated into this versioned file.
132
Must raise RevisionNotPresent if any of the specified versions
133
are not present in the other files history."""
134
raise NotImplementedError(self.join)
136
def walk(self, version_ids=None):
137
"""Walk the versioned file as a weave-like structure, for
138
versions relative to version_ids. Yields sequence of (lineno,
139
insert, deletes, text) for each relevant line.
141
Must raise RevisionNotPresent if any of the specified versions
142
are not present in the file history.
144
:param version_ids: the version_ids to walk with respect to. If not
145
supplied the entire weave-like structure is walked.
147
raise NotImplementedError(self.walk)
149
def plan_merge(self, ver_a, ver_b):
150
"""Return pseudo-annotation indicating how the two versions merge.
152
This is computed between versions a and b and their common
155
Weave lines present in none of them are skipped entirely.
157
inc_a = set(self.inclusions([ver_a]))
158
inc_b = set(self.inclusions([ver_b]))
159
inc_c = inc_a & inc_b
161
for lineno, insert, deleteset, line in self.walk():
162
if deleteset & inc_c:
163
# killed in parent; can't be in either a or b
164
# not relevant to our work
165
yield 'killed-base', line
166
elif insert in inc_c:
167
# was inserted in base
168
killed_a = bool(deleteset & inc_a)
169
killed_b = bool(deleteset & inc_b)
170
if killed_a and killed_b:
171
yield 'killed-both', line
173
yield 'killed-a', line
175
yield 'killed-b', line
177
yield 'unchanged', line
178
elif insert in inc_a:
179
if deleteset & inc_a:
180
yield 'ghost-a', line
184
elif insert in inc_b:
185
if deleteset & inc_b:
186
yield 'ghost-b', line
190
# not in either revision
191
yield 'irrelevant', line
193
yield 'unchanged', '' # terminator
195
def weave_merge(self, plan, a_marker='<<<<<<< \n', b_marker='>>>>>>> \n'):
199
# TODO: Return a structured form of the conflicts (e.g. 2-tuples for
200
# conflicted regions), rather than just inserting the markers.
202
# TODO: Show some version information (e.g. author, date) on
203
# conflicted regions.
204
for state, line in plan:
205
if state == 'unchanged' or state == 'killed-both':
206
# resync and flush queued conflicts changes if any
207
if not lines_a and not lines_b:
209
elif ch_a and not ch_b:
211
for l in lines_a: yield l
212
elif ch_b and not ch_a:
213
for l in lines_b: yield l
214
elif lines_a == lines_b:
215
for l in lines_a: yield l
218
for l in lines_a: yield l
220
for l in lines_b: yield l
227
if state == 'unchanged':
230
elif state == 'killed-a':
233
elif state == 'killed-b':
236
elif state == 'new-a':
239
elif state == 'new-b':
243
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',