~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Alexander Belchenko
  • Date: 2007-11-02 08:45:10 UTC
  • mto: This revision was merged to the branch mainline in revision 2968.
  • Revision ID: bialix@ukr.net-20071102084510-ngqdd24hjhfdkgw3
start 0.93 development cycle; deprecate osutils.backup_file

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
    revision,
31
31
    ui,
32
32
    )
33
 
from bzrlib.graph import Graph
34
33
from bzrlib.transport.memory import MemoryTransport
35
34
""")
36
35
 
37
36
from cStringIO import StringIO
38
37
 
39
38
from bzrlib.inter import InterObject
40
 
from bzrlib.registry import Registry
41
 
from bzrlib.symbol_versioning import *
42
39
from bzrlib.textmerge import TextMerge
43
40
 
44
41
 
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',
49
 
    'FTPlainToFullText')
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')
58
 
 
59
 
 
60
 
class ContentFactory(object):
61
 
    """Abstract interface for insertion and retrieval from a VersionedFile.
62
 
    
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
69
 
        string in it.
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
72
 
        parents).
73
 
        """
74
 
 
75
 
    def __init__(self):
76
 
        """Create a ContentFactory."""
77
 
        self.sha1 = None
78
 
        self.storage_kind = None
79
 
        self.key = None
80
 
        self.parents = None
81
 
 
82
 
 
83
 
class AbsentContentFactory(object):
84
 
    """A placeholder content factory for unavailable texts.
85
 
    
86
 
    :ivar sha1: None.
87
 
    :ivar storage_kind: 'absent'.
88
 
    :ivar key: The key of this content. Each key is a tuple with a single
89
 
        string in it.
90
 
    :ivar parents: None.
91
 
    """
92
 
 
93
 
    def __init__(self, key):
94
 
        """Create a ContentFactory."""
95
 
        self.sha1 = None
96
 
        self.storage_kind = 'absent'
97
 
        self.key = key
98
 
        self.parents = None
99
 
 
100
 
 
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':
105
 
            yield record
106
 
 
107
 
 
108
42
class VersionedFile(object):
109
43
    """Versioned text file storage.
110
44
    
119
53
    Texts are identified by a version-id string.
120
54
    """
121
55
 
 
56
    def __init__(self, access_mode):
 
57
        self.finished = False
 
58
        self._access_mode = access_mode
 
59
 
122
60
    @staticmethod
123
61
    def check_not_reserved_id(version_id):
124
62
        revision.check_not_reserved_id(version_id)
127
65
        """Copy this versioned file to name on transport."""
128
66
        raise NotImplementedError(self.copy_to)
129
67
 
130
 
    def get_record_stream(self, versions, ordering, include_delta_closure):
131
 
        """Get a stream of records for versions.
 
68
    def versions(self):
 
69
        """Return a unsorted list of versions."""
 
70
        raise NotImplementedError(self.versions)
132
71
 
133
 
        :param versions: The versions to include. Each version is a tuple
134
 
            (version,).
135
 
        :param ordering: Either 'unordered' or 'topological'. A topologically
136
 
            sorted stream has compression parents strictly before their
137
 
            children.
138
 
        :param include_delta_closure: If True then the closure across any
139
 
            compression parents will be included (in the data content of the
140
 
            stream, not in the emitted records). This guarantees that
141
 
            'fulltext' can be used successfully on every record.
142
 
        :return: An iterator of ContentFactory objects, each of which is only
143
 
            valid until the iterator is advanced.
144
 
        """
145
 
        raise NotImplementedError(self.get_record_stream)
 
72
    def has_ghost(self, version_id):
 
73
        """Returns whether version is present as a ghost."""
 
74
        raise NotImplementedError(self.has_ghost)
146
75
 
147
76
    def has_version(self, version_id):
148
77
        """Returns whether version is present."""
149
78
        raise NotImplementedError(self.has_version)
150
79
 
151
 
    def insert_record_stream(self, stream):
152
 
        """Insert a record stream into this versioned file.
153
 
 
154
 
        :param stream: A stream of records to insert. 
155
 
        :return: None
156
 
        :seealso VersionedFile.get_record_stream:
157
 
        """
158
 
        raise NotImplementedError
159
 
 
160
80
    def add_lines(self, version_id, parents, lines, parent_texts=None,
161
81
        left_matching_blocks=None, nostore_sha=None, random_id=False,
162
82
        check_content=True):
208
128
 
209
129
    def add_lines_with_ghosts(self, version_id, parents, lines,
210
130
        parent_texts=None, nostore_sha=None, random_id=False,
211
 
        check_content=True, left_matching_blocks=None):
 
131
        check_content=True):
212
132
        """Add lines to the versioned file, allowing ghosts to be present.
