/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tree.py

  • Committer: Jelmer Vernooij
  • Date: 2018-03-24 10:24:48 UTC
  • mfrom: (6910 work)
  • mto: This revision was merged to the branch mainline in revision 6913.
  • Revision ID: jelmer@jelmer.uk-20180324102448-132p8l8t5ogdzhhu
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
108
108
    trees or versioned trees.
109
109
    """
110
110
 
 
111
    def supports_rename_tracking(self):
 
112
        """Whether this tree supports rename tracking.
 
113
 
 
114
        This defaults to True, but some implementations may want to override
 
115
        it.
 
116
        """
 
117
        return True
 
118
 
111
119
    def has_versioned_directories(self):
112
120
        """Whether this tree can contain explicitly versioned directories.
113
121
 
205
213
        """
206
214
        raise NotImplementedError(self.id2path)
207
215
 
208
 
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
 
216
    def iter_entries_by_dir(self, specific_files=None, yield_parents=False):
209
217
        """Walk the tree in 'by_dir' order.
210
218
 
211
219
        This will yield each entry in the tree as a (path, entry) tuple.
231
239
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
232
240
 
233
241
        :param yield_parents: If True, yield the parents from the root leading
234
 
            down to specific_file_ids that have been requested. This has no
235
 
            impact if specific_file_ids is None.
 
242
            down to specific_files that have been requested. This has no
 
243
            impact if specific_files is None.
236
244
        """
237
245
        raise NotImplementedError(self.iter_entries_by_dir)
238
246
 
309
317
        """
310
318
        raise NotImplementedError(self._comparison_data)
311
319
 
312
 
    def _file_size(self, entry, stat_value):
313
 
        raise NotImplementedError(self._file_size)
314
 
 
315
320
    def get_file(self, path, file_id=None):
316
321
        """Return a file object for the file file_id in the tree.
317
322
 
482
487
            except errors.NoSuchRevisionInTree:
483
488
                yield self.repository.revision_tree(revision_id)
484
489
 
485
 
    def _check_retrieved(self, ie, f):
486
 
        if not __debug__:
487
 
            return
488
 
        fp = osutils.fingerprint_file(f)
489
 
        f.seek(0)
490
 
 
491
 
        if ie.text_size is not None:
492
 
            if ie.text_size != fp['size']:
493
 
                raise errors.BzrError(
494
 
                        "mismatched size for file %r in %r" %
495
 
                        (ie.file_id, self._store),
496
 
                        ["inventory expects %d bytes" % ie.text_size,
497
 
                         "file is actually %d bytes" % fp['size'],
498
 
                         "store is probably damaged/corrupt"])
499
 
 
500
 
        if ie.text_sha1 != fp['sha1']:
501
 
            raise errors.BzrError("wrong SHA-1 for file %r in %r" %
502
 
                    (ie.file_id, self._store),
503
 
                    ["inventory expects %s" % ie.text_sha1,
504
 
                     "file is actually %s" % fp['sha1'],
505
 
                     "store is probably damaged/corrupt"])
506
 
 
507
490
    def path2id(self, path):
508
491
        """Return the id for path in this tree."""
509
492
        raise NotImplementedError(self.path2id)
516
499
        """
517
500
        return self.path2id(path) is not None
518
501
 
519
 
    def paths2ids(self, paths, trees=[], require_versioned=True):
520
 
        """Return all the ids that can be reached by walking from paths.
521
 
 
522
 
        Each path is looked up in this tree and any extras provided in
523
 
        trees, and this is repeated recursively: the children in an extra tree
524
 
        of a directory that has been renamed under a provided path in this tree
525
 
        are all returned, even if none exist under a provided path in this
526
 
        tree, and vice versa.
527
 
 
528
 
        :param paths: An iterable of paths to start converting to ids from.
529
 
            Alternatively, if paths is None, no ids should be calculated and None
530
 
            will be returned. This is offered to make calling the api unconditional
531
 
            for code that *might* take a list of files.
532
 
        :param trees: Additional trees to consider.
533
 
        :param require_versioned: If False, do not raise NotVersionedError if
534
 
            an element of paths is not versioned in this tree and all of trees.
535
 
        """
536
 
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
537
 
 
538
 
    def iter_children(self, file_id):
539
 
        """Iterate over the file ids of the children of an entry.
540
 
 
541
 
        :param file_id: File id of the entry
542
 
        :return: Iterator over child file ids.
543
 
        """
544
 
        raise NotImplementedError(self.iter_children)
 
