78
77
"""Returns whether version is present."""
79
78
raise NotImplementedError(self.has_version)
81
def add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
82
"""Add a text to the versioned file via a pregenerated delta.
84
:param version_id: The version id being added.
85
:param parents: The parents of the version_id.
86
:param delta_parent: The parent this delta was created against.
87
:param sha1: The sha1 of the full text.
88
:param delta: The delta instructions. See get_delta for details.
90
self._check_write_ok()
91
if self.has_version(version_id):
92
raise errors.RevisionAlreadyPresent(version_id, self)
93
return self._add_delta(version_id, parents, delta_parent, sha1, noeol, delta)
95
def _add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
96
"""Class specific routine to add a delta.
98
This generic version simply applies the delta to the delta_parent and
101
# strip annotation from delta
103
for start, stop, delta_len, delta_lines in delta:
104
new_delta.append((start, stop, delta_len, [text for origin, text in delta_lines]))
105
if delta_parent is not None:
106
parent_full = self.get_lines(delta_parent)
109
new_full = self._apply_delta(parent_full, new_delta)
110
# its impossible to have noeol on an empty file
111
if noeol and new_full[-1][-1] == '\n':
112
new_full[-1] = new_full[-1][:-1]
113
self.add_lines(version_id, parents, new_full)
115
def add_lines(self, version_id, parents, lines, parent_texts=None):
80
def add_lines(self, version_id, parents, lines, parent_texts=None,
81
left_matching_blocks=None, nostore_sha=None, random_id=False,
116
83
"""Add a single text on top of the versioned file.
118
85
Must raise RevisionAlreadyPresent if the new version is
121
88
Must raise RevisionNotPresent if any of the given parents are
122
89
not present in file history.
91
:param lines: A list of lines. Each line must be a bytestring. And all
92
of them except the last must be terminated with \n and contain no
93
other \n's. The last line may either contain no \n's or a single
94
terminated \n. If the lines list does meet this constraint the add
95
routine may error or may succeed - but you will be unable to read
96
the data back accurately. (Checking the lines have been split
97
correctly is expensive and extremely unlikely to catch bugs so it
98
is not done at runtime unless check_content is True.)
123
99
:param parent_texts: An optional dictionary containing the opaque
124
representations of some or all of the parents of
125
version_id to allow delta optimisations.
126
VERY IMPORTANT: the texts must be those returned
127
by add_lines or data corruption can be caused.
128
:return: An opaque representation of the inserted version which can be
129
provided back to future add_lines calls in the parent_texts
100
representations of some or all of the parents of version_id to
101
allow delta optimisations. VERY IMPORTANT: the texts must be those
102
returned by add_lines or data corruption can be caused.
103
:param left_matching_blocks: a hint about which areas are common
104
between the text and its left-hand-parent. The format is
105
the SequenceMatcher.get_matching_blocks format.
106
:param nostore_sha: Raise ExistingContent and do not add the lines to
107
the versioned file if the digest of the lines matches this.
108
:param random_id: If True a random id has been selected rather than
109
an id determined by some deterministic process such as a converter
110
from a foreign VCS. When True the backend may choose not to check
111
for uniqueness of the resulting key within the versioned file, so
112
this should only be done when the result is expected to be unique
114
:param check_content: If True, the lines supplied are verified to be
115
bytestrings that are correctly formed lines.
116
:return: The text sha1, the number of bytes in the text, and an opaque
117
representation of the inserted version which can be provided
118
back to future add_lines calls in the parent_texts dictionary.
132
120
self._check_write_ok()
133
return self._add_lines(version_id, parents, lines, parent_texts)
121
return self._add_lines(version_id, parents, lines, parent_texts,
122
left_matching_blocks, nostore_sha, random_id, check_content)
135
def _add_lines(self, version_id, parents, lines, parent_texts):
124
def _add_lines(self, version_id, parents, lines, parent_texts,
125
left_matching_blocks, nostore_sha, random_id, check_content):
136
126
"""Helper to do the class specific add_lines."""
137
127
raise NotImplementedError(self.add_lines)
139
129
def add_lines_with_ghosts(self, version_id, parents, lines,
130
parent_texts=None, nostore_sha=None, random_id=False,
141
132
"""Add lines to the versioned file, allowing ghosts to be present.
143
This takes the same parameters as add_lines.
134
This takes the same parameters as add_lines and returns the same.
145
136
self._check_write_ok()
146
137
return self._add_lines_with_ghosts(version_id, parents, lines,
138
parent_texts, nostore_sha, random_id, check_content)
149
def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts):
140
def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
141
nostore_sha, random_id, check_content):
150
142
"""Helper to do class specific add_lines_with_ghosts."""
151
143
raise NotImplementedError(self.add_lines_with_ghosts)
212
204
raise NotImplementedError(self.create_empty)
214
def fix_parents(self, version, new_parents):
215
"""Fix the parents list for version.
217
This is done by appending a new version to the index
218
with identical data except for the parents list.
219
the parents list must be a superset of the current
222
self._check_write_ok()
223
return self._fix_parents(version, new_parents)
225
def _fix_parents(self, version, new_parents):
226
"""Helper for fix_parents."""
227
raise NotImplementedError(self.fix_parents)
229
def get_delta(self, version):
230
"""Get a delta for constructing version from some other version.
232
:return: (delta_parent, sha1, noeol, delta)
233
Where delta_parent is a version id or None to indicate no parent.
235
raise NotImplementedError(self.get_delta)
237
def get_deltas(self, versions):
238
"""Get multiple deltas at once for constructing versions.
240
:return: dict(version_id:(delta_parent, sha1, noeol, delta))
241
Where delta_parent is a version id or None to indicate no parent, and
242
version_id is the version_id created by that delta.
245
for version in versions:
246
result[version] = self.get_delta(version)
206
def get_format_signature(self):
207
"""Get a text description of the data encoding in this file.
211
raise NotImplementedError(self.get_format_signature)
213
def make_mpdiffs(self, version_ids):
214
"""Create multiparent diffs for specified versions."""
215
knit_versions = set()
216
for version_id in version_ids:
217
knit_versions.add(version_id)
218
knit_versions.update(self.get_parents(version_id))
219
lines = dict(zip(knit_versions,
220
self._get_lf_split_line_list(knit_versions)))
222
for version_id in version_ids:
223
target = lines[version_id]
224
parents = [lines[p] for p in self.get_parents(version_id)]
226
left_parent_blocks = self._extract_blocks(version_id,
229
left_parent_blocks = None
230
diffs.append(multiparent.MultiParent.from_lines(target, parents,
234
def _extract_blocks(self, version_id, source, target):
237
def add_mpdiffs(self, records):
238
"""Add mpdiffs to this VersionedFile.
240
Records should be iterables of version, parents, expected_sha1,
241
mpdiff. mpdiff should be a MultiParent instance.
243
# Does this need to call self._check_write_ok()? (IanC 20070919)
245
mpvf = multiparent.MultiMemoryVersionedFile()
247
for version, parent_ids, expected_sha1, mpdiff in records:
248
versions.append(version)
249
mpvf.add_diff(mpdiff, version, parent_ids)
250
needed_parents = set()
251
for version, parent_ids, expected_sha1, mpdiff in records:
252
needed_parents.update(p for p in parent_ids
253
if not mpvf.has_version(p))
254
for parent_id, lines in zip(needed_parents,
255
self._get_lf_split_line_list(needed_parents)):
256
mpvf.add_version(lines, parent_id, [])
257
for (version, parent_ids, expected_sha1, mpdiff), lines in\
258
zip(records, mpvf.get_line_list(versions)):
259
if len(parent_ids) == 1:
260
left_matching_blocks = list(mpdiff.get_matching_blocks(0,
261
mpvf.get_diff(parent_ids[0]).num_lines()))
263
left_matching_blocks = None
264
_, _, version_text = self.add_lines(version, parent_ids, lines,
265
vf_parents, left_matching_blocks=left_matching_blocks)
266
vf_parents[version] = version_text
267
for (version, parent_ids, expected_sha1, mpdiff), sha1 in\
268
zip(records, self.get_sha1s(versions)):
269
if expected_sha1 != sha1:
270
raise errors.VersionedFileInvalidChecksum(version)
249
272
def get_sha1(self, version_id):
250
273
"""Get the stored sha1 sum for the given revision.
252
:param name: The name of the version to lookup
275
:param version_id: The name of the version to lookup
254
277
raise NotImplementedError(self.get_sha1)
279
def get_sha1s(self, version_ids):
280
"""Get the stored sha1 sums for the given revisions.
282
:param version_ids: The names of the versions to lookup
283
:return: a list of sha1s in order according to the version_ids
285
raise NotImplementedError(self.get_sha1s)
256
287
def get_suffixes(self):
257
288
"""Return the file suffixes associated with this versioned file."""
258
289
raise NotImplementedError(self.get_suffixes)
412
430
thinks is relevant, but given that such hints are just guesses,
413
431
its better not to have it if we don't need it.
433
If a progress bar is supplied, it may be used to indicate progress.
434
The caller is responsible for cleaning up progress bars (because this
415
437
NOTES: Lines are normalised: they will all have \n terminators.
416
438
Lines are returned in arbitrary order.
418
440
raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
442
def iter_parents(self, version_ids):
443
"""Iterate through the parents for many version ids.
445
:param version_ids: An iterable yielding version_ids.
446
:return: An iterator that yields (version_id, parents). Requested
447
version_ids not present in the versioned file are simply skipped.
448
The order is undefined, allowing for different optimisations in
449
the underlying implementation.
451
for version_id in version_ids:
453
yield version_id, tuple(self.get_parents(version_id))
454
except errors.RevisionNotPresent:
420
457
def transaction_finished(self):
421
458
"""The transaction that this file was opened in has finished.
426
463
self.finished = True
428
@deprecated_method(zero_eight)
429
def walk(self, version_ids=None):
430
"""Walk the versioned file as a weave-like structure, for
431
versions relative to version_ids. Yields sequence of (lineno,
432
insert, deletes, text) for each relevant line.
434
Must raise RevisionNotPresent if any of the specified versions
435
are not present in the file history.
437
:param version_ids: the version_ids to walk with respect to. If not
438
supplied the entire weave-like structure is walked.
440
walk is deprecated in favour of iter_lines_added_or_present_in_versions
442
raise NotImplementedError(self.walk)
444
@deprecated_method(zero_eight)
445
def iter_names(self):
446
"""Walk the names list."""
447
return iter(self.versions())
449
465
def plan_merge(self, ver_a, ver_b):
450
466
"""Return pseudo-annotation indicating how the two versions merge.
602
618
# TODO: remove parent texts when they are not relevant any more for
603
619
# memory pressure reduction. RBC 20060313
604
620
# pb.update('Converting versioned data', 0, len(order))
605
# deltas = self.source.get_deltas(order)
606
622
for index, version in enumerate(order):
607
pb.update('Converting versioned data', index, len(order))
608
parent_text = target.add_lines(version,
623
pb.update('Converting versioned data', index, total)
624
_, _, parent_text = target.add_lines(version,
609
625
self.source.get_parents(version),
610
626
self.source.get_lines(version),
611
627
parent_texts=parent_texts)
612
628
parent_texts[version] = parent_text
613
#delta_parent, sha1, noeol, delta = deltas[version]
614
#target.add_delta(version,
615
# self.source.get_parents(version),
620
#target.get_lines(version)
622
630
# this should hit the native code path for target
623
631
if target is not self.target:
656
666
new_version_ids.add(version)
657
667
return new_version_ids
660
class InterVersionedFileTestProviderAdapter(object):
661
"""A tool to generate a suite testing multiple inter versioned-file classes.
663
This is done by copying the test once for each InterVersionedFile provider
664
and injecting the transport_server, transport_readonly_server,
665
versionedfile_factory and versionedfile_factory_to classes into each copy.
666
Each copy is also given a new id() to make it easy to identify.
669
def __init__(self, transport_server, transport_readonly_server, formats):
670
self._transport_server = transport_server
671
self._transport_readonly_server = transport_readonly_server
672
self._formats = formats
674
def adapt(self, test):
676
for (interversionedfile_class,
677
versionedfile_factory,
678
versionedfile_factory_to) in self._formats:
679
new_test = deepcopy(test)
680
new_test.transport_server = self._transport_server
681
new_test.transport_readonly_server = self._transport_readonly_server
682
new_test.interversionedfile_class = interversionedfile_class
683
new_test.versionedfile_factory = versionedfile_factory
684
new_test.versionedfile_factory_to = versionedfile_factory_to
685
def make_new_test_id():
686
new_id = "%s(%s)" % (new_test.id(), interversionedfile_class.__name__)
687
return lambda: new_id
688
new_test.id = make_new_test_id()
689
result.addTest(new_test)
693
def default_test_list():
694
"""Generate the default list of interversionedfile permutations to test."""
695
from bzrlib.weave import WeaveFile
696
from bzrlib.knit import KnitVersionedFile
698
# test the fallback InterVersionedFile from annotated knits to weave
699
result.append((InterVersionedFile,
702
for optimiser in InterVersionedFile._optimisers:
703
result.append((optimiser,
704
optimiser._matching_file_from_factory,
705
optimiser._matching_file_to_factory
707
# if there are specific combinations we want to use, we can add them