213
133
        
214
134
        This takes the same parameters as add_lines and returns the same.
215
135
        """
216
136
        self._check_write_ok()
217
137
        return self._add_lines_with_ghosts(version_id, parents, lines,
218
 
            parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
 
138
            parent_texts, nostore_sha, random_id, check_content)
219
139
 
220
140
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
221
 
        nostore_sha, random_id, check_content, left_matching_blocks):
 
141
        nostore_sha, random_id, check_content):
222
142
        """Helper to do class specific add_lines_with_ghosts."""
223
143
        raise NotImplementedError(self.add_lines_with_ghosts)
224
144
 
238
158
            if '\n' in line[:-1]:
239
159
                raise errors.BzrBadParameterContainsNewline("lines")
240
160
 
 
161
    def _check_write_ok(self):
 
162
        """Is the versioned file marked as 'finished' ? Raise if it is."""
 
163
        if self.finished:
 
164
            raise errors.OutSideTransaction()
 
165
        if self._access_mode != 'w':
 
166
            raise errors.ReadOnlyObjectDirtiedError(self)
 
167
 
 
168
    def enable_cache(self):
 
169
        """Tell this versioned file that it should cache any data it reads.
 
170
        
 
171
        This is advisory, implementations do not have to support caching.
 
172
        """
 
173
        pass
 
174
    
 
175
    def clear_cache(self):
 
176
        """Remove any data cached in the versioned file object.
 
177
 
 
178
        This only needs to be supported if caches are supported
 
179
        """
 
180
        pass
 
181
 
 
182
    def clone_text(self, new_version_id, old_version_id, parents):
 
183
        """Add an identical text to old_version_id as new_version_id.
 
184
 
 
185
        Must raise RevisionNotPresent if the old version or any of the
 
186
        parents are not present in file history.
 
187
 
 
188
        Must raise RevisionAlreadyPresent if the new version is
 
189
        already present in file history."""
 
190
        self._check_write_ok()
 
191
        return self._clone_text(new_version_id, old_version_id, parents)
 
192
 
 
193
    def _clone_text(self, new_version_id, old_version_id, parents):
 
194
        """Helper function to do the _clone_text work."""
 
195
        raise NotImplementedError(self.clone_text)
 
196
 
 
197
    def create_empty(self, name, transport, mode=None):
 
198
        """Create a new versioned file of this exact type.
 
199
 
 
200
        :param name: the file name
 
201
        :param transport: the transport
 
202
        :param mode: optional file mode.
 
203
        """
 
204
        raise NotImplementedError(self.create_empty)
 
205
 
241
206
    def get_format_signature(self):
242
207
        """Get a text description of the data encoding in this file.
243
208
        
248
213
    def make_mpdiffs(self, version_ids):
249
214
        """Create multiparent diffs for specified versions."""
250
215
        knit_versions = set()
251
 
        knit_versions.update(version_ids)
252
 
        parent_map = self.get_parent_map(version_ids)
253
216
        for version_id in version_ids:
254
 
            try:
255
 
                knit_versions.update(parent_map[version_id])
256
 
            except KeyError:
257
 
                raise RevisionNotPresent(version_id, self)
258
 
        # We need to filter out ghosts, because we can't diff against them.
259
 
        knit_versions = set(self.get_parent_map(knit_versions).keys())
 
217
            knit_versions.add(version_id)
 
218
            knit_versions.update(self.get_parents(version_id))
260
219
        lines = dict(zip(knit_versions,
261
220
            self._get_lf_split_line_list(knit_versions)))
262
221
        diffs = []
263
222
        for version_id in version_ids:
264
223
            target = lines[version_id]
265
 
            try:
266
 
                parents = [lines[p] for p in parent_map[version_id] if p in
267
 
                    knit_versions]
268
 
            except KeyError:
269
 
                raise RevisionNotPresent(version_id, self)
 
224
            parents = [lines[p] for p in self.get_parents(version_id)]
270
225
            if len(parents) > 0:
271
226
                left_parent_blocks = self._extract_blocks(version_id,
272
227
                                                          parents[0], target)
296
251
        for version, parent_ids, expected_sha1, mpdiff in records:
297
252
            needed_parents.update(p for p in parent_ids
298
253
                                  if not mpvf.has_version(p))
299
 
        present_parents = set(self.get_parent_map(needed_parents).keys())
300
 
        for parent_id, lines in zip(present_parents,
301
 
                                 self._get_lf_split_line_list(present_parents)):
 
254
        for parent_id, lines in zip(needed_parents,
 
255
                                 self._get_lf_split_line_list(needed_parents)):
302
256
            mpvf.add_version(lines, parent_id, [])
303
257
        for (version, parent_ids, expected_sha1, mpdiff), lines in\
304
258
            zip(records, mpvf.get_line_list(versions)):
307
261
                    mpvf.get_diff(parent_ids[0]).num_lines()))
308
262
            else:
309
263
                left_matching_blocks = None
310
 
            try:
311
 
                _, _, version_text = self.add_lines_with_ghosts(version,
312
 
                    parent_ids, lines, vf_parents,
313
 
                    left_matching_blocks=left_matching_blocks)
314
 
            except NotImplementedError:
315
 
                # The vf can't handle ghosts, so add lines normally, which will
316
 
                # (reasonably) fail if there are ghosts in the data.
317
 
                _, _, version_text = self.add_lines(version,
318
 
                    parent_ids, lines, vf_parents,
319
 
                    left_matching_blocks=left_matching_blocks)
 
264
            _, _, version_text = self.add_lines(version, parent_ids, lines,
 
265
                vf_parents, left_matching_blocks=left_matching_blocks)
320
266
            vf_parents[version] = version_text
321
267
        for (version, parent_ids, expected_sha1, mpdiff), sha1 in\
322
268
             zip(records, self.get_sha1s(versions)):
323
269
            if expected_sha1 != sha1:
324
270
                raise errors.VersionedFileInvalidChecksum(version)
325
271
 
 
272
    def get_sha1(self, version_id):
 
273
        """Get the stored sha1 sum for the given revision.
 
274
        
 
275
        :param version_id: The name of the version to lookup
 
276
        """
 
277
        raise NotImplementedError(self.get_sha1)
 
278
 
326
279
    def get_sha1s(self, version_ids):
327
280
        """Get the stored sha1 sums for the given revisions.
328
281
 
331
284
        """
332
285
        raise NotImplementedError(self.get_sha1s)
333
286
 
 
287
    def get_suffixes(self):
 
288
        """Return the file suffixes associated with this versioned file."""
 
289
        raise NotImplementedError(self.get_suffixes)
 
290
    
334
291
    def get_text(self, version_id):
335
292
        """Return version contents as a text string.
336
293
 
383
340
        but are not explicitly marked.
384
341
        """
385
342
        raise NotImplementedError(self.get_ancestry_with_ghosts)
386
 
    
387
 
    def get_parent_map(self, version_ids):
388
 
        """Get a map of the parents of version_ids.
389
 
 
390
 
        :param version_ids: The version ids to look up parents for.
391
 
        :return: A mapping from version id to parents.
392
 
        """
393
 
        raise NotImplementedError(self.get_parent_map)
 
343
        
 
344
    def get_graph(self, version_ids=None):
 
345
        """Return a graph from the versioned file. 
 
346
        
 
347
        Ghosts are not listed or referenced in the graph.
 
348
        :param version_ids: Versions to select.
 
349
                            None means retrieve all versions.
 
350
        """
 
351
        if version_ids is None:
 
352
            return dict(self.iter_parents(self.versions()))
 
353
        result = {}
 
354
        pending = set(version_ids)
 
355
        while pending:
 
356
            this_iteration = pending
 
357
            pending = set()
 
358
            for version, parents in self.iter_parents(this_iteration):
 
359
                result[version] = parents
 
360
                for parent in parents:
 
361
                    if parent in result:
 
362
                        continue
 
363
                    pending.add(parent)
 
364
        return result
 
365
 
 
366
    def get_graph_with_ghosts(self):
 
367
        """Return a graph for the entire versioned file.
 
368
        
 
369
        Ghosts are referenced in parents list but are not
 
370
        explicitly listed.
 
371
        """
 
372
        raise NotImplementedError(self.get_graph_with_ghosts)
 
373
 
 
374
    def get_parents(self, version_id):
 
375
        """Return version names for parents of a version.
 
376
 
 
377
        Must raise RevisionNotPresent if version is not present in
 
378
        file history.
 
379
        """
 
380
        raise NotImplementedError(self.get_parents)