502
    def find_related_paths_across_trees(self, paths, trees=[],
 
503
            require_versioned=True):
 
504
        """Find related paths in tree corresponding to specified filenames in any
 
505
        of `lookup_trees`.
 
506
 
 
507
        All matches in all trees will be used, and all children of matched
 
508
        directories will be used.
 
509
 
 
510
        :param paths: The filenames to find related paths for (if None, returns
 
511
            None)
 
512
        :param trees: The trees to find file_ids within
 
513
        :param require_versioned: if true, all specified filenames must occur in
 
514
            at least one tree.
 
515
        :return: a set of paths for the specified filenames and their children
 
516
            in `tree`
 
517
        """
 
518
        raise NotImplementedError(self.find_related_paths_across_trees)
545
519
 
546
520
    def lock_read(self):
547
521
        """Lock this tree for multiple read only operations.
618
592
    def supports_content_filtering(self):
619
593
        return False
620
594
 
621
 
    def _content_filter_stack(self, path=None, file_id=None):
 
595
    def _content_filter_stack(self, path=None):
622
596
        """The stack of content filters for a path if filtering is supported.
623
597
 
624
598
        Readers will be applied in first-to-last order.
627
601
 
628
602
        :param path: path relative to the root of the tree
629
603
            or None if unknown
630
 
        :param file_id: file_id or None if unknown
631
604
        :return: the list of filters - [] if there are none
632
605
        """
633
606
        filter_pref_names = filters._get_registered_names()
634
607
        if len(filter_pref_names) == 0:
635
608
            return []
636
 
        if path is None:
637
 
            path = self.id2path(file_id)
638
609
        prefs = next(self.iter_search_rules([path], filter_pref_names))
639
610
        stk = filters._get_filter_stack_for(prefs)
640
611
        if 'filters' in debug.debug_flags:
651
622
        """
652
623
        if self.supports_content_filtering():
653
624
            return lambda path, file_id: \
654
 
                    self._content_filter_stack(path, file_id)
 
625
                    self._content_filter_stack(path)
655
626
        else:
656
627
            return None
657
628
 
683
654
        return searcher
684
655
 
685
656
 
686
 
def find_ids_across_trees(filenames, trees, require_versioned=True):
687
 
    """Find the ids corresponding to specified filenames.
688
 
 
689
 
    All matches in all trees will be used, and all children of matched
690
 
    directories will be used.
691
 
 
692
 
    :param filenames: The filenames to find file_ids for (if None, returns
693
 
        None)
694
 
    :param trees: The trees to find file_ids within
695
 
    :param require_versioned: if true, all specified filenames must occur in
696
 
        at least one tree.
697
 
    :return: a set of file ids for the specified filenames and their children.
698
 
    """
699
 
    if not filenames:
700
 
        return None
701
 
    specified_path_ids = _find_ids_across_trees(filenames, trees,
702
 
        require_versioned)
703
 
    return _find_children_across_trees(specified_path_ids, trees)
704
 
 
705
 
 
706
 
def _find_ids_across_trees(filenames, trees, require_versioned):
707
 
    """Find the ids corresponding to specified filenames.
708
 
 
709
 
    All matches in all trees will be used, but subdirectories are not scanned.
710
 
 
711
 
    :param filenames: The filenames to find file_ids for
712
 
    :param trees: The trees to find file_ids within
713
 
    :param require_versioned: if true, all specified filenames must occur in
714
 
        at least one tree.
715
 
    :return: a set of file ids for the specified filenames
716
 
    """
717
 
    not_versioned = []
718
 
    interesting_ids = set()
719
 
    for tree_path in filenames:
720
 
        not_found = True
721
 
        for tree in trees:
722
 
            file_id = tree.path2id(tree_path)
723
 
            if file_id is not None:
724
 
                interesting_ids.add(file_id)
725
 
                not_found = False
726
 
        if not_found:
727
 
            not_versioned.append(tree_path)
728
 
    if len(not_versioned) > 0 and require_versioned:
729
 
        raise errors.PathsNotVersionedError(not_versioned)
730
 
    return interesting_ids
731
 
 
732
 
 
733
 
def _find_children_across_trees(specified_ids, trees):
734
 
    """Return a set including specified ids and their children.
735
 
 
736
 
    All matches in all trees will be used.
737
 
 
738
 
    :param trees: The trees to find file_ids within
739
 
    :return: a set containing all specified ids and their children
740
 
    """
741
 
    interesting_ids = set(specified_ids)
742
 
    pending = interesting_ids
