~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

add ghost aware apis to knits.

Show diffs side-by-side

added added

removed removed

Lines of Context:
284
284
                                current_values[3],
285
285
                                new_parents)
286
286
 
 
287
    def get_graph_with_ghosts(self):
 
288
        """See VersionedFile.get_graph_with_ghosts()."""
 
289
        graph_items = self._index.get_graph()
 
290
        return dict(graph_items)
 
291
 
287
292
    @staticmethod
288
293
    def get_suffixes():
289
294
        """See VersionedFile.get_suffixes()."""
290
295
        return [DATA_SUFFIX, INDEX_SUFFIX]
291
296
 
 
297
    def has_ghost(self, version_id):
 
298
        """True if there is a ghost reference in the file to version_id."""
 
299
        # maybe we have it
 
300
        if self.has_version(version_id):
 
301
            return False
 
302
        # optimisable if needed by memoising the _ghosts set.
 
303
        items = self._index.get_graph()
 
304
        for node, parents in items:
 
305
            for parent in parents:
 
306
                if parent not in self._index._cache:
 
307
                    if parent == version_id:
 
308
                        return True
 
309
        return False
 
310
 
292
311
    def versions(self):
293
312
        """See VersionedFile.versions."""
294
313
        return self._index.get_versions()
405
424
        if version_ids:
406
425
            raise RevisionNotPresent(list(version_ids)[0], self.filename)
407
426
 
 
427
    def add_lines_with_ghosts(self, version_id, parents, lines):
 
428
        """See VersionedFile.add_lines_with_ghosts()."""
 
429
        self._check_add(version_id, lines)
 
430
        return self._add(version_id, lines[:], parents, self.delta)
 
431
 
408
432
    def add_lines(self, version_id, parents, lines):
409
433
        """See VersionedFile.add_lines."""
 
434
        self._check_add(version_id, lines)
 
435
        self._check_versions_present(parents)
 
436
        return self._add(version_id, lines[:], parents, self.delta)
 
437
 
 
438
    def _check_add(self, version_id, lines):
 
439
        """check that version_id and lines are safe to add."""
410
440
        assert self.writable, "knit is not opened for write"
411
441
        ### FIXME escape. RBC 20060228
412
442
        if contains_whitespace(version_id):
418
448
            for l in lines:
419
449
                assert '\n' not in l[:-1]
420
450
 
421
 
        self._check_versions_present(parents)
422
 
        return self._add(version_id, lines[:], parents, self.delta)
423
 
 
424
451
    def _add(self, version_id, lines, parents, delta):
425
452
        """Add a set of lines on top of version specified by parents.
426
453
 
427
454
        If delta is true, compress the text as a line-delta against
428
455
        the first parent.
 
456
 
 
457
        Any versions not present will be converted into ghosts.
429
458
        """
430
 
        if delta and not parents:
 
459
        ghosts = []
 
460
        for parent in parents:
 
461
            if not self.has_version(parent):
 
462
                ghosts.append(parent)
 
463
 
 
464
        if delta and not len(parents)-len(ghosts):
431
465
            delta = False
432
466
 
433
467
        digest = sha_strings(lines)
438
472
                lines[-1] = lines[-1] + '\n'
439
473
 
440
474
        lines = self.factory.make(lines, len(self._index))
441
 
        if self.factory.annotated and len(parents) > 0:
 
475
        if self.factory.annotated and len(parents)-len(ghosts) > 0:
442
476
            # Merge annotations from parent texts if so is needed.
443
477
            self._merge_annotations(lines, parents)
444
478
 
445
 
        if parents and delta:
 
479
        if len(parents)-len(ghosts) and delta:
446
480
            # To speed the extract of texts the delta chain is limited
447
481
            # to a fixed number of deltas.  This should minimize both
448
482
            # I/O and the time spend applying deltas.
449
483
            count = 0
450
 
            delta_parents = parents
 
484
            delta_parents = [parent for parent in parents if not parent in ghosts]
 
485
            first_parent = delta_parents[0]
451
486
            while count < 25:
452
487
                parent = delta_parents[0]
453
488
                method = self._index.get_method(parent)
460
495
 