394
381
 
395
382
    def get_parents_with_ghosts(self, version_id):
396
383
        """Return version names for parents of version_id.
401
388
        Ghosts that are known about will be included in the parent list,
402
389
        but are not explicitly marked.
403
390
        """
404
 
        try:
405
 
            return list(self.get_parent_map([version_id])[version_id])
406
 
        except KeyError:
407
 
            raise errors.RevisionNotPresent(version_id, self)
 
391
        raise NotImplementedError(self.get_parents_with_ghosts)
 
392
 
 
393
    def annotate_iter(self, version_id):
 
394
        """Yield list of (version-id, line) pairs for the specified
 
395
        version.
 
396
 
 
397
        Must raise RevisionNotPresent if the given version is
 
398
        not present in file history.
 
399
        """
 
400
        raise NotImplementedError(self.annotate_iter)
408
401
 
409
402
    def annotate(self, version_id):
410
 
        """Return a list of (version-id, line) tuples for version_id.
411
 
 
412
 
        :raise RevisionNotPresent: If the given version is
413
 
        not present in file history.
414
 
        """
415
 
        raise NotImplementedError(self.annotate)
416
 
 
417
 
    @deprecated_method(one_five)
 
403
        return list(self.annotate_iter(version_id))
 
404
 
418
405
    def join(self, other, pb=None, msg=None, version_ids=None,
419
406
             ignore_missing=False):
420
407
        """Integrate versions from other into this versioned file.
433
420
            version_ids,
434
421
            ignore_missing)
435
422
 
436
 
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
 
423
    def iter_lines_added_or_present_in_versions(self, version_ids=None, 
437
424
                                                pb=None):
438
425
        """Iterate over the lines in the versioned file from version_ids.
439
426
 
440
 
        This may return lines from other versions. Each item the returned
441
 
        iterator yields is a tuple of a line and a text version that that line
442
 
        is present in (not introduced in).
443
 
 
444
 
        Ordering of results is in whatever order is most suitable for the
445
 
        underlying storage format.
 
427
        This may return lines from other versions, and does not return the
 
428
        specific version marker at this point. The api may be changed
 
429
        during development to include the version that the versioned file
 
430
        thinks is relevant, but given that such hints are just guesses,
 
431
        its better not to have it if we don't need it.
446
432
 
447
433
        If a progress bar is supplied, it may be used to indicate progress.
448
434
        The caller is responsible for cleaning up progress bars (because this
450
436
 
451
437
        NOTES: Lines are normalised: they will all have \n terminators.
452
438
               Lines are returned in arbitrary order.
453
 
 
454
 
        :return: An iterator over (line, version_id).
455
439
        """
456
440
        raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
457
441
 
 
442
    def iter_parents(self, version_ids):
 
443
        """Iterate through the parents for many version ids.
 
444
 
 
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.
 
450
        """
 
451
        for version_id in version_ids:
 
452
            try:
 
453
                yield version_id, tuple(self.get_parents(version_id))
 
454
            except errors.RevisionNotPresent:
 
455
                pass
 
456
 
 
457
    def transaction_finished(self):
 
458
        """The transaction that this file was opened in has finished.
 
459
 
 
460
        This records self.finished = True and should cause all mutating
 
461
        operations to error.
 
462
        """
 
463
        self.finished = True
 
464
 
458
465
    def plan_merge(self, ver_a, ver_b):
459
466
        """Return pseudo-annotation indicating how the two versions merge.
460
467
 
482
489
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
483
490
 
484
491
 
485
 
class RecordingVersionedFileDecorator(object):
486
 
    """A minimal versioned file that records calls made on it.
487
 
    
488
 
    Only enough methods have been added to support tests using it to date.
489
 
 
490
 
    :ivar calls: A list of the calls made; can be reset at any time by
491
 
        assigning [] to it.
492
 
    """
493
 
 
494
 
    def __init__(self, backing_vf):
495
 
        """Create a RecordingVersionedFileDecorator decorating backing_vf.
496
 
        
497
 
        :param backing_vf: The versioned file to answer all methods.
498
 
        """
499
 
        self._backing_vf = backing_vf
500
 
        self.calls = []
501
 
 
502
 
    def get_lines(self, version_ids):
503
 
        self.calls.append(("get_lines", version_ids))
504
 
        return self._backing_vf.get_lines(version_ids)
505
 
 
506
 
 
507
 
class _PlanMergeVersionedFile(object):
508
 
    """A VersionedFile for uncommitted and committed texts.
509
 
 
510
 
    It is intended to allow merges to be planned with working tree texts.
511
 
    It implements only the small part of the VersionedFile interface used by
512
 
    PlanMerge.  It falls back to multiple versionedfiles for data not stored in
513
 
    _PlanMergeVersionedFile itself.
514
 
    """
515
 
 
516
 
    def __init__(self, file_id, fallback_versionedfiles=None):
517
 
        """Constuctor
518
 
 
519
 
        :param file_id: Used when raising exceptions.
520
 
        :param fallback_versionedfiles: If supplied, the set of fallbacks to
521
 
            use.  Otherwise, _PlanMergeVersionedFile.fallback_versionedfiles
522
 
            can be appended to later.
523
 
        """
524
 
        self._file_id = file_id
525
 
        if fallback_versionedfiles is None:
526
 
            self.fallback_versionedfiles = []
527
 
        else:
528
 
            self.fallback_versionedfiles = fallback_versionedfiles
529
 
        self._parents = {}
530
 
        self._lines = {}
531
 
 
532
 
    def plan_merge(self, ver_a, ver_b, base=None):
533
 
        """See VersionedFile.plan_merge"""
534
 
        from bzrlib.merge import _PlanMerge
535
 
        if base is None:
536
 
            return _PlanMerge(ver_a, ver_b, self).plan_merge()
537
 
        old_plan = list(_PlanMerge(ver_a, base, self).plan_merge())
538
 
        new_plan = list(_PlanMerge(ver_a, ver_b, self).plan_merge())
539
 
        return _PlanMerge._subtract_plans(old_plan, new_plan)
540
 
 
541
 
    def plan_lca_merge(self, ver_a, ver_b, base=None):
542
 
        from bzrlib.merge import _PlanLCAMerge
543
 
        graph = self._get_graph()
544
 
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, graph).plan_merge()
545
 
        if base is None:
546
 
            return new_plan
547
 
        old_plan = _PlanLCAMerge(ver_a, base, self, graph).plan_merge()
548
 
        return _PlanLCAMerge._subtract_plans(list(old_plan), list(new_plan))
549
 
 
550
 
    def add_lines(self, version_id, parents, lines):
551
 
        """See VersionedFile.add_lines
552
 
 
553
 
        Lines are added locally, not fallback versionedfiles.  Also, ghosts are
554
 
        permitted.  Only reserved ids are permitted.
555
 
        """
556
 
        if not revision.is_reserved_id(version_id):
557
 
            raise ValueError('Only reserved ids may be used')
558
 
        if parents is None:
559
 
            raise ValueError('Parents may not be None')
560
 
        if lines is None:
561
 
            raise ValueError('Lines may not be None')
562
 
        self._parents[version_id] = tuple(parents)
563
 
        self._lines[version_id] = lines
564
 
 
565
 
    def get_lines(self, version_id):
566
 
        """See VersionedFile.get_ancestry"""
567
 
        lines = self._lines.get(version_id)
568
 
        if lines is not None:
569
 
            return lines
570
 
        for versionedfile in self.fallback_versionedfiles:
571
 
            try:
572
 
                return versionedfile.get_lines(version_id)
573
 
            except errors.RevisionNotPresent:
574
 
                continue
575
 
        else:
576
 
            raise errors.RevisionNotPresent(version_id, self._file_id)
577
 
 
578
 
    def get_ancestry(self, version_id, topo_sorted=False):
579
 
        """See VersionedFile.get_ancestry.
580
 
 
581
 
        Note that this implementation assumes that if a VersionedFile can
582
 
        answer get_ancestry at all, it can give an authoritative answer.  In
583
 
        fact, ghosts can invalidate this assumption.  But it's good enough
584
 
        99% of the time, and far cheaper/simpler.
585
 
 
586
 
        Also note that the results of this version are never topologically
587
 
        sorted, and are a set.
588
 
        """
589
 
        if topo_sorted:
590
 
            raise ValueError('This implementation does not provide sorting')
591
 
        parents = self._parents.get(version_id)
592
 
        if parents is None:
593
 
            for vf in self.fallback_versionedfiles:
594
 
                try:
595
 
                    return vf.get_ancestry(version_id, topo_sorted=False)
596
 
                except errors.RevisionNotPresent:
597
 
                    continue
598
 
            else:
599
 
                raise errors.RevisionNotPresent(version_id, self._file_id)
600
 
        ancestry = set([version_id])
