/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: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-07-28 02:47:10 UTC
  • mfrom: (7519.1.1 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200728024710-a2ylds219f1lsl62
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/merge-3.1/+merge/388173

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
"""Tree classes, representing directory at point in time.
18
18
"""
19
19
 
20
 
from __future__ import absolute_import
21
 
 
22
 
try:
23
 
    from collections.abc import deque
24
 
except ImportError:  # python < 3.7
25
 
    from collections import deque
26
 
 
27
20
from .lazy_import import lazy_import
28
21
lazy_import(globals(), """
29
 
 
30
 
from breezy import (
31
 
    conflicts as _mod_conflicts,
32
 
    debug,
33
 
    delta,
34
 
    filters,
35
 
    revision as _mod_revision,
36
 
    rules,
37
 
    trace,
38
 
    )
39
22
from breezy.i18n import gettext
40
23
""")
41
24
 
43
26
    errors,
44
27
    lock,
45
28
    osutils,
 
29
    revision as _mod_revision,
 
30
    trace,
46
31
    )
47
32
from .inter import InterObject
48
 
from .sixish import (
49
 
    text_type,
50
 
    viewvalues,
51
 
    )
52
33
 
53
34
 
54
35
class FileTimestampUnavailable(errors.BzrError):
275
256
 
276
257
        Each conflict is an instance of breezy.conflicts.Conflict.
277
258
        """
 
259
        from . import conflicts as _mod_conflicts
278
260
        return _mod_conflicts.ConflictList()
279
261
 
280
262
    def extras(self):
694
676
            or None if unknown
695
677
        :return: the list of filters - [] if there are none
696
678
        """
 
679
        from . import debug, filters
697
680
        filter_pref_names = filters._get_registered_names()
698
681
        if len(filter_pref_names) == 0:
699
682
            return []
728
711
        :return: an iterator of tuple sequences, one per path-name.
729
712
          See _RulesSearcher.get_items for details on the tuple sequence.
730
713
        """
 
714
        from . import rules
731
715
        if _default_searcher is None:
732
716
            _default_searcher = rules._per_user_searcher
733
717
        searcher = self._get_rules_searcher(_default_searcher)
764
748
        """Check if this tree support versioning a specific file kind."""
765
749
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
766
750
 
 
751
    def preview_transform(self, pb=None):
 
752
        """Obtain a transform object."""
 
753
        raise NotImplementedError(self.preview_transform)
 
754
 
767
755
 
768
756
class InterTree(InterObject):
769
757
    """This class represents operations taking place between two Trees.
790
778
        # it works for all trees.
791
779
        return True
792
780
 
793
 
    def _changes_from_entries(self, source_entry, target_entry, source_path,
794
 
                              target_path):
795
 
        """Generate a iter_changes tuple between source_entry and target_entry.
796
 
 
797
 
        :param source_entry: An inventory entry from self.source, or None.
798
 
        :param target_entry: An inventory entry from self.target, or None.
799
 
        :param source_path: The path of source_entry.
800
 
        :param target_path: The path of target_entry.
801
 
        :return: A tuple, item 0 of which is an iter_changes result tuple, and
802
 
            item 1 is True if there are any changes in the result tuple.
803
 
        """
804
 
        if source_entry is None:
805
 
            if target_entry is None:
806
 
                return None
807
 
            file_id = target_entry.file_id
808
 
        else:
809
 
            file_id = source_entry.file_id
810
 
        if source_entry is not None:
811
 
            source_versioned = True
812
 
            source_name = source_entry.name
813
 
            source_parent = source_entry.parent_id
814
 
            source_kind, source_executable, source_stat = \
815
 
                self.source._comparison_data(source_entry, source_path)
816
 
        else:
817
 
            source_versioned = False
818
 
            source_name = None
819
 
            source_parent = None
820
 
            source_kind = None
821
 
            source_executable = None
822
 
        if target_entry is not None:
823
 
            target_versioned = True
824
 
            target_name = target_entry.name
825
 
            target_parent = target_entry.parent_id
826
 
            target_kind, target_executable, target_stat = \
827
 
                self.target._comparison_data(target_entry, target_path)
828
 
        else:
829
 
            target_versioned = False
830
 
            target_name = None
831
 
            target_parent = None
832
 
            target_kind = None
833
 
            target_executable = None
834
 
        versioned = (source_versioned, target_versioned)
835
 
        kind = (source_kind, target_kind)
836
 
        changed_content = False
837
 
        if source_kind != target_kind:
838
 
            changed_content = True
839
 
        elif source_kind == 'file':
840
 
            if not self.file_content_matches(
841
 
                    source_path, target_path,
842
 
                    source_stat, target_stat):
843
 
                changed_content = True
