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.registry import Registry
41
from bzrlib.symbol_versioning import *
42
from bzrlib.textmerge import TextMerge
45
adapter_registry = Registry()
46
adapter_registry.register_lazy(('knit-delta-gz', 'fulltext'), 'bzrlib.knit',
47
'DeltaPlainToFullText')
48
adapter_registry.register_lazy(('knit-ft-gz', 'fulltext'), 'bzrlib.knit',
50
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'knit-delta-gz'),
51
'bzrlib.knit', 'DeltaAnnotatedToUnannotated')
52
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'fulltext'),
53
'bzrlib.knit', 'DeltaAnnotatedToFullText')
54
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'knit-ft-gz'),
55
'bzrlib.knit', 'FTAnnotatedToUnannotated')
56
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
57
'bzrlib.knit', 'FTAnnotatedToFullText')
60
class ContentFactory(object):
61
"""Abstract interface for insertion and retrieval from a VersionedFile.
63
:ivar sha1: None, or the sha1 of the content fulltext.
64
:ivar storage_kind: The native storage kind of this factory. One of
65
'mpdiff', 'knit-annotated-ft', 'knit-annotated-delta', 'knit-ft',
66
'knit-delta', 'fulltext', 'knit-annotated-ft-gz',
67
'knit-annotated-delta-gz', 'knit-ft-gz', 'knit-delta-gz'.
68
:ivar key: The key of this content. Each key is a tuple with a single
70
:ivar parents: A tuple of parent keys for self.key. If the object has
71
no parent information, None (as opposed to () for an empty list of
76
"""Create a ContentFactory."""
78
self.storage_kind = None
83
class AbsentContentFactory(object):
84
"""A placeholder content factory for unavailable texts.
87
:ivar storage_kind: 'absent'.
88
:ivar key: The key of this content. Each key is a tuple with a single
93
def __init__(self, key):
94
"""Create a ContentFactory."""
96
self.storage_kind = 'absent'
101
def filter_absent(record_stream):
102
"""Adapt a record stream to remove absent records."""
103
for record in record_stream:
104
if record.storage_kind != 'absent':
108
class VersionedFile(object):
109
"""Versioned text file storage.
111
A versioned file manages versions of line-based text files,
112
keeping track of the originating version for each line.
114
To clients the "lines" of the file are represented as a list of
115
strings. These strings will typically have terminal newline
116
characters, but this is not required. In particular files commonly
117
do not have a newline at the end of the file.
119
Texts are identified by a version-id string.
123
def check_not_reserved_id(version_id):
124
revision.check_not_reserved_id(version_id)
126
def copy_to(self, name, transport):
127
"""Copy this versioned file to name on transport."""
128
raise NotImplementedError(self.copy_to)
130
def get_record_stream(self, versions, ordering, include_delta_closure):
131
"""Get a stream of records for versions.
133
:param versions: The versions to include. Each version is a tuple
135
:param ordering: Either 'unordered' or 'topological'. A topologically
136
sorted stream has compression parents strictly before their
138
:param include_delta_closure: If True then the closure across any
139
compression parents will be included (in the opaque data).
140
:return: An iterator of ContentFactory objects, each of which is only
141
valid until the iterator is advanced.
143
raise NotImplementedError(self.get_record_stream)
145
@deprecated_method(one_four)
146
def has_ghost(self, version_id):
147
"""Returns whether version is present as a ghost."""
148
raise NotImplementedError(self.has_ghost)
150
def has_version(self, version_id):
151
"""Returns whether version is present."""
152
raise NotImplementedError(self.has_version)
154
def insert_record_stream(self, stream):
155
"""Insert a record stream into this versioned file.
157
:param stream: A stream of records to insert.
159
:seealso VersionedFile.get_record_stream:
161
raise NotImplementedError
163
def add_lines(self, version_id, parents, lines, parent_texts=None,
164
left_matching_blocks=None, nostore_sha=None, random_id=False,
166
"""Add a single text on top of the versioned file.
168
Must raise RevisionAlreadyPresent if the new version is
169
already present in file history.
171
Must raise RevisionNotPresent if any of the given parents are
172
not present in file history.
174
:param lines: A list of lines. Each line must be a bytestring. And all
175
of them except the last must be terminated with \n and contain no
176
other \n's. The last line may either contain no \n's or a single
177
terminated \n. If the lines list does meet this constraint the add
178
routine may error or may succeed - but you will be unable to read
179
the data back accurately. (Checking the lines have been split
180
correctly is expensive and extremely unlikely to catch bugs so it
181
is not done at runtime unless check_content is True.)
182
:param parent_texts: An optional dictionary containing the opaque
183
representations of some or all of the parents of version_id to
184
allow delta optimisations. VERY IMPORTANT: the texts must be those
185
returned by add_lines or data corruption can be caused.
186
:param left_matching_blocks: a hint about which areas are common
187
between the text and its left-hand-parent. The format is
188
the SequenceMatcher.get_matching_blocks format.
189
:param nostore_sha: Raise ExistingContent and do not add the lines to
190
the versioned file if the digest of the lines matches this.
191
:param random_id: If True a random id has been selected rather than
192
an id determined by some deterministic process such as a converter
193
from a foreign VCS. When True the backend may choose not to check
194
for uniqueness of the resulting key within the versioned file, so
195
this should only be done when the result is expected to be unique
197
:param check_content: If True, the lines supplied are verified to be
198
bytestrings that are correctly formed lines.
199
:return: The text sha1, the number of bytes in the text, and an opaque
200
representation of the inserted version which can be provided
201
back to future add_lines calls in the parent_texts dictionary.
203
self._check_write_ok()
204
return self._add_lines(version_id, parents, lines, parent_texts,
205
left_matching_blocks, nostore_sha, random_id, check_content)
207
def _add_lines(self, version_id, parents, lines, parent_texts,
208
left_matching_blocks, nostore_sha, random_id, check_content):
209
"""Helper to do the class specific add_lines."""
210
raise NotImplementedError(self.add_lines)
212
def add_lines_with_ghosts(self, version_id, parents, lines,
213
parent_texts=None, nostore_sha=None, random_id=False,
214
check_content=True, left_matching_blocks=None):
215
"""Add lines to the versioned file, allowing ghosts to be present.
217
This takes the same parameters as add_lines and returns the same.
219
self._check_write_ok()
220
return self._add_lines_with_ghosts(version_id, parents, lines,
221
parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
223
def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
224
nostore_sha, random_id, check_content, left_matching_blocks):
225
"""Helper to do class specific add_lines_with_ghosts."""
226
raise NotImplementedError(self.add_lines_with_ghosts)
228
def check(self, progress_bar=None):
229
"""Check the versioned file for integrity."""
230
raise NotImplementedError(self.check)
232
def _check_lines_not_unicode(self, lines):
233
"""Check that lines being added to a versioned file are not unicode."""
235
if line.__class__ is not str:
236
raise errors.BzrBadParameterUnicode("lines")
238
def _check_lines_are_lines(self, lines):
239
"""Check that the lines really are full lines without inline EOL."""
241
if '\n' in line[:-1]:
242
raise errors.BzrBadParameterContainsNewline("lines")
244
@deprecated_method(one_four)
245
def enable_cache(self):
246
"""Tell this versioned file that it should cache any data it reads.
248
This is advisory, implementations do not have to support caching.
252
@deprecated_method(one_four)
253
def clear_cache(self):
254
"""Remove any data cached in the versioned file object.
256
This only needs to be supported if caches are supported
260
@deprecated_method(one_four)
261
def clone_text(self, new_version_id, old_version_id, parents):
262
"""Add an identical text to old_version_id as new_version_id.
264
Must raise RevisionNotPresent if the old version or any of the
265
parents are not present in file history.
267
Must raise RevisionAlreadyPresent if the new version is
268
already present in file history."""
269
self._check_write_ok()
270
return self.add_lines(new_version_id, parents,
271
self.get_lines(old_version_id))
273
def get_format_signature(self):
274
"""Get a text description of the data encoding in this file.
278
raise NotImplementedError(self.get_format_signature)
280
def make_mpdiffs(self, version_ids):
281
"""Create multiparent diffs for specified versions."""
282
knit_versions = set()
283
knit_versions.update(version_ids)
284
parent_map = self.get_parent_map(version_ids)
285
for version_id in version_ids:
287
knit_versions.update(parent_map[version_id])
289
raise RevisionNotPresent(version_id, self)
290
# We need to filter out ghosts, because we can't diff against them.
291
knit_versions = set(self.get_parent_map(knit_versions).keys())
292
lines = dict(zip(knit_versions,
293
self._get_lf_split_line_list(knit_versions)))
295
for version_id in version_ids:
296
target = lines[version_id]
298
parents = [lines[p] for p in parent_map[version_id] if p in
301
raise RevisionNotPresent(version_id, self)
303
left_parent_blocks = self._extract_blocks(version_id,
306
left_parent_blocks = None
307
diffs.append(multiparent.MultiParent.from_lines(target, parents,
311
def _extract_blocks(self, version_id, source, target):
314
def add_mpdiffs(self, records):
315
"""Add mpdiffs to this VersionedFile.
317
Records should be iterables of version, parents, expected_sha1,
318
mpdiff. mpdiff should be a MultiParent instance.
320
# Does this need to call self._check_write_ok()? (IanC 20070919)
322
mpvf = multiparent.MultiMemoryVersionedFile()
324
for version, parent_ids, expected_sha1, mpdiff in records:
325
versions.append(version)
326
mpvf.add_diff(mpdiff, version, parent_ids)
327
needed_parents = set()
328
for version, parent_ids, expected_sha1, mpdiff in records:
329
needed_parents.update(p for p in parent_ids
330
if not mpvf.has_version(p))
331
present_parents = set(self.get_parent_map(needed_parents).keys())
332
for parent_id, lines in zip(present_parents,
333
self._get_lf_split_line_list(present_parents)):
334
mpvf.add_version(lines, parent_id, [])
335
for (version, parent_ids, expected_sha1, mpdiff), lines in\
336
zip(records, mpvf.get_line_list(versions)):
337
if len(parent_ids) == 1:
338
left_matching_blocks = list(mpdiff.get_matching_blocks(0,
339
mpvf.get_diff(parent_ids[0]).num_lines()))
341
left_matching_blocks = None
343
_, _, version_text = self.add_lines_with_ghosts(version,
344
parent_ids, lines, vf_parents,
345
left_matching_blocks=left_matching_blocks)
346
except NotImplementedError:
347
# The vf can't handle ghosts, so add lines normally, which will
348
# (reasonably) fail if there are ghosts in the data.
349
_, _, version_text = self.add_lines(version,
350
parent_ids, lines, vf_parents,
351
left_matching_blocks=left_matching_blocks)
352
vf_parents[version] = version_text
353
for (version, parent_ids, expected_sha1, mpdiff), sha1 in\
354
zip(records, self.get_sha1s(versions)):
355
if expected_sha1 != sha1:
356
raise errors.VersionedFileInvalidChecksum(version)
358
@deprecated_method(one_four)
359
def get_sha1(self, version_id):
360
"""Get the stored sha1 sum for the given revision.
362
:param version_id: The name of the version to lookup
364
return self.get_sha1s([version_id])[0]
366
def get_sha1s(self, version_ids):
367
"""Get the stored sha1 sums for the given revisions.
369
:param version_ids: The names of the versions to lookup
370
:return: a list of sha1s in order according to the version_ids
372
raise NotImplementedError(self.get_sha1s)
374
def get_text(self, version_id):
375
"""Return version contents as a text string.
377
Raises RevisionNotPresent if version is not present in
380
return ''.join(self.get_lines(version_id))
381
get_string = get_text
383
def get_texts(self, version_ids):
384
"""Return the texts of listed versions as a list of strings.
386
Raises RevisionNotPresent if version is not present in
389
return [''.join(self.get_lines(v)) for v in version_ids]
391
def get_lines(self, version_id):
392
"""Return version contents as a sequence of lines.
394
Raises RevisionNotPresent if version is not present in
397
raise NotImplementedError(self.get_lines)
399
def _get_lf_split_line_list(self, version_ids):
400
return [StringIO(t).readlines() for t in self.get_texts(version_ids)]
402
def get_ancestry(self, version_ids, topo_sorted=True):
403
"""Return a list of all ancestors of given version(s). This
404
will not include the null revision.
406
This list will not be topologically sorted if topo_sorted=False is
409
Must raise RevisionNotPresent if any of the given versions are
410
not present in file history."""
411
if isinstance(version_ids, basestring):
412
version_ids = [version_ids]
413
raise NotImplementedError(self.get_ancestry)
415
def get_ancestry_with_ghosts(self, version_ids):
416
"""Return a list of all ancestors of given version(s). This
417
will not include the null revision.
419
Must raise RevisionNotPresent if any of the given versions are
420
not present in file history.
422
Ghosts that are known about will be included in ancestry list,
423
but are not explicitly marked.
425
raise NotImplementedError(self.get_ancestry_with_ghosts)
427
@deprecated_method(one_four)
428
def get_graph(self, version_ids=None):
429
"""Return a graph from the versioned file.
431
Ghosts are not listed or referenced in the graph.
432
:param version_ids: Versions to select.
433
None means retrieve all versions.
435
if version_ids is None:
436
result = self.get_parent_map(self.versions())
439
pending = set(version_ids)
441
this_iteration = pending
443
parents = self.get_parent_map(this_iteration)
444
for version, parents in parents.iteritems():
445
result[version] = parents
446
for parent in parents:
451
for parents in result.itervalues():
452
references.update(parents)
453
existing_parents = self.get_parent_map(references)
454
for key, parents in result.iteritems():
455
present_parents = [parent for parent in parents if parent in
457
result[key] = tuple(present_parents)
460
@deprecated_method(one_four)
461
def get_graph_with_ghosts(self):
462
"""Return a graph for the entire versioned file.
464
Ghosts are referenced in parents list but are not
467
raise NotImplementedError(self.get_graph_with_ghosts)
469
def get_parent_map(self, version_ids):
470
"""Get a map of the parents of version_ids.
472
:param version_ids: The version ids to look up parents for.
473
:return: A mapping from version id to parents.
475
raise NotImplementedError(self.get_parent_map)
477
@deprecated_method(one_four)
478
def get_parents(self, version_id):
479
"""Return version names for parents of a version.
481
Must raise RevisionNotPresent if version is not present in
485
all = self.get_parent_map([version_id])[version_id]
487
raise errors.RevisionNotPresent(version_id, self)
489
parent_parents = self.get_parent_map(all)
490
for version_id in all:
491
if version_id in parent_parents:
492
result.append(version_id)
495
def get_parents_with_ghosts(self, version_id):
496
"""Return version names for parents of version_id.
498
Will raise RevisionNotPresent if version_id is not present
501
Ghosts that are known about will be included in the parent list,
502
but are not explicitly marked.
505
return list(self.get_parent_map([version_id])[version_id])
507
raise errors.RevisionNotPresent(version_id, self)
509
@deprecated_method(one_four)
510
def annotate_iter(self, version_id):
511
"""Yield list of (version-id, line) pairs for the specified
514
Must raise RevisionNotPresent if the given version is
515
not present in file history.
517
return iter(self.annotate(version_id))
519
def annotate(self, version_id):
520
"""Return a list of (version-id, line) tuples for version_id.
522
:raise RevisionNotPresent: If the given version is
523
not present in file history.
525
raise NotImplementedError(self.annotate)
527
@deprecated_method(one_five)
528
def join(self, other, pb=None, msg=None, version_ids=None,
529
ignore_missing=False):
530
"""Integrate versions from other into this versioned file.
532
If version_ids is None all versions from other should be
533
incorporated into this versioned file.
535
Must raise RevisionNotPresent if any of the specified versions
536
are not present in the other file's history unless ignore_missing
537
is supplied in which case they are silently skipped.
539
self._check_write_ok()
540
return InterVersionedFile.get(other, self).join(
546
def iter_lines_added_or_present_in_versions(self, version_ids=None,
548
"""Iterate over the lines in the versioned file from version_ids.
550
This may return lines from other versions. Each item the returned
551
iterator yields is a tuple of a line and a text version that that line
552
is present in (not introduced in).
554
Ordering of results is in whatever order is most suitable for the
555
underlying storage format.
557
If a progress bar is supplied, it may be used to indicate progress.
558
The caller is responsible for cleaning up progress bars (because this
561
NOTES: Lines are normalised: they will all have \n terminators.
562
Lines are returned in arbitrary order.
564
:return: An iterator over (line, version_id).
566
raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
568
@deprecated_method(one_four)
569
def iter_parents(self, version_ids):
570
"""Iterate through the parents for many version ids.
572
:param version_ids: An iterable yielding version_ids.
573
:return: An iterator that yields (version_id, parents). Requested
574
version_ids not present in the versioned file are simply skipped.
575
The order is undefined, allowing for different optimisations in
576
the underlying implementation.
578
return self.get_parent_map(version_ids).iteritems()
580
def plan_merge(self, ver_a, ver_b):
581
"""Return pseudo-annotation indicating how the two versions merge.
583
This is computed between versions a and b and their common
586
Weave lines present in none of them are skipped entirely.
589
killed-base Dead in base revision
590
killed-both Killed in each revision
593
unchanged Alive in both a and b (possibly created in both)
596
ghost-a Killed in a, unborn in b
597
ghost-b Killed in b, unborn in a
598
irrelevant Not in either revision
600
raise NotImplementedError(VersionedFile.plan_merge)
602
def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
603
b_marker=TextMerge.B_MARKER):
604
return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
607
class RecordingVersionedFileDecorator(object):
608
"""A minimal versioned file that records calls made on it.
610
Only enough methods have been added to support tests using it to date.
612
:ivar calls: A list of the calls made; can be reset at any time by
616
def __init__(self, backing_vf):
617
"""Create a RecordingVersionedFileDecorator decorating backing_vf.
619
:param backing_vf: The versioned file to answer all methods.
621
self._backing_vf = backing_vf
624
def get_lines(self, version_ids):
625
self.calls.append(("get_lines", version_ids))
626
return self._backing_vf.get_lines(version_ids)
629
class _PlanMergeVersionedFile(object):
630
"""A VersionedFile for uncommitted and committed texts.
632
It is intended to allow merges to be planned with working tree texts.
633
It implements only the small part of the VersionedFile interface used by
634
PlanMerge. It falls back to multiple versionedfiles for data not stored in
635
_PlanMergeVersionedFile itself.
638
def __init__(self, file_id, fallback_versionedfiles=None):
641
:param file_id: Used when raising exceptions.
642
:param fallback_versionedfiles: If supplied, the set of fallbacks to
643
use. Otherwise, _PlanMergeVersionedFile.fallback_versionedfiles
644
can be appended to later.
646
self._file_id = file_id
647
if fallback_versionedfiles is None:
648
self.fallback_versionedfiles = []
650
self.fallback_versionedfiles = fallback_versionedfiles
654
def plan_merge(self, ver_a, ver_b, base=None):
655
"""See VersionedFile.plan_merge"""
656
from bzrlib.merge import _PlanMerge
658
return _PlanMerge(ver_a, ver_b, self).plan_merge()
659
old_plan = list(_PlanMerge(ver_a, base, self).plan_merge())
660
new_plan = list(_PlanMerge(ver_a, ver_b, self).plan_merge())
661
return _PlanMerge._subtract_plans(old_plan, new_plan)
663
def plan_lca_merge(self, ver_a, ver_b, base=None):
664
from bzrlib.merge import _PlanLCAMerge
665
graph = self._get_graph()
666
new_plan = _PlanLCAMerge(ver_a, ver_b, self, graph).plan_merge()
669
old_plan = _PlanLCAMerge(ver_a, base, self, graph).plan_merge()
670
return _PlanLCAMerge._subtract_plans(list(old_plan), list(new_plan))
672
def add_lines(self, version_id, parents, lines):
673
"""See VersionedFile.add_lines
675
Lines are added locally, not fallback versionedfiles. Also, ghosts are
676
permitted. Only reserved ids are permitted.
678
if not revision.is_reserved_id(version_id):
679
raise ValueError('Only reserved ids may be used')
681
raise ValueError('Parents may not be None')
683
raise ValueError('Lines may not be None')
684
self._parents[version_id] = tuple(parents)
685
self._lines[version_id] = lines
687
def get_lines(self, version_id):
688
"""See VersionedFile.get_ancestry"""
689
lines = self._lines.get(version_id)
690
if lines is not None:
692
for versionedfile in self.fallback_versionedfiles:
694
return versionedfile.get_lines(version_id)
695
except errors.RevisionNotPresent:
698
raise errors.RevisionNotPresent(version_id, self._file_id)
700
def get_ancestry(self, version_id, topo_sorted=False):
701
"""See VersionedFile.get_ancestry.
703
Note that this implementation assumes that if a VersionedFile can
704
answer get_ancestry at all, it can give an authoritative answer. In
705
fact, ghosts can invalidate this assumption. But it's good enough
706
99% of the time, and far cheaper/simpler.
708
Also note that the results of this version are never topologically
709
sorted, and are a set.
712
raise ValueError('This implementation does not provide sorting')
713
parents = self._parents.get(version_id)
715
for vf in self.fallback_versionedfiles:
717
return vf.get_ancestry(version_id, topo_sorted=False)
718
except errors.RevisionNotPresent:
721
raise errors.RevisionNotPresent(version_id, self._file_id)
722
ancestry = set([version_id])
723
for parent in parents:
724
ancestry.update(self.get_ancestry(parent, topo_sorted=False))
727
def get_parent_map(self, version_ids):
728
"""See VersionedFile.get_parent_map"""
730
pending = set(version_ids)
731
for key in version_ids:
733
result[key] = self._parents[key]
736
pending = pending - set(result.keys())
737
for versionedfile in self.fallback_versionedfiles:
738
parents = versionedfile.get_parent_map(pending)
739
result.update(parents)
740
pending = pending - set(parents.keys())
745
def _get_graph(self):
746
from bzrlib.graph import (
749
_StackedParentsProvider,
751
from bzrlib.repofmt.knitrepo import _KnitParentsProvider
752
parent_providers = [DictParentsProvider(self._parents)]
753
for vf in self.fallback_versionedfiles:
754
parent_providers.append(_KnitParentsProvider(vf))
755
return Graph(_StackedParentsProvider(parent_providers))
758
class PlanWeaveMerge(TextMerge):
759
"""Weave merge that takes a plan as its input.
761
This exists so that VersionedFile.plan_merge is implementable.
762
Most callers will want to use WeaveMerge instead.
765
def __init__(self, plan, a_marker=TextMerge.A_MARKER,
766
b_marker=TextMerge.B_MARKER):
767
TextMerge.__init__(self, a_marker, b_marker)
770
def _merge_struct(self):
775
def outstanding_struct():
776
if not lines_a and not lines_b:
778
elif ch_a and not ch_b:
781
elif ch_b and not ch_a:
783
elif lines_a == lines_b:
786
yield (lines_a, lines_b)
788
# We previously considered either 'unchanged' or 'killed-both' lines
789
# to be possible places to resynchronize. However, assuming agreement
790
# on killed-both lines may be too aggressive. -- mbp 20060324
791
for state, line in self.plan:
792
if state == 'unchanged':
793
# resync and flush queued conflicts changes if any
794
for struct in outstanding_struct():
800
if state == 'unchanged':
803
elif state == 'killed-a':
806
elif state == 'killed-b':
809
elif state == 'new-a':
812
elif state == 'new-b':
815
elif state == 'conflicted-a':
818
elif state == 'conflicted-b':
822
assert state in ('irrelevant', 'ghost-a', 'ghost-b',
823
'killed-base', 'killed-both'), state
824
for struct in outstanding_struct():
828
class WeaveMerge(PlanWeaveMerge):
829
"""Weave merge that takes a VersionedFile and two versions as its input."""
831
def __init__(self, versionedfile, ver_a, ver_b,
832
a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
833
plan = versionedfile.plan_merge(ver_a, ver_b)
834
PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
837
class InterVersionedFile(InterObject):
838
"""This class represents operations taking place between two VersionedFiles.
840
Its instances have methods like join, and contain
841
references to the source and target versionedfiles these operations can be
844
Often we will provide convenience methods on 'versionedfile' which carry out
845
operations with another versionedfile - they will always forward to
846
InterVersionedFile.get(other).method_name(parameters).
850
"""The available optimised InterVersionedFile types."""
852
def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
853
"""Integrate versions from self.source into self.target.
855
If version_ids is None all versions from source should be
856
incorporated into this versioned file.
858
Must raise RevisionNotPresent if any of the specified versions
859
are not present in the other file's history unless ignore_missing is
860
supplied in which case they are silently skipped.
863
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
864
graph = Graph(self.source)
865
search = graph._make_breadth_first_searcher(version_ids)
866
transitive_ids = set()
867
map(transitive_ids.update, list(search))
868
parent_map = self.source.get_parent_map(transitive_ids)
869
order = tsort.topo_sort(parent_map.items())
870
pb = ui.ui_factory.nested_progress_bar()
873
# TODO for incremental cross-format work:
874
# make a versioned file with the following content:
875
# all revisions we have been asked to join
876
# all their ancestors that are *not* in target already.
877
# the immediate parents of the above two sets, with
878
# empty parent lists - these versions are in target already
879
# and the incorrect version data will be ignored.
880
# TODO: for all ancestors that are present in target already,
881
# check them for consistent data, this requires moving sha1 from
883
# TODO: remove parent texts when they are not relevant any more for
884
# memory pressure reduction. RBC 20060313
885
# pb.update('Converting versioned data', 0, len(order))
887
for index, version in enumerate(order):
888
pb.update('Converting versioned data', index, total)
889
if version in target:
891
_, _, parent_text = target.add_lines(version,
893
self.source.get_lines(version),
894
parent_texts=parent_texts)
895
parent_texts[version] = parent_text
900
def _get_source_version_ids(self, version_ids, ignore_missing):
901
"""Determine the version ids to be used from self.source.
903
:param version_ids: The caller-supplied version ids to check. (None
904
for all). If None is in version_ids, it is stripped.
905
:param ignore_missing: if True, remove missing ids from the version
906
list. If False, raise RevisionNotPresent on
907
a missing version id.
908
:return: A set of version ids.
910
if version_ids is None:
911
# None cannot be in source.versions
912
return set(self.source.versions())
915
return set(self.source.versions()).intersection(set(version_ids))
917
new_version_ids = set()
918
for version in version_ids:
921
if not self.source.has_version(version):
922
raise errors.RevisionNotPresent(version, str(self.source))
924
new_version_ids.add(version)
925
return new_version_ids