601
 
        for parent in parents:
602
 
            ancestry.update(self.get_ancestry(parent, topo_sorted=False))
603
 
        return ancestry
604
 
 
605
 
    def get_parent_map(self, version_ids):
606
 
        """See VersionedFile.get_parent_map"""
607
 
        result = {}
608
 
        pending = set(version_ids)
609
 
        for key in version_ids:
610
 
            try:
611
 
                result[key] = self._parents[key]
612
 
            except KeyError:
613
 
                pass
614
 
        pending = pending - set(result.keys())
615
 
        for versionedfile in self.fallback_versionedfiles:
616
 
            parents = versionedfile.get_parent_map(pending)
617
 
            result.update(parents)
618
 
            pending = pending - set(parents.keys())
619
 
            if not pending:
620
 
                return result
621
 
        return result
622
 
 
623
 
    def _get_graph(self):
624
 
        from bzrlib.graph import (
625
 
            DictParentsProvider,
626
 
            Graph,
627
 
            _StackedParentsProvider,
628
 
            )
629
 
        from bzrlib.repofmt.knitrepo import _KnitParentsProvider
630
 
        parent_providers = [DictParentsProvider(self._parents)]
631
 
        for vf in self.fallback_versionedfiles:
632
 
            parent_providers.append(_KnitParentsProvider(vf))
633
 
        return Graph(_StackedParentsProvider(parent_providers))
634
 
 
635
 
 
636
492
class PlanWeaveMerge(TextMerge):
637
493
    """Weave merge that takes a plan as its input.
638
494
    
690
546
            elif state == 'new-b':
691
547
                ch_b = True
692
548
                lines_b.append(line)
693
 
            elif state == 'conflicted-a':
694
 
                ch_b = ch_a = True
695
 
                lines_a.append(line)
696
 
            elif state == 'conflicted-b':
697
 
                ch_b = ch_a = True
698
 
                lines_b.append(line)
699
549
            else:
700
 
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
701
 
                        'killed-base', 'killed-both'):
702
 
                    raise AssertionError(state)
 
550
                assert state in ('irrelevant', 'ghost-a', 'ghost-b', 
 
551
                                 'killed-base', 'killed-both'), state
703
552
        for struct in outstanding_struct():
704
553
            yield struct
705
554
 
738
587
        are not present in the other file's history unless ignore_missing is 
739
588
        supplied in which case they are silently skipped.
740
589
        """
741
 
        target = self.target
 
590
        # the default join: 
 
591
        # - if the target is empty, just add all the versions from 
 
592
        #   source to target, otherwise:
 
593
        # - make a temporary versioned file of type target
 
594
        # - insert the source content into it one at a time
 
595
        # - join them
 
596
        if not self.target.versions():
 
597
            target = self.target
 
598
        else:
 
599
            # Make a new target-format versioned file. 
 
600
            temp_source = self.target.create_empty("temp", MemoryTransport())
 
601
            target = temp_source
742
602
        version_ids = self._get_source_version_ids(version_ids, ignore_missing)
743
 
        graph = Graph(self.source)
744
 
        search = graph._make_breadth_first_searcher(version_ids)
745
 
        transitive_ids = set()
746
 
        map(transitive_ids.update, list(search))
747
 
        parent_map = self.source.get_parent_map(transitive_ids)
748
 
        order = tsort.topo_sort(parent_map.items())
 
603
        graph = self.source.get_graph(version_ids)
 
604
        order = tsort.topo_sort(graph.items())
749
605
        pb = ui.ui_factory.nested_progress_bar()
750
606
        parent_texts = {}
751
607
        try:
765
621
            total = len(order)
766
622
            for index, version in enumerate(order):
767
623
                pb.update('Converting versioned data', index, total)
768
 
                if version in target:
769
 
                    continue
770
624
                _, _, parent_text = target.add_lines(version,
771
 
                                               parent_map[version],
 
625
                                               self.source.get_parents(version),
772
626
                                               self.source.get_lines(version),
773
627
                                               parent_texts=parent_texts)
774
628
                parent_texts[version] = parent_text
775
 
            return total
 
629
            
 
630
            # this should hit the native code path for target
 
631
            if target is not self.target:
 
632
                return self.target.join(temp_source,
 
633
                                        pb,
 
634
                                        msg,
 
635
                                        version_ids,
 
636
                                        ignore_missing)
 
637
            else:
 
638
                return total
776
639
        finally:
777
640
            pb.finished()
778
641