844
 
        elif source_kind == 'symlink':
845
 
            if (self.source.get_symlink_target(source_path) !=
846
 
                    self.target.get_symlink_target(target_path)):
847
 
                changed_content = True
848
 
        elif source_kind == 'tree-reference':
849
 
            if (self.source.get_reference_revision(source_path)
850
 
                    != self.target.get_reference_revision(target_path)):
851
 
                changed_content = True
852
 
        parent = (source_parent, target_parent)
853
 
        name = (source_name, target_name)
854
 
        executable = (source_executable, target_executable)
855
 
        if (changed_content is not False or versioned[0] != versioned[1] or
856
 
            parent[0] != parent[1] or name[0] != name[1] or
857
 
                executable[0] != executable[1]):
858
 
            changes = True
859
 
        else:
860
 
            changes = False
861
 
        return TreeChange(
862
 
            file_id, (source_path, target_path), changed_content,
863
 
            versioned, parent, name, kind, executable), changes
864
 
 
865
781
    def compare(self, want_unchanged=False, specific_files=None,
866
782
                extra_trees=None, require_versioned=False, include_root=False,
867
783
                want_unversioned=False):
881
797
            a PathsNotVersionedError will be thrown.
882
798
        :param want_unversioned: Scan for unversioned paths.
883
799
        """
 
800
        from . import delta
884
801
        trees = (self.source,)
885
802
        if extra_trees is not None:
886
803
            trees = trees + tuple(extra_trees)
926
843
            output. An unversioned file is defined as one with (False, False)
927
844
            for the versioned pair.
928
845
        """
929
 
        if not extra_trees:
930
 
            extra_trees = []
931
 
        else:
932
 
            extra_trees = list(extra_trees)
933
 
        # The ids of items we need to examine to insure delta consistency.
934
 
        precise_file_ids = set()
935
 
        changed_file_ids = []
936
 
        if specific_files == []:
937
 
            target_specific_files = []
938
 
            source_specific_files = []
939
 
        else:
940
 
            target_specific_files = self.target.find_related_paths_across_trees(
941
 
                specific_files, [self.source] + extra_trees,
942
 
                require_versioned=require_versioned)
943
 
            source_specific_files = self.source.find_related_paths_across_trees(
944
 
                specific_files, [self.target] + extra_trees,
945
 
                require_versioned=require_versioned)
946
 
        if specific_files is not None:
947
 
            # reparented or added entries must have their parents included
948
 
            # so that valid deltas can be created. The seen_parents set
949
 
            # tracks the parents that we need to have.
950
 
            # The seen_dirs set tracks directory entries we've yielded.
951
 
            # After outputting version object in to_entries we set difference
952
 
            # the two seen sets and start checking parents.
953
 
            seen_parents = set()
954
 
            seen_dirs = set()
955
 
        if want_unversioned:
956
 
            all_unversioned = sorted([(p.split('/'), p) for p in
957
 
                                      self.target.extras()
958
 
                                      if specific_files is None or
959
 
                                      osutils.is_inside_any(specific_files, p)])
960
 
            all_unversioned = deque(all_unversioned)
961
 
        else:
962
 
            all_unversioned = deque()
963
 
        to_paths = {}
964
 
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
965
 
            specific_files=source_specific_files))
966
 
        from_data = dict(from_entries_by_dir)
967
 
        to_entries_by_dir = list(self.target.iter_entries_by_dir(
968
 
            specific_files=target_specific_files))
969
 
        path_equivs = self.find_source_paths([p for p, e in to_entries_by_dir])
970
 
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
971
 
        entry_count = 0
972
 
        # the unversioned path lookup only occurs on real trees - where there
973
 
        # can be extras. So the fake_entry is solely used to look up
974
 
        # executable it values when execute is not supported.
975
 
        fake_entry = TreeFile()
976
 
        for target_path, target_entry in to_entries_by_dir:
977
 
            while (all_unversioned and
978
 
                   all_unversioned[0][0] < target_path.split('/')):
979
 
                unversioned_path = all_unversioned.popleft()
980
 
                target_kind, target_executable, target_stat = \
981
 
                    self.target._comparison_data(
982
 
                        fake_entry, unversioned_path[1])
983
 
                yield TreeChange(
984
 
                    None, (None, unversioned_path[1]), True, (False, False),
985
 
                    (None, None),
986
 
                    (None, unversioned_path[0][-1]),
987
 
                    (None, target_kind),
988
 
                    (None, target_executable))
989
 
            source_path = path_equivs[target_path]
990
 
            if source_path is not None:
991
 
                source_entry = from_data.get(source_path)
992
 
            else:
993
 
                source_entry = None