461
496
        if delta:
462
497
            options.append('line-delta')
463
 
            content = self._get_content(parents[0])
 
498
            content = self._get_content(first_parent)
464
499
            delta_hunks = content.line_delta(lines)
465
500
            store_lines = self.factory.lower_line_delta(delta_hunks)
466
501
        else:
527
562
        self._check_versions_present([version_id])
528
563
        return list(self._index.get_parents(version_id))
529
564
 
 
565
    def get_parents_with_ghosts(self, version_id):
 
566
        """See VersionedFile.get_parents."""
 
567
        self._check_versions_present([version_id])
 
568
        return list(self._index.get_parents_with_ghosts(version_id))
 
569
 
530
570
    def get_ancestry(self, versions):
531
571
        """See VersionedFile.get_ancestry."""
532
572
        if isinstance(versions, basestring):
536
576
        self._check_versions_present(versions)
537
577
        return self._index.get_ancestry(versions)
538
578
 
 
579
    def get_ancestry_with_ghosts(self, versions):
 
580
        """See VersionedFile.get_ancestry_with_ghosts."""
 
581
        if isinstance(versions, basestring):
 
582
            versions = [versions]
 
583
        if not versions:
 
584
            return []
 
585
        self._check_versions_present(versions)
 
586
        return self._index.get_ancestry_with_ghosts(versions)
 
587
 
539
588
    def _reannotate_line_delta(self, other, lines, new_version_id,
540
589
                               new_version_idx):
541
590
        """Re-annotate line-delta and return new delta."""
665
714
            fp = self._transport.get(self._filename)
666
715
            self.check_header(fp)
667
716
            for rec in self._iter_index(fp):
 
717
                parents = self._parse_parents(rec[4:])
668
718
                self._cache_version(rec[0], rec[1].split(','), int(rec[2]), int(rec[3]),
669
 
                    [self._history[int(i)] for i in rec[4:]])
 
719
                    parents)
670
720
        except NoSuchFile, e:
671
721
            if mode != 'w' or not create:
672
722
                raise
673
723
            self.write_header()
674
724
 
 
725
    def _parse_parents(self, compressed_parents):
 
726
        """convert a list of string parent values into version ids.
 
727
 
 
728
        ints are looked up in the index.
 
729
        .FOO values are ghosts and converted in to FOO.
 
730
        """
 
731
        result = []
 
732
        for value in compressed_parents:
 
733
            if value.startswith('.'):
 
734
                result.append(value[1:])
 
735
            else:
 
736
                result.append(self._history[int(value)])
 
737
        return result
 
738
 
675
739
    def get_graph(self):
676
740
        graph = []
677
741
        for version_id, index in self._cache.iteritems():
685
749
        pending = set(versions)
686
750
        while len(pending):
687
751
            version = pending.pop()
 
752
#            try:
688
753
            parents = self._cache[version][4]
 
754
#            except KeyError:
 
755
#                # ghost, elide it.
 
756
#                pass
 
757
#            else:
 
758
            # got the parents ok
 
759
            # trim ghosts
 
760
            parents = [parent for parent in parents if parent in self._cache]
689
761
            for parent in parents:
 
762
                # if not completed and not a ghost
690
763
                if parent not in graph:
691
764
                    pending.add(parent)
692
765
            graph[version] = parents
693
766
        return topo_sort(graph.items())
694
767
 
 
768
    def get_ancestry_with_ghosts(self, versions):
 
769
        """See VersionedFile.get_ancestry_with_ghosts."""
 
770
        # get a graph of all the mentioned versions:
 
771
        graph = {}
 
772
        pending = set(versions)
 
773
        while len(pending):
 
774
            version = pending.pop()
 
775
            try:
 
776
                parents = self._cache[version][4]
 
777
            except KeyError:
 
778
                # ghost, fake it
 
779
                graph[version] = []
 
780
                pass
 
781
            else:
 
782
                # got the parents ok
 
783
                for parent in parents:
 
784
                    if parent not in graph:
 
785
                        pending.add(parent)
 
786
                graph[version] = parents
 
787
        return topo_sort(graph.items())
 
788
 
695
789
    def num_versions(self):
