1
# Copyright (C) 2005, 2006 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
"""Versioned text file storage api."""
22
from bzrlib.lazy_import import lazy_import
23
lazy_import(globals(), """
33
from bzrlib.graph import Graph
34
from bzrlib.transport.memory import MemoryTransport
37
from cStringIO import StringIO
39
from bzrlib.inter import InterObject
40
from bzrlib.symbol_versioning import *
41
from bzrlib.textmerge import TextMerge
44
class ContentFactory(object):
45
"""Abstract interface for insertion and retrieval from a VersionedFile.
47
:ivar sha1: None, or the sha1 of the content fulltext.
48
:ivar storage_kind: The native storage kind of this factory. One of
49
'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
50
'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
51
'knit-annotated-delta-gz', 'knit-ft-gz', 'knit-delta-gz'.
52
:ivar key: The key of this content. Each key is a tuple with a single
54
:ivar parents: A tuple of parent keys for self.key. If the object has
55
no parent information, None (as opposed to () for an empty list of
60
"""Create a ContentFactory."""
62
self.storage_kind = None
67
class VersionedFile(object):
68
"""Versioned text file storage.
70
A versioned file manages versions of line-based text files,
71
keeping track of the originating version for each line.
73
To clients the "lines" of the file are represented as a list of
74
strings. These strings will typically have terminal newline
75
characters, but this is not required. In particular files commonly
76
do not have a newline at the end of the file.
78
Texts are identified by a version-id string.
82
def check_not_reserved_id(version_id):
83
revision.check_not_reserved_id(version_id)
85
def copy_to(self, name, transport):
86
"""Copy this versioned file to name on transport."""
87
raise NotImplementedError(self.copy_to)
89
def get_record_stream(self, versions, ordering, include_delta_closure):
90
"""Get a stream of records for versions.
92
:param versions: The versions to include. Each version is a tuple
94
:param ordering: Either 'unordered' or 'topological'. A topologically
95
sorted stream has compression parents strictly before their
97
:param include_delta_closure: If True then the closure across any
98
compression parents will be included (in the opaque data).
99
:return: An iterator of ContentFactory objects, each of which is only
100
valid until the iterator is advanced.
102
raise NotImplementedError(self.get_record_stream)
104
@deprecated_method(one_four)
105
def has_ghost(self, version_id):
106
"""Returns whether version is present as a ghost."""
107
raise NotImplementedError(self.has_ghost)
109
def has_version(self, version_id):
110
"""Returns whether version is present."""
111
raise NotImplementedError(self.has_version)
113
def add_lines(self, version_id, parents, lines, parent_texts=None,
114
left_matching_blocks=None, nostore_sha=None, random_id=False,
116
"""Add a single text on top of the versioned file.
118
Must raise RevisionAlreadyPresent if the new version is
119
already present in file history.
121
Must raise RevisionNotPresent if any of the given parents are
122
not present in file history.
124
:param lines: A list of lines. Each line must be a bytestring. And all
125
of them except the last must be terminated with \n and contain no
126
other \n's. The last line may either contain no \n's or a single
127
terminated \n. If the lines list does meet this constraint the add
128
routine may error or may succeed - but you will be unable to read
129
the data back accurately. (Checking the lines have been split
130
correctly is expensive and extremely unlikely to catch bugs so it
131
is not done at runtime unless check_content is True.)
132
:param parent_texts: An optional dictionary containing the opaque
133
representations of some or all of the parents of version_id to
134
allow delta optimisations. VERY IMPORTANT: the texts must be those
135
returned by add_lines or data corruption can be caused.
136
:param left_matching_blocks: a hint about which areas are common
137
between the text and its left-hand-parent. The format is
138
the SequenceMatcher.get_matching_blocks format.
139
:param nostore_sha: Raise ExistingContent and do not add the lines to
140
the versioned file if the digest of the lines matches this.
141
:param random_id: If True a random id has been selected rather than
142
an id determined by some deterministic process such as a converter
143
from a foreign VCS. When True the backend may choose not to check
144
for uniqueness of the resulting key within the versioned file, so
145
this should only be done when the result is expected to be unique
147
:param check_content: If True, the lines supplied are verified to be
148
bytestrings that are correctly formed lines.
149
:return: The text sha1, the number of bytes in the text, and an opaque
150
representation of the inserted version which can be provided
151
back to future add_lines calls in the parent_texts dictionary.
153
self._check_write_ok()
154
return self._add_lines(version_id, parents, lines, parent_texts,
155
left_matching_blocks, nostore_sha, random_id, check_content)
157
def _add_lines(self, version_id, parents, lines, parent_texts,
158
left_matching_blocks, nostore_sha, random_id, check_content):
159
"""Helper to do the class specific add_lines."""
160
raise NotImplementedError(self.add_lines)
162
def add_lines_with_ghosts(self, version_id, parents, lines,
163
parent_texts=None, nostore_sha=None, random_id=False,
164
check_content=True, left_matching_blocks=None):
165
"""Add lines to the versioned file, allowing ghosts to be present.
167
This takes the same parameters as add_lines and returns the same.
169
self._check_write_ok()
170
return self._add_lines_with_ghosts(version_id, parents, lines,
171
parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
173
def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
174
nostore_sha, random_id, check_content, left_matching_blocks):
175
"""Helper to do class specific add_lines_with_ghosts."""
176
raise NotImplementedError(self.add_lines_with_ghosts)
178
def check(self, progress_bar=None):
179
"""Check the versioned file for integrity."""
180
raise NotImplementedError(self.check)
182
def _check_lines_not_unicode(self, lines):
183
"""Check that lines being added to a versioned file are not unicode."""
185
if line.__class__ is not str:
186
raise errors.BzrBadParameterUnicode("lines")
188
def _check_lines_are_lines(self, lines):
189
"""Check that the lines really are full lines without inline EOL."""
191
if '\n' in line[:-1]:
192
raise errors.BzrBadParameterContainsNewline("lines")
194
@deprecated_method(one_four)
195
def enable_cache(self):
196
"""Tell this versioned file that it should cache any data it reads.
198
This is advisory, implementations do not have to support caching.
202
@deprecated_method(one_four)
203
def clear_cache(self):
204
"""Remove any data cached in the versioned file object.
206
This only needs to be supported if caches are supported
210
@deprecated_method(one_four)
211
def clone_text(self, new_version_id, old_version_id, parents):
212
"""Add an identical text to old_version_id as new_version_id.
214
Must raise RevisionNotPresent if the old version or any of the
215
parents are not present in file history.
217
Must raise RevisionAlreadyPresent if the new version is
218
already present in file history."""
219
self._check_write_ok()
220
return self.add_lines(new_version_id, parents,
221
self.get_lines(old_version_id))
223
def get_format_signature(self):
224
"""Get a text description of the data encoding in this file.
228
raise NotImplementedError(self.get_format_signature)
230
def make_mpdiffs(self, version_ids):
231
"""Create multiparent diffs for specified versions."""
232
knit_versions = set()
233
knit_versions.update(version_ids)
234
parent_map = self.get_parent_map(version_ids)
235
for version_id in version_ids:
237
knit_versions.update(parent_map[version_id])
239
raise RevisionNotPresent(version_id, self)
240
# We need to filter out ghosts, because we can't diff against them.
241
knit_versions = set(self.get_parent_map(knit_versions).keys())
242
lines = dict(zip(knit_versions,
243
self._get_lf_split_line_list(knit_versions)))
245
for version_id in version_ids:
246
target = lines[version_id]
248
parents = [lines[p] for p in parent_map[version_id] if p in
251
raise RevisionNotPresent(version_id, self)
253
left_parent_blocks = self._extract_blocks(version_id,
256
left_parent_blocks = None
257
diffs.append(multiparent.MultiParent.from_lines(target, parents,
261
def _extract_blocks(self, version_id, source, target):
264
def add_mpdiffs(self, records):
265
"""Add mpdiffs to this VersionedFile.
267
Records should be iterables of version, parents, expected_sha1,
268
mpdiff. mpdiff should be a MultiParent instance.
270
# Does this need to call self._check_write_ok()? (IanC 20070919)
272
mpvf = multiparent.MultiMemoryVersionedFile()
274
for version, parent_ids, expected_sha1, mpdiff in records:
275
versions.append(version)
276
mpvf.add_diff(mpdiff, version, parent_ids)
277
needed_parents = set()
278
for version, parent_ids, expected_sha1, mpdiff in records:
279
needed_parents.update(p for p in parent_ids
280
if not mpvf.has_version(p))
281
present_parents = set(self.get_parent_map(needed_parents).keys())
282
for parent_id, lines in zip(present_parents,
283
self._get_lf_split_line_list(present_parents)):
284
mpvf.add_version(lines, parent_id, [])
285
for (version, parent_ids, expected_sha1, mpdiff), lines in\
286
zip(records, mpvf.get_line_list(versions)):
287
if len(parent_ids) == 1:
288
left_matching_blocks = list(mpdiff.get_matching_blocks(0,
289
mpvf.get_diff(parent_ids[0]).num_lines()))
291
left_matching_blocks = None
293
_, _, version_text = self.add_lines_with_ghosts(version,
294
parent_ids, lines, vf_parents,
295
left_matching_blocks=left_matching_blocks)
296
except NotImplementedError:
297
# The vf can't handle ghosts, so add lines normally, which will
298
# (reasonably) fail if there are ghosts in the data.
299
_, _, version_text = self.add_lines(version,
300
parent_ids, lines, vf_parents,
301
left_matching_blocks=left_matching_blocks)
302
vf_parents[version] = version_text
303
for (version, parent_ids, expected_sha1, mpdiff), sha1 in\
304
zip(records, self.get_sha1s(versions)):
305
if expected_sha1 != sha1:
306
raise errors.VersionedFileInvalidChecksum(version)
308
@deprecated_method(one_four)
309
def get_sha1(self, version_id):
310
"""Get the stored sha1 sum for the given revision.
312
:param version_id: The name of the version to lookup
314
return self.get_sha1s([version_id])[0]
316
def get_sha1s(self, version_ids):
317
"""Get the stored sha1 sums for the given revisions.
319
:param version_ids: The names of the versions to lookup
320
:return: a list of sha1s in order according to the version_ids
322
raise NotImplementedError(self.get_sha1s)
324
def get_text(self, version_id):
325
"""Return version contents as a text string.
327
Raises RevisionNotPresent if version is not present in
330
return ''.join(self.get_lines(version_id))
331
get_string = get_text
333
def get_texts(self, version_ids):
334
"""Return the texts of listed versions as a list of strings.
336
Raises RevisionNotPresent if version is not present in
339
return [''.join(self.get_lines(v)) for v in version_ids]
341
def get_lines(self, version_id):
342
"""Return version contents as a sequence of lines.
344
Raises RevisionNotPresent if version is not present in
347
raise NotImplementedError(self.get_lines)
349
def _get_lf_split_line_list(self, version_ids):
350
return [StringIO(t).readlines() for t in self.get_texts(version_ids)]
352
def get_ancestry(self, version_ids, topo_sorted=True):
353
"""Return a list of all ancestors of given version(s). This
354
will not include the null revision.
356
This list will not be topologically sorted if topo_sorted=False is
359
Must raise RevisionNotPresent if any of the given versions are
360
not present in file history."""
361
if isinstance(version_ids, basestring):
362
version_ids = [version_ids]
363
raise NotImplementedError(self.get_ancestry)
365
def get_ancestry_with_ghosts(self, version_ids):
366
"""Return a list of all ancestors of given version(s). This
367
will not include the null revision.
369
Must raise RevisionNotPresent if any of the given versions are
370
not present in file history.
372
Ghosts that are known about will be included in ancestry list,
373
but are not explicitly marked.
375
raise NotImplementedError(self.get_ancestry_with_ghosts)
377
@deprecated_method(one_four)
378
def get_graph(self, version_ids=None):
379
"""Return a graph from the versioned file.
381
Ghosts are not listed or referenced in the graph.
382
:param version_ids: Versions to select.
383
None means retrieve all versions.
385
if version_ids is None:
386
result = self.get_parent_map(self.versions())
389
pending = set(version_ids)
391
this_iteration = pending
393
parents = self.get_parent_map(this_iteration)
394
for version, parents in parents.iteritems():
395
result[version] = parents
396
for parent in parents:
401
for parents in result.itervalues():
402
references.update(parents)
403
existing_parents = self.get_parent_map(references)
404
for key, parents in result.iteritems():
405
present_parents = [parent for parent in parents if parent in
407
result[key] = tuple(present_parents)
410
@deprecated_method(one_four)
411
def get_graph_with_ghosts(self):
412
"""Return a graph for the entire versioned file.
414
Ghosts are referenced in parents list but are not
417
raise NotImplementedError(self.get_graph_with_ghosts)
419
def get_parent_map(self, version_ids):
420
"""Get a map of the parents of version_ids.
422
:param version_ids: The version ids to look up parents for.
423
:return: A mapping from version id to parents.
425
raise NotImplementedError(self.get_parent_map)
427
@deprecated_method(one_four)
428
def get_parents(self, version_id):
429
"""Return version names for parents of a version.
431
Must raise RevisionNotPresent if version is not present in
435
all = self.get_parent_map([version_id])[version_id]
437
raise errors.RevisionNotPresent(version_id, self)
439
parent_parents = self.get_parent_map(all)
440
for version_id in all:
441
if version_id in parent_parents:
442
result.append(version_id)
445
def get_parents_with_ghosts(self, version_id):
446
"""Return version names for parents of version_id.
448
Will raise RevisionNotPresent if version_id is not present
451
Ghosts that are known about will be included in the parent list,
452
but are not explicitly marked.
455
return list(self.get_parent_map([version_id])[version_id])
457
raise errors.RevisionNotPresent(version_id, self)
459
@deprecated_method(one_four)
460
def annotate_iter(self, version_id):
461
"""Yield list of (version-id, line) pairs for the specified
464
Must raise RevisionNotPresent if the given version is
465
not present in file history.
467
return iter(self.annotate(version_id))
469
def annotate(self, version_id):
470
"""Return a list of (version-id, line) tuples for version_id.
472
:raise RevisionNotPresent: If the given version is
473
not present in file history.
475
raise NotImplementedError(self.annotate)
477
def join(self, other, pb=None, msg=None, version_ids=None,
478
ignore_missing=False):
479
"""Integrate versions from other into this versioned file.
481
If version_ids is None all versions from other should be
482
incorporated into this versioned file.
484
Must raise RevisionNotPresent if any of the specified versions
485
are not present in the other file's history unless ignore_missing
486
is supplied in which case they are silently skipped.
488
self._check_write_ok()
489
return InterVersionedFile.get(other, self).join(
495
def iter_lines_added_or_present_in_versions(self, version_ids=None,
497
"""Iterate over the lines in the versioned file from version_ids.
499
This may return lines from other versions. Each item the returned
500
iterator yields is a tuple of a line and a text version that that line
501
is present in (not introduced in).
503
Ordering of results is in whatever order is most suitable for the
504
underlying storage format.
506
If a progress bar is supplied, it may be used to indicate progress.
507
The caller is responsible for cleaning up progress bars (because this
510
NOTES: Lines are normalised: they will all have \n terminators.
511
Lines are returned in arbitrary order.
513
:return: An iterator over (line, version_id).
515
raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
517
@deprecated_method(one_four)
518
def iter_parents(self, version_ids):
519
"""Iterate through the parents for many version ids.
521
:param version_ids: An iterable yielding version_ids.
522
:return: An iterator that yields (version_id, parents). Requested
523
version_ids not present in the versioned file are simply skipped.
524
The order is undefined, allowing for different optimisations in
525
the underlying implementation.
527
return self.get_parent_map(version_ids).iteritems()
529
def plan_merge(self, ver_a, ver_b):
530
"""Return pseudo-annotation indicating how the two versions merge.
532
This is computed between versions a and b and their common
535
Weave lines present in none of them are skipped entirely.
538
killed-base Dead in base revision
539
killed-both Killed in each revision
542
unchanged Alive in both a and b (possibly created in both)
545
ghost-a Killed in a, unborn in b
546
ghost-b Killed in b, unborn in a
547
irrelevant Not in either revision
549
raise NotImplementedError(VersionedFile.plan_merge)
551
def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
552
b_marker=TextMerge.B_MARKER):
553
return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
556
class _PlanMergeVersionedFile(object):
557
"""A VersionedFile for uncommitted and committed texts.
559
It is intended to allow merges to be planned with working tree texts.
560
It implements only the small part of the VersionedFile interface used by
561
PlanMerge. It falls back to multiple versionedfiles for data not stored in
562
_PlanMergeVersionedFile itself.
565
def __init__(self, file_id, fallback_versionedfiles=None):
568
:param file_id: Used when raising exceptions.
569
:param fallback_versionedfiles: If supplied, the set of fallbacks to
570
use. Otherwise, _PlanMergeVersionedFile.fallback_versionedfiles
571
can be appended to later.
573
self._file_id = file_id
574
if fallback_versionedfiles is None:
575
self.fallback_versionedfiles = []
577
self.fallback_versionedfiles = fallback_versionedfiles
581
def plan_merge(self, ver_a, ver_b, base=None):
582
"""See VersionedFile.plan_merge"""
583
from bzrlib.merge import _PlanMerge
585
return _PlanMerge(ver_a, ver_b, self).plan_merge()
586
old_plan = list(_PlanMerge(ver_a, base, self).plan_merge())
587
new_plan = list(_PlanMerge(ver_a, ver_b, self).plan_merge())
588
return _PlanMerge._subtract_plans(old_plan, new_plan)
590
def plan_lca_merge(self, ver_a, ver_b, base=None):
591
from bzrlib.merge import _PlanLCAMerge
592
graph = self._get_graph()
593
new_plan = _PlanLCAMerge(ver_a, ver_b, self, graph).plan_merge()
596
old_plan = _PlanLCAMerge(ver_a, base, self, graph).plan_merge()
597
return _PlanLCAMerge._subtract_plans(list(old_plan), list(new_plan))
599
def add_lines(self, version_id, parents, lines):
600
"""See VersionedFile.add_lines
602
Lines are added locally, not fallback versionedfiles. Also, ghosts are
603
permitted. Only reserved ids are permitted.
605
if not revision.is_reserved_id(version_id):
606
raise ValueError('Only reserved ids may be used')
608
raise ValueError('Parents may not be None')
610
raise ValueError('Lines may not be None')
611
self._parents[version_id] = tuple(parents)
612
self._lines[version_id] = lines
614
def get_lines(self, version_id):
615
"""See VersionedFile.get_ancestry"""
616
lines = self._lines.get(version_id)
617
if lines is not None:
619
for versionedfile in self.fallback_versionedfiles:
621
return versionedfile.get_lines(version_id)
622
except errors.RevisionNotPresent:
625
raise errors.RevisionNotPresent(version_id, self._file_id)
627
def get_ancestry(self, version_id, topo_sorted=False):
628
"""See VersionedFile.get_ancestry.
630
Note that this implementation assumes that if a VersionedFile can
631
answer get_ancestry at all, it can give an authoritative answer. In
632
fact, ghosts can invalidate this assumption. But it's good enough
633
99% of the time, and far cheaper/simpler.
635
Also note that the results of this version are never topologically
636
sorted, and are a set.
639
raise ValueError('This implementation does not provide sorting')
640
parents = self._parents.get(version_id)
642
for vf in self.fallback_versionedfiles:
644
return vf.get_ancestry(version_id, topo_sorted=False)
645
except errors.RevisionNotPresent:
648
raise errors.RevisionNotPresent(version_id, self._file_id)
649
ancestry = set([version_id])
650
for parent in parents:
651
ancestry.update(self.get_ancestry(parent, topo_sorted=False))
654
def get_parent_map(self, version_ids):
655
"""See VersionedFile.get_parent_map"""
657
pending = set(version_ids)
658
for key in version_ids:
660
result[key] = self._parents[key]
663
pending = pending - set(result.keys())
664
for versionedfile in self.fallback_versionedfiles:
665
parents = versionedfile.get_parent_map(pending)
666
result.update(parents)
667
pending = pending - set(parents.keys())
672
def _get_graph(self):
673
from bzrlib.graph import (
676
_StackedParentsProvider,
678
from bzrlib.repofmt.knitrepo import _KnitParentsProvider
679
parent_providers = [DictParentsProvider(self._parents)]
680
for vf in self.fallback_versionedfiles:
681
parent_providers.append(_KnitParentsProvider(vf))
682
return Graph(_StackedParentsProvider(parent_providers))
685
class PlanWeaveMerge(TextMerge):
686
"""Weave merge that takes a plan as its input.
688
This exists so that VersionedFile.plan_merge is implementable.
689
Most callers will want to use WeaveMerge instead.
692
def __init__(self, plan, a_marker=TextMerge.A_MARKER,
693
b_marker=TextMerge.B_MARKER):
694
TextMerge.__init__(self, a_marker, b_marker)
697
def _merge_struct(self):
702
def outstanding_struct():
703
if not lines_a and not lines_b:
705
elif ch_a and not ch_b:
708
elif ch_b and not ch_a:
710
elif lines_a == lines_b:
713
yield (lines_a, lines_b)
715
# We previously considered either 'unchanged' or 'killed-both' lines
716
# to be possible places to resynchronize. However, assuming agreement
717
# on killed-both lines may be too aggressive. -- mbp 20060324
718
for state, line in self.plan:
719
if state == 'unchanged':
720
# resync and flush queued conflicts changes if any
721
for struct in outstanding_struct():
727
if state == 'unchanged':
730
elif state == 'killed-a':
733
elif state == 'killed-b':
736
elif state == 'new-a':
739
elif state == 'new-b':
742
elif state == 'conflicted-a':
745
elif state == 'conflicted-b':
749
assert state in ('irrelevant', 'ghost-a', 'ghost-b',
750
'killed-base', 'killed-both'), state
751
for struct in outstanding_struct():
755
class WeaveMerge(PlanWeaveMerge):
756
"""Weave merge that takes a VersionedFile and two versions as its input."""
758
def __init__(self, versionedfile, ver_a, ver_b,
759
a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
760
plan = versionedfile.plan_merge(ver_a, ver_b)
761
PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
764
class InterVersionedFile(InterObject):
765
"""This class represents operations taking place between two VersionedFiles.
767
Its instances have methods like join, and contain
768
references to the source and target versionedfiles these operations can be
771
Often we will provide convenience methods on 'versionedfile' which carry out
772
operations with another versionedfile - they will always forward to
773
InterVersionedFile.get(other).method_name(parameters).
777
"""The available optimised InterVersionedFile types."""
779
def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
780
"""Integrate versions from self.source into self.target.
782
If version_ids is None all versions from source should be
783
incorporated into this versioned file.
785
Must raise RevisionNotPresent if any of the specified versions
786
are not present in the other file's history unless ignore_missing is
787
supplied in which case they are silently skipped.
790
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
791
graph = Graph(self.source)
792
search = graph._make_breadth_first_searcher(version_ids)
793
transitive_ids = set()
794
map(transitive_ids.update, list(search))
795
parent_map = self.source.get_parent_map(transitive_ids)
796
order = tsort.topo_sort(parent_map.items())
797
pb = ui.ui_factory.nested_progress_bar()
800
# TODO for incremental cross-format work:
801
# make a versioned file with the following content:
802
# all revisions we have been asked to join
803
# all their ancestors that are *not* in target already.
804
# the immediate parents of the above two sets, with
805
# empty parent lists - these versions are in target already
806
# and the incorrect version data will be ignored.
807
# TODO: for all ancestors that are present in target already,
808
# check them for consistent data, this requires moving sha1 from
810
# TODO: remove parent texts when they are not relevant any more for
811
# memory pressure reduction. RBC 20060313
812
# pb.update('Converting versioned data', 0, len(order))
814
for index, version in enumerate(order):
815
pb.update('Converting versioned data', index, total)
816
if version in target:
818
_, _, parent_text = target.add_lines(version,
820
self.source.get_lines(version),
821
parent_texts=parent_texts)
822
parent_texts[version] = parent_text
827
def _get_source_version_ids(self, version_ids, ignore_missing):
828
"""Determine the version ids to be used from self.source.
830
:param version_ids: The caller-supplied version ids to check. (None
831
for all). If None is in version_ids, it is stripped.
832
:param ignore_missing: if True, remove missing ids from the version
833
list. If False, raise RevisionNotPresent on
834
a missing version id.
835
:return: A set of version ids.
837
if version_ids is None:
838
# None cannot be in source.versions
839
return set(self.source.versions())
842
return set(self.source.versions()).intersection(set(version_ids))
844
new_version_ids = set()
845
for version in version_ids:
848
if not self.source.has_version(version):
849
raise errors.RevisionNotPresent(version, str(self.source))
851
new_version_ids.add(version)
852
return new_version_ids