743
 
    # now handle children of interesting ids
744
 
    # we loop so that we handle all children of each id in both trees
745
 
    while len(pending) > 0:
746
 
        new_pending = set()
747
 
        for file_id in pending:
748
 
            for tree in trees:
749
 
                if not tree.has_or_had_id(file_id):
750
 
                    continue
751
 
                for child_id in tree.iter_children(file_id):
752
 
                    if child_id not in interesting_ids:
753
 
                        new_pending.add(child_id)
754
 
        interesting_ids.update(new_pending)
755
 
        pending = new_pending
756
 
    return interesting_ids
757
 
 
758
 
 
759
657
class InterTree(InterObject):
760
658
    """This class represents operations taking place between two Trees.
761
659
 
781
679
        # it works for all trees.
782
680
        return True
783
681
 
784
 
    def _changes_from_entries(self, source_entry, target_entry,
785
 
        source_path=None, target_path=None):
 
682
    def _changes_from_entries(self, source_entry, target_entry, source_path,
 
683
                              target_path):
786
684
        """Generate a iter_changes tuple between source_entry and target_entry.
787
685
 
788
686
        :param source_entry: An inventory entry from self.source, or None.
789
687
        :param target_entry: An inventory entry from self.target, or None.
790
 
        :param source_path: The path of source_entry, if known. If not known
791
 
            it will be looked up.
792
 
        :param target_path: The path of target_entry, if known. If not known
793
 
            it will be looked up.
 
688
        :param source_path: The path of source_entry.
 
689
        :param target_path: The path of target_entry.
794
690
        :return: A tuple, item 0 of which is an iter_changes result tuple, and
795
691
            item 1 is True if there are any changes in the result tuple.
796
692
        """
804
700
            source_versioned = True
805
701
            source_name = source_entry.name
806
702
            source_parent = source_entry.parent_id
807
 
            if source_path is None:
808
 
                source_path = self.source.id2path(file_id)
809
703
            source_kind, source_executable, source_stat = \
810
704
                self.source._comparison_data(source_entry, source_path)
811
705
        else:
818
712
            target_versioned = True
819
713
            target_name = target_entry.name
820
714
            target_parent = target_entry.parent_id
821
 
            if target_path is None:
822
 
                target_path = self.target.id2path(file_id)
823
715
            target_kind, target_executable, target_stat = \
824
716
                self.target._comparison_data(target_entry, target_path)
825
717
        else:
834
726
        if source_kind != target_kind:
835
727
            changed_content = True
836
728
        elif source_kind == 'file':
837
 
            if not self.file_content_matches(file_id, file_id, source_path,
838
 
                    target_path, source_stat, target_stat):
 
729
            if not self.file_content_matches(
 
730
                    source_path, target_path,
 
731
                    file_id, file_id, source_stat, target_stat):
839
732
                changed_content = True
840
733
        elif source_kind == 'symlink':
841
734
            if (self.source.get_symlink_target(source_path, file_id) !=
880
773
        if extra_trees is not None:
881
774
            trees = trees + tuple(extra_trees)
882
775
        with self.lock_read():
883
 
            # target is usually the newer tree:
884
 
            specific_file_ids = self.target.paths2ids(specific_files, trees,
885
 
                require_versioned=require_versioned)
886
 
            if specific_files and not specific_file_ids:
887
 
                # All files are unversioned, so just return an empty delta
888
 
                # _compare_trees would think we want a complete delta
889
 
                result = delta.TreeDelta()
890
 
                fake_entry = inventory.InventoryFile('unused', 'unused', 'unused')
891
 
                result.unversioned = [(path, None,
892
 
                    self.target._comparison_data(fake_entry, path)[0]) for path in
893
 
                    specific_files]
894
 
                return result
895
776
            return delta._compare_trees(self.source, self.target, want_unchanged,
896
777
                specific_files, include_root, extra_trees=extra_trees,
897
778
                require_versioned=require_versioned,
933
814
            output. An unversioned file is defined as one with (False, False)
934
815
            for the versioned pair.
935
816
        """
936
 
        lookup_trees = [self.source]
937
 
        if extra_trees:
938
 
             lookup_trees.extend(extra_trees)
 
817
        if not extra_trees:
 
818
             extra_trees = []
 
819
        else:
 
820
             extra_trees = list(extra_trees)
939
821
        # The ids of items we need to examine to insure delta consistency.
940
822
        precise_file_ids = set()
941
823
        changed_file_ids = []
942
824
        if specific_files == []:
943
 
            specific_file_ids = []
 