696
790
        return len(self._history)
697
791
 
707
801
        assert version_id in self._cache
708
802
        return self._history.index(version_id)
709
803
 
 
804
    def _version_list_to_index(self, versions):
 
805
        result_list = []
 
806
        for version in versions:
 
807
            if version in self._cache:
 
808
                result_list.append(str(self._history.index(version)))
 
809
            else:
 
810
                result_list.append('.' + version)
 
811
        return ' '.join(result_list)
 
812
 
710
813
    def add_version(self, version_id, options, pos, size, parents):
711
814
        """Add a version record to the index."""
712
815
        self._cache_version(version_id, options, pos, size, parents)
715
818
                                        ','.join(options),
716
819
                                        pos,
717
820
                                        size,
718
 
                                        ' '.join([str(self.lookup(vid)) for 
719
 
                                                  vid in parents]))
 
821
                                        self._version_list_to_index(parents))
720
822
        self._transport.append(self._filename, StringIO(content))
721
823
 
722
824
    def has_version(self, version_id):
741
843
        return self._cache[version_id][1]
742
844
 
743
845
    def get_parents(self, version_id):
744
 
        """Return parents of specified version."""
745
 
        return self._cache[version_id][4]
 
846
        """Return parents of specified version ignoring ghosts."""
 
847
        return [parent for parent in self._cache[version_id][4] 
 
848
                if parent in self._cache]
 
849
 
 
850
    def get_parents_with_ghosts(self, version_id):
 
851
        """Return parents of specified version wth ghosts."""
 
852
        return self._cache[version_id][4] 
746
853
 
747
854
    def check_versions_present(self, version_ids):
748
855
        """Check that all specified versions are present."""
899
1006
        mismatched_versions = set()
900
1007
        for version in cross_check_versions:
901
1008
            # scan to include needed parents.
902
 
            n1 = set(self.target.get_parents(version))
903
 
            n2 = set(self.source.get_parents(version))
 
1009
            n1 = set(self.target.get_parents_with_ghosts(version))
 
1010
            n2 = set(self.source.get_parents_with_ghosts(version))
904
1011
            if n1 != n2:
905
1012
                # FIXME TEST this check for cycles being introduced works
906
1013
                # the logic is we have a cycle if in our graph we are an
920
1027
 
921
1028
        if not needed_versions and not cross_check_versions:
922
1029
            return 0
923
 
        full_list = topo_sort(self.source._index.get_graph())
 
1030
        full_list = topo_sort(self.source.get_graph())
924
1031
 
925
1032
        version_list = [i for i in full_list if (not self.target.has_version(i)
926
1033
                        and i in needed_versions)]
934
1041
        for version_id, lines, digest \
935
1042
                in self.source._data.read_records_iter(records):
936
1043
            options = self.source._index.get_options(version_id)
937
 
            parents = self.source._index.get_parents(version_id)
 
1044
            parents = self.source._index.get_parents_with_ghosts(version_id)
938
1045
            
939
1046
            for parent in parents:
940
 
                assert self.target.has_version(parent)
 
1047
                # if source has the parent, we must hav grabbed it first.
 
1048
                assert (self.target.has_version(parent) or not
 
1049
                        self.source.has_version(parent))
941
1050
 
942
1051
            if self.target.factory.annotated:
943
1052
                # FIXME jrydberg: it should be possible to skip
959
1068
            self.target._index.add_version(version_id, options, pos, size, parents)
960
1069
 
961
1070
        for version in mismatched_versions:
962
 
            n1 = set(self.target.get_parents(version))
963
 
            n2 = set(self.source.get_parents(version))
 
1071
            n1 = set(self.target.get_parents_with_ghosts(version))
 
1072
            n2 = set(self.source.get_parents_with_ghosts(version))
964
1073
            # write a combined record to our history preserving the current 
965
1074
            # parents as first in the list
966
 
            new_parents = self.target.get_parents(version) + list(n2.difference(n1))
 
1075
            new_parents = self.target.get_parents_with_ghosts(version) + list(n2.difference(n1))
967
1076
            self.target.fix_parents(version, new_parents)
968
1077
        pb.clear()
969
1078
        return count