994
 
            result, changes = self._changes_from_entries(
995
 
                source_entry, target_entry, source_path=source_path, target_path=target_path)
996
 
            to_paths[result.file_id] = result.path[1]
997
 
            entry_count += 1
998
 
            if result.versioned[0]:
999
 
                entry_count += 1
1000
 
            if pb is not None:
1001
 
                pb.update('comparing files', entry_count, num_entries)
1002
 
            if changes or include_unchanged:
1003
 
                if specific_files is not None:
1004
 
                    precise_file_ids.add(result.parent_id[1])
1005
 
                    changed_file_ids.append(result.file_id)
1006
 
                yield result
1007
 
            # Ensure correct behaviour for reparented/added specific files.
1008
 
            if specific_files is not None:
1009
 
                # Record output dirs
1010
 
                if result.kind[1] == 'directory':
1011
 
                    seen_dirs.add(result.file_id)
1012
 
                # Record parents of reparented/added entries.
1013
 
                if not result.versioned[0] or result.is_reparented():
1014
 
                    seen_parents.add(result.parent_id[1])
1015
 
        while all_unversioned:
1016
 
            # yield any trailing unversioned paths
1017
 
            unversioned_path = all_unversioned.popleft()
1018
 
            to_kind, to_executable, to_stat = \
1019
 
                self.target._comparison_data(fake_entry, unversioned_path[1])
1020
 
            yield TreeChange(
1021
 
                None, (None, unversioned_path[1]), True, (False, False),
1022
 
                (None, None),
1023
 
                (None, unversioned_path[0][-1]),
1024
 
                (None, to_kind),
1025
 
                (None, to_executable))
1026
 
        # Yield all remaining source paths
1027
 
        for path, from_entry in from_entries_by_dir:
1028
 
            file_id = from_entry.file_id
1029
 
            if file_id in to_paths:
1030
 
                # already returned
1031
 
                continue
1032
 
            to_path = self.find_target_path(path)
1033
 
            entry_count += 1
1034
 
            if pb is not None:
1035
 
                pb.update('comparing files', entry_count, num_entries)
1036
 
            versioned = (True, False)
1037
 
            parent = (from_entry.parent_id, None)
1038
 
            name = (from_entry.name, None)
1039
 
            from_kind, from_executable, stat_value = \
1040
 
                self.source._comparison_data(from_entry, path)
1041
 
            kind = (from_kind, None)
1042
 
            executable = (from_executable, None)
1043
 
            changed_content = from_kind is not None
1044
 
            # the parent's path is necessarily known at this point.
1045
 
            changed_file_ids.append(file_id)
1046
 
            yield TreeChange(
1047
 
                file_id, (path, to_path), changed_content, versioned, parent,
1048
 
                name, kind, executable)
1049
 
        changed_file_ids = set(changed_file_ids)
1050
 
        if specific_files is not None:
1051
 
            for result in self._handle_precise_ids(precise_file_ids,
1052
 
                                                   changed_file_ids):
1053
 
                yield result
1054
 
 
1055
 
    @staticmethod
1056
 
    def _get_entry(tree, path):
1057
 
        """Get an inventory entry from a tree, with missing entries as None.
1058
 
 
1059
 
        If the tree raises NotImplementedError on accessing .inventory, then
1060
 
        this is worked around using iter_entries_by_dir on just the file id
1061
 
        desired.
1062
 
 
1063
 
        :param tree: The tree to lookup the entry in.
1064
 
        :param path: The path to look up
1065
 
        """
1066
 
        # No inventory available.
1067
 
        try:
1068
 
            iterator = tree.iter_entries_by_dir(specific_files=[path])
1069
 
            return next(iterator)[1]
1070
 
        except StopIteration:
1071
 
            return None
1072
 
 
1073
 
    def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
1074
 
                            discarded_changes=None):
1075
 
        """Fill out a partial iter_changes to be consistent.
1076
 
 
1077
 
        :param precise_file_ids: The file ids of parents that were seen during
1078
 
            the iter_changes.
1079
 
        :param changed_file_ids: The file ids of already emitted items.
1080
 
        :param discarded_changes: An optional dict of precalculated
1081
 
            iter_changes items which the partial iter_changes had not output
1082
 
            but had calculated.
1083
 
        :return: A generator of iter_changes items to output.
1084
 
        """
1085
 
        # process parents of things that had changed under the users
1086
 
        # requested paths to prevent incorrect paths or parent ids which
1087
 
        # aren't in the tree.
1088
 
        while precise_file_ids:
1089
 
            precise_file_ids.discard(None)
1090
 
            # Don't emit file_ids twice
1091
 
            precise_file_ids.difference_update(changed_file_ids)
1092
 
            if not precise_file_ids:
1093
 
                break
