/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-20 02:17:05 UTC
  • mfrom: (7518.1.2 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200720021705-5f11tmo1hdqjxm6x
Merge lp:brz/3.1.

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

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