825
            target_specific_files = []
 
826
            source_specific_files = []
944
827
        else:
945
 
            specific_file_ids = self.target.paths2ids(specific_files,
946
 
                lookup_trees, require_versioned=require_versioned)
 
828
            target_specific_files = self.target.find_related_paths_across_trees(
 
829
                    specific_files, [self.source] + extra_trees,
 
830
                    require_versioned=require_versioned)
 
831
            source_specific_files = self.source.find_related_paths_across_trees(
 
832
                    specific_files, [self.target] + extra_trees,
 
833
                    require_versioned=require_versioned)
947
834
        if specific_files is not None:
948
835
            # reparented or added entries must have their parents included
949
836
            # so that valid deltas can be created. The seen_parents set
963
850
            all_unversioned = collections.deque()
964
851
        to_paths = {}
965
852
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
966
 
            specific_file_ids=specific_file_ids))
 
853
            specific_files=source_specific_files))
967
854
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
968
855
        to_entries_by_dir = list(self.target.iter_entries_by_dir(
969
 
            specific_file_ids=specific_file_ids))
 
856
            specific_files=target_specific_files))
970
857
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
971
858
        entry_count = 0
972
859
        # the unversioned path lookup only occurs on real trees - where there
995
882
            if pb is not None:
996
883
                pb.update('comparing files', entry_count, num_entries)
997
884
            if changes or include_unchanged:
998
 
                if specific_file_ids is not None:
 
885
                if specific_files is not None:
999
886
                    new_parent_id = result[4][1]
1000
887
                    precise_file_ids.add(new_parent_id)
1001
888
                    changed_file_ids.append(result[0])
1026
913
            if file_id in to_paths:
1027
914
                # already returned
1028
915
                continue
1029
 
            if not self.target.has_id(file_id):
1030
 
                # common case - paths we have not emitted are not present in
1031
 
                # target.
1032
 
                to_path = None
1033
 
            else:
1034
 
                to_path = self.target.id2path(file_id)
 
916
            to_path = find_previous_path(self.source, self.target, path)
1035
917
            entry_count += 1
1036
918
            if pb is not None:
1037
919
                pb.update('comparing files', entry_count, num_entries)
1048
930
            yield(file_id, (path, to_path), changed_content, versioned, parent,
1049
931
                  name, kind, executable)
1050
932
        changed_file_ids = set(changed_file_ids)
1051
 
        if specific_file_ids is not None:
 
933
        if specific_files is not None:
1052
934
            for result in self._handle_precise_ids(precise_file_ids,
1053
935
                changed_file_ids):
1054
936
                yield result
1055
937
 
1056
 
    def _get_entry(self, tree, file_id):
 
938
    def _get_entry(self, tree, path):
1057
939
        """Get an inventory entry from a tree, with missing entries as None.
1058
940
 
1059
941
        If the tree raises NotImplementedError on accessing .inventory, then
1063
945
        :param tree: The tree to lookup the entry in.
1064
946
        :param file_id: The file_id to lookup.
1065
947
        """
 
948
        # No inventory available.
1066
949
        try:
1067
 
            inventory = tree.root_inventory
1068
 
        except (AttributeError, NotImplementedError):
1069
 
            # No inventory available.
1070
 
            try:
1071
 
                iterator = tree.iter_entries_by_dir(specific_file_ids=[file_id])
1072
 
                return iterator.next()[1]
1073
 
            except StopIteration:
1074
 
                return None
1075
 
        else:
1076
 
            try:
1077
 
                return inventory[file_id]
1078
 
            except errors.NoSuchId:
1079
 
                return None
 
950
            iterator = tree.iter_entries_by_dir(specific_files=[path])
 
951
            return iterator.next()[1]
 
952
        except StopIteration:
 
953
            return None
1080
954
 
1081
955
    def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
1082
956
        discarded_changes=None):
1124
998
                # Examine file_id
1125
999
                if discarded_changes:
1126
1000
                    result = discarded_changes.get(file_id)
1127
 
                    old_entry = None
 
1001
                    source_entry = None
1128
1002
                else:
1129
1003
                    result = None
1130
1004
                if result is None:
1131
 
                    old_entry = self._get_entry(self.source, file_id)
1132
 
                    new_entry = self._get_entry(self.target, file_id)
 
1005
                    try:
 
1006
                        source_path = self.source.id2path(file_id)
 
1007
                    except errors.NoSuchId:
 
1008
                        source_path = None
 
1009
                        source_entry = None
 