1094
 
            # If the there was something at a given output path in source, we
1095
 
            # have to include the entry from source in the delta, or we would
1096
 
            # be putting this entry into a used path.
1097
 
            paths = []
1098
 
            for parent_id in precise_file_ids:
1099
 
                try:
1100
 
                    paths.append(self.target.id2path(parent_id))
1101
 
                except errors.NoSuchId:
1102
 
                    # This id has been dragged in from the source by delta
1103
 
                    # expansion and isn't present in target at all: we don't
1104
 
                    # need to check for path collisions on it.
1105
 
                    pass
1106
 
            for path in paths:
1107
 
                old_id = self.source.path2id(path)
1108
 
                precise_file_ids.add(old_id)
1109
 
            precise_file_ids.discard(None)
1110
 
            current_ids = precise_file_ids
1111
 
            precise_file_ids = set()
1112
 
            # We have to emit all of precise_file_ids that have been altered.
1113
 
            # We may have to output the children of some of those ids if any
1114
 
            # directories have stopped being directories.
1115
 
            for file_id in current_ids:
1116
 
                # Examine file_id
1117
 
                if discarded_changes:
1118
 
                    result = discarded_changes.get(file_id)
1119
 
                    source_entry = None
1120
 
                else:
1121
 
                    result = None
1122
 
                if result is None:
1123
 
                    try:
1124
 
                        source_path = self.source.id2path(file_id)
1125
 
                    except errors.NoSuchId:
1126
 
                        source_path = None
1127
 
                        source_entry = None
1128
 
                    else:
1129
 
                        source_entry = self._get_entry(
1130
 
                            self.source, source_path)
1131
 
                    try:
1132
 
                        target_path = self.target.id2path(file_id)
1133
 
                    except errors.NoSuchId:
1134
 
                        target_path = None
1135
 
                        target_entry = None
1136
 
                    else:
1137
 
                        target_entry = self._get_entry(
1138
 
                            self.target, target_path)
1139
 
                    result, changes = self._changes_from_entries(
1140
 
                        source_entry, target_entry, source_path, target_path)
1141
 
                else:
1142
 
                    changes = True
1143
 
                # Get this parents parent to examine.
1144
 
                new_parent_id = result.parent_id[1]
1145
 
                precise_file_ids.add(new_parent_id)
1146
 
                if changes:
1147
 
                    if (result.kind[0] == 'directory' and
1148
 
                            result.kind[1] != 'directory'):
1149
 
                        # This stopped being a directory, the old children have
1150
 
                        # to be included.
1151
 
                        if source_entry is None:
1152
 
                            # Reusing a discarded change.
1153
 
                            source_entry = self._get_entry(
1154
 
                                self.source, result.path[0])
1155
 
                        precise_file_ids.update(
1156
 
                            child.file_id
1157
 
                            for child in self.source.iter_child_entries(result.path[0]))
1158
 
                    changed_file_ids.add(result.file_id)
1159
 
                    yield result
 
846
        raise NotImplementedError(self.iter_changes)
1160
847
 
1161
848
    def file_content_matches(
1162
849
            self, source_path, target_path,
1200
887
        :return: path in target, or None if there is no equivalent path.
1201
888
        :raise NoSuchFile: If the path doesn't exist in source
1202
889
        """
1203
 
        file_id = self.source.path2id(path)
1204
 
        if file_id is None:
1205
 
            raise errors.NoSuchFile(path)
1206
 
        try:
1207
 
            return self.target.id2path(file_id, recurse=recurse)
1208
 
        except errors.NoSuchId:
1209
 
            return None
 
890
        raise NotImplementedError(self.find_target_path)
1210
891
 
1211
892
    def find_source_path(self, path, recurse='none'):
1212
893
        """Find the source tree path.
1215
896
        :return: path in source, or None if there is no equivalent path.
1216
897
        :raise NoSuchFile: if the path doesn't exist in target
1217
898
        """
1218
 
        file_id = self.target.path2id(path)
1219
 
        if file_id is None:
1220
 
            raise errors.NoSuchFile(path)
1221
 
        try:
1222
 
            return self.source.id2path(file_id, recurse=recurse)
1223
 
        except errors.NoSuchId:
1224
 
            return None
 
899
        raise NotImplementedError(self.find_source_path)
1225
900
 
1226
901
    def find_target_paths(self, paths, recurse='none'):
1227
902
        """Find target tree paths.
1248
923
        return ret
1249
924
 
1250
925
 
1251
 
InterTree.register_optimiser(InterTree)
1252
 
 
1253
 
 
1254
926
def find_previous_paths(from_tree, to_tree, paths, recurse='none'):
1255
927
    """Find previous tree paths.
1256
928