1010
                    else:
 
1011
                        source_entry = self._get_entry(self.source, source_path)
 
1012
                    try:
 
1013
                        target_path = self.target.id2path(file_id)
 
1014
                    except errors.NoSuchId:
 
1015
                        target_path = None
 
1016
                        target_entry = None
 
1017
                    else:
 
1018
                        target_entry = self._get_entry(self.target, target_path)
1133
1019
                    result, changes = self._changes_from_entries(
1134
 
                        old_entry, new_entry)
 
1020
                        source_entry, target_entry, source_path, target_path)
1135
1021
                else:
1136
1022
                    changes = True
1137
1023
                # Get this parents parent to examine.
1142
1028
                            result[6][1] != 'directory'):
1143
1029
                        # This stopped being a directory, the old children have
1144
1030
                        # to be included.
1145
 
                        if old_entry is None:
 
1031
                        if source_entry is None:
1146
1032
                            # Reusing a discarded change.
1147
 
                            old_entry = self._get_entry(self.source, file_id)
 
1033
                            source_entry = self._get_entry(self.source, result[1][0])
1148
1034
                        precise_file_ids.update(
1149
 
                                self.source.iter_children(file_id))
 
1035
                                child.file_id
 
1036
                                for child in self.source.iter_child_entries(result[1][0]))
1150
1037
                    changed_file_ids.add(result[0])
1151
1038
                    yield result
1152
1039
 
1153
1040
    def file_content_matches(
1154
 
            self, source_file_id, target_file_id, source_path=None,
1155
 
            target_path=None, source_stat=None, target_stat=None):
 
1041
            self, source_path, target_path,
 
1042
            source_file_id=None, target_file_id=None,
 
1043
            source_stat=None, target_stat=None):
1156
1044
        """Check if two files are the same in the source and target trees.
1157
1045
 
1158
1046
        This only checks that the contents of the files are the same,
1159
1047
        it does not touch anything else.
1160
1048
 
1161
 
        :param source_file_id: File id of the file in the source tree
1162
 
        :param target_file_id: File id of the file in the target tree
1163
1049
        :param source_path: Path of the file in the source tree
1164
1050
        :param target_path: Path of the file in the target tree
 
1051
        :param source_file_id: Optional file id of the file in the source tree
 
1052
        :param target_file_id: Optional file id of the file in the target tree
1165
1053
        :param source_stat: Optional stat value of the file in the source tree
1166
1054
        :param target_stat: Optional stat value of the file in the target tree
1167
1055
        :return: Boolean indicating whether the files have the same contents
1168
1056
        """
1169
1057
        with self.lock_read():
1170
 
            if source_path is None:
1171
 
                source_path = self.source.id2path(source_file_id)
1172
 
            if target_path is None:
1173
 
                target_path = self.target.id2path(target_file_id)
1174
1058
            source_verifier_kind, source_verifier_data = (
1175
1059
                    self.source.get_file_verifier(
1176
1060
                        source_path, source_file_id, source_stat))
1426
1310
                    other_values.append(self._lookup_by_file_id(
1427
1311
                                            alt_extra, alt_tree, file_id))
1428
1312
                yield other_path, file_id, None, other_values
 
1313
 
 
1314
 
 
1315
def find_previous_paths(from_tree, to_tree, paths):
 
1316
    """Find previous tree paths.
 
1317
 
 
1318
    :param from_tree: From tree
 
1319
    :param to_tree: To tree
 
1320
    :param paths: Iterable over paths to search for
 
1321
    :return: Dictionary mapping from from_tree paths to paths in to_tree, or
 
1322
        None if there is no equivalent path.
 
1323
    """
 
1324
    ret = {}
 
1325
    for path in paths:
 
1326
        ret[path] = find_previous_path(from_tree, to_tree, path)
 
1327
    return ret
 
1328
 
 
1329
 
 
1330
def find_previous_path(from_tree, to_tree, path, file_id=None):
 
1331
    """Find previous tree path.
 
1332
 
 
1333
    :param from_tree: From tree
 
1334
    :param to_tree: To tree
 
1335
    :param path: Path to search for
 
1336
    :return: path in to_tree, or None if there is no equivalent path.
 
1337
    """
 
1338
    if file_id is None:
 
1339
        file_id = from_tree.path2id(path)
 
1340
    if file_id is None:
 
1341
        raise errors.NoSuchFile(path)
 
1342
    try:
 
1343
        return to_tree.id2path(file_id)
 
1344
    except errors.NoSuchId:
 
1345
        return None