~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Vincent Ladeuil
  • Date: 2010-02-11 09:27:55 UTC
  • mfrom: (5017.3.46 test-servers)
  • mto: This revision was merged to the branch mainline in revision 5030.
  • Revision ID: v.ladeuil+lp@free.fr-20100211092755-3vvu4vbwiwjjte3s
Move tests servers from bzrlib.transport to bzrlib.tests.test_server

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
98
98
    def iter_changes(self, from_tree, include_unchanged=False,
99
99
                     specific_files=None, pb=None, extra_trees=None,
100
100
                     require_versioned=True, want_unversioned=False):
 
101
        """See InterTree.iter_changes"""
101
102
        intertree = InterTree.get(from_tree, self)
102
103
        return intertree.iter_changes(include_unchanged, specific_files, pb,
103
104
            extra_trees, require_versioned, want_unversioned=want_unversioned)
170
171
        return self.bzrdir.is_control_filename(filename)
171
172
 
172
173
    @needs_read_lock
173
 
    def iter_entries_by_dir(self, specific_file_ids=None):
 
174
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
174
175
        """Walk the tree in 'by_dir' order.
175
176
 
176
177
        This will yield each entry in the tree as a (path, entry) tuple.
193
194
 
194
195
        The yield order (ignoring root) would be::
195
196
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
 
197
 
 
198
        :param yield_parents: If True, yield the parents from the root leading
 
199
            down to specific_file_ids that have been requested. This has no
 
200
            impact if specific_file_ids is None.
196
201
        """
197
202
        return self.inventory.iter_entries_by_dir(
198
 
            specific_file_ids=specific_file_ids)
 
203
            specific_file_ids=specific_file_ids, yield_parents=yield_parents)
199
204
 
200
205
    def iter_references(self):
201
206
        if self.supports_tree_reference():
218
223
    def path_content_summary(self, path):
219
224
        """Get a summary of the information about path.
220
225
 
 
226
        All the attributes returned are for the canonical form, not the
 
227
        convenient form (if content filters are in use.)
 
228
 
221
229
        :param path: A relative path within the tree.
222
230
        :return: A tuple containing kind, size, exec, sha1-or-link.
223
231
            Kind is always present (see tree.kind()).
224
 
            size is present if kind is file, None otherwise.
 
232
            size is present if kind is file and the size of the 
 
233
                canonical form can be cheaply determined, None otherwise.
225
234
            exec is None unless kind is file and the platform supports the 'x'
226
235
                bit.
227
236
            sha1-or-link is the link target if kind is symlink, or the sha1 if
689
698
                for path in path_names:
690
699
                    yield searcher.get_items(path)
691
700
 
692
 
    @needs_read_lock
693
701
    def _get_rules_searcher(self, default_searcher):
694
702
        """Get the RulesSearcher for this tree given the default one."""
695
703
        searcher = default_searcher
844
852
    will pass through to InterTree as appropriate.
845
853
    """
846
854
 
 
855
    # Formats that will be used to test this InterTree. If both are
 
856
    # None, this InterTree will not be tested (e.g. because a complex
 
857
    # setup is required)
 
858
    _matching_from_tree_format = None
 
859
    _matching_to_tree_format = None
 
860
 
847
861
    _optimisers = []
848
862
 
 
863
    def _changes_from_entries(self, source_entry, target_entry,
 
864
        source_path=None, target_path=None):
 
865
        """Generate a iter_changes tuple between source_entry and target_entry.
 
866
 
 
867
        :param source_entry: An inventory entry from self.source, or None.
 
868
        :param target_entry: An inventory entry from self.target, or None.
 
869
        :param source_path: The path of source_entry, if known. If not known
 
870
            it will be looked up.
 
871
        :param target_path: The path of target_entry, if known. If not known
 
872
            it will be looked up.
 
873
        :return: A tuple, item 0 of which is an iter_changes result tuple, and
 
874
            item 1 is True if there are any changes in the result tuple.
 
875
        """
 
876
        if source_entry is None:
 
877
            if target_entry is None:
 
878
                return None
 
879
            file_id = target_entry.file_id
 
880
        else:
 
881
            file_id = source_entry.file_id
 
882
        if source_entry is not None:
 
883
            source_versioned = True
 
884
            source_name = source_entry.name
 
885
            source_parent = source_entry.parent_id
 
886
            if source_path is None:
 
887
                source_path = self.source.id2path(file_id)
 
888
            source_kind, source_executable, source_stat = \
 
889
                self.source._comparison_data(source_entry, source_path)
 
890
        else:
 
891
            source_versioned = False
 
892
            source_name = None
 
893
            source_parent = None
 
894
            source_kind = None
 
895
            source_executable = None
 
896
        if target_entry is not None:
 
897
            target_versioned = True
 
898
            target_name = target_entry.name
 
899
            target_parent = target_entry.parent_id
 
900
            if target_path is None:
 
901
                target_path = self.target.id2path(file_id)
 
902
            target_kind, target_executable, target_stat = \
 
903
                self.target._comparison_data(target_entry, target_path)
 
904
        else:
 
905
            target_versioned = False
 
906
            target_name = None
 
907
            target_parent = None
 
908
            target_kind = None
 
909
            target_executable = None
 
910
        versioned = (source_versioned, target_versioned)
 
911
        kind = (source_kind, target_kind)
 
912
        changed_content = False
 
913
        if source_kind != target_kind:
 
914
            changed_content = True
 
915
        elif source_kind == 'file':
 
916
            if (self.source.get_file_sha1(file_id, source_path, source_stat) !=
 
917
                self.target.get_file_sha1(file_id, target_path, target_stat)):
 
918
                changed_content = True
 
919
        elif source_kind == 'symlink':
 
920
            if (self.source.get_symlink_target(file_id) !=
 
921
                self.target.get_symlink_target(file_id)):
 
922
                changed_content = True
 
923
            # XXX: Yes, the indentation below is wrong. But fixing it broke
 
924
            # test_merge.TestMergerEntriesLCAOnDisk.
 
925
            # test_nested_tree_subtree_renamed_and_modified. We'll wait for
 
926
            # the fix from bzr.dev -- vila 2009026
 
927
            elif source_kind == 'tree-reference':
 
928
                if (self.source.get_reference_revision(file_id, source_path)
 
929
                    != self.target.get_reference_revision(file_id, target_path)):
 
930
                    changed_content = True
 
931
        parent = (source_parent, target_parent)
 
932
        name = (source_name, target_name)
 
933
        executable = (source_executable, target_executable)
 
934
        if (changed_content is not False or versioned[0] != versioned[1]
 
935
            or parent[0] != parent[1] or name[0] != name[1] or
 
936
            executable[0] != executable[1]):
 
937
            changes = True
 
938
        else:
 
939
            changes = False
 
940
        return (file_id, (source_path, target_path), changed_content,
 
941
                versioned, parent, name, kind, executable), changes
 
942
 
849
943
    @needs_read_lock
850
944
    def compare(self, want_unchanged=False, specific_files=None,
851
945
        extra_trees=None, require_versioned=False, include_root=False,
866
960
            a PathsNotVersionedError will be thrown.
867
961
        :param want_unversioned: Scan for unversioned paths.
868
962
        """
869
 
        # NB: show_status depends on being able to pass in non-versioned files
870
 
        # and report them as unknown
871
963
        trees = (self.source,)
872
964
        if extra_trees is not None:
873
965
            trees = trees + tuple(extra_trees)
914
1006
        :param require_versioned: Raise errors.PathsNotVersionedError if a
915
1007
            path in the specific_files list is not versioned in one of
916
1008
            source, target or extra_trees.
 
1009
        :param specific_files: An optional list of file paths to restrict the
 
1010
            comparison to. When mapping filenames to ids, all matches in all
 
1011
            trees (including optional extra_trees) are used, and all children
 
1012
            of matched directories are included. The parents in the target tree
 
1013
            of the specific files up to and including the root of the tree are
 
1014
            always evaluated for changes too.
917
1015
        :param want_unversioned: Should unversioned files be returned in the
918
1016
            output. An unversioned file is defined as one with (False, False)
919
1017
            for the versioned pair.
921
1019
        lookup_trees = [self.source]
922
1020
        if extra_trees:
923
1021
             lookup_trees.extend(extra_trees)
 
1022
        # The ids of items we need to examine to insure delta consistency.
 
1023
        precise_file_ids = set()
 
1024
        changed_file_ids = []
924
1025
        if specific_files == []:
925
1026
            specific_file_ids = []
926
1027
        else:
927
1028
            specific_file_ids = self.target.paths2ids(specific_files,
928
1029
                lookup_trees, require_versioned=require_versioned)
 
1030
        if specific_files is not None:
 
1031
            # reparented or added entries must have their parents included
 
1032
            # so that valid deltas can be created. The seen_parents set
 
1033
            # tracks the parents that we need to have.
 
1034
            # The seen_dirs set tracks directory entries we've yielded.
 
1035
            # After outputting version object in to_entries we set difference
 
1036
            # the two seen sets and start checking parents.
 
1037
            seen_parents = set()
 
1038
            seen_dirs = set()
929
1039
        if want_unversioned:
930
1040
            all_unversioned = sorted([(p.split('/'), p) for p in
931
1041
                                     self.target.extras()
946
1056
        # can be extras. So the fake_entry is solely used to look up
947
1057
        # executable it values when execute is not supported.
948
1058
        fake_entry = InventoryFile('unused', 'unused', 'unused')
949
 
        for to_path, to_entry in to_entries_by_dir:
950
 
            while all_unversioned and all_unversioned[0][0] < to_path.split('/'):
 
1059
        for target_path, target_entry in to_entries_by_dir:
 
1060
            while (all_unversioned and
 
1061
                all_unversioned[0][0] < target_path.split('/')):
951
1062
                unversioned_path = all_unversioned.popleft()
952
 
                to_kind, to_executable, to_stat = \
 
1063
                target_kind, target_executable, target_stat = \
953
1064
                    self.target._comparison_data(fake_entry, unversioned_path[1])
954
1065
                yield (None, (None, unversioned_path[1]), True, (False, False),
955
1066
                    (None, None),
956
1067
                    (None, unversioned_path[0][-1]),
957
 
                    (None, to_kind),
958
 
                    (None, to_executable))
959
 
            file_id = to_entry.file_id
960
 
            to_paths[file_id] = to_path
 
1068
                    (None, target_kind),
 
1069
                    (None, target_executable))
 
1070
            source_path, source_entry = from_data.get(target_entry.file_id,
 
1071
                (None, None))
 
1072
            result, changes = self._changes_from_entries(source_entry,
 
1073
                target_entry, source_path=source_path, target_path=target_path)
 
1074
            to_paths[result[0]] = result[1][1]
961
1075
            entry_count += 1
962
 
            changed_content = False
963
 
            from_path, from_entry = from_data.get(file_id, (None, None))
964
 
            from_versioned = (from_entry is not None)
965
 
            if from_entry is not None:
966
 
                from_versioned = True
967
 
                from_name = from_entry.name
968
 
                from_parent = from_entry.parent_id
969
 
                from_kind, from_executable, from_stat = \
970
 
                    self.source._comparison_data(from_entry, from_path)
 
1076
            if result[3][0]:
971
1077
                entry_count += 1
972
 
            else:
973
 
                from_versioned = False
974
 
                from_kind = None
975
 
                from_parent = None
976
 
                from_name = None
977
 
                from_executable = None
978
 
            versioned = (from_versioned, True)
979
 
            to_kind, to_executable, to_stat = \
980
 
                self.target._comparison_data(to_entry, to_path)
981
 
            kind = (from_kind, to_kind)
982
 
            if kind[0] != kind[1]:
983
 
                changed_content = True
984
 
            elif from_kind == 'file':
985
 
                if (self.source.get_file_sha1(file_id, from_path, from_stat) !=
986
 
                    self.target.get_file_sha1(file_id, to_path, to_stat)):
987
 
                    changed_content = True
988
 
            elif from_kind == 'symlink':
989
 
                if (self.source.get_symlink_target(file_id) !=
990
 
                    self.target.get_symlink_target(file_id)):
991
 
                    changed_content = True
992
 
                # XXX: Yes, the indentation below is wrong. But fixing it broke
993
 
                # test_merge.TestMergerEntriesLCAOnDisk.
994
 
                # test_nested_tree_subtree_renamed_and_modified. We'll wait for
995
 
                # the fix from bzr.dev -- vila 2009026
996
 
                elif from_kind == 'tree-reference':
997
 
                    if (self.source.get_reference_revision(file_id, from_path)
998
 
                        != self.target.get_reference_revision(file_id, to_path)):
999
 
                        changed_content = True
1000
 
            parent = (from_parent, to_entry.parent_id)
1001
 
            name = (from_name, to_entry.name)
1002
 
            executable = (from_executable, to_executable)
1003
1078
            if pb is not None:
1004
1079
                pb.update('comparing files', entry_count, num_entries)
1005
 
            if (changed_content is not False or versioned[0] != versioned[1]
1006
 
                or parent[0] != parent[1] or name[0] != name[1] or
1007
 
                executable[0] != executable[1] or include_unchanged):
1008
 
                yield (file_id, (from_path, to_path), changed_content,
1009
 
                    versioned, parent, name, kind, executable)
1010
 
 
 
1080
            if changes or include_unchanged:
 
1081
                if specific_file_ids is not None:
 
1082
                    new_parent_id = result[4][1]
 
1083
                    precise_file_ids.add(new_parent_id)
 
1084
                    changed_file_ids.append(result[0])
 
1085
                yield result
 
1086
            # Ensure correct behaviour for reparented/added specific files.
 
1087
            if specific_files is not None:
 
1088
                # Record output dirs
 
1089
                if result[6][1] == 'directory':
 
1090
                    seen_dirs.add(result[0])
 
1091
                # Record parents of reparented/added entries.
 
1092
                versioned = result[3]
 
1093
                parents = result[4]
 
1094
                if not versioned[0] or parents[0] != parents[1]:
 
1095
                    seen_parents.add(parents[1])
1011
1096
        while all_unversioned:
1012
1097
            # yield any trailing unversioned paths
1013
1098
            unversioned_path = all_unversioned.popleft()
1018
1103
                (None, unversioned_path[0][-1]),
1019
1104
                (None, to_kind),
1020
1105
                (None, to_executable))
1021
 
 
1022
 
        def get_to_path(to_entry):
1023
 
            if to_entry.parent_id is None:
1024
 
                to_path = '' # the root
1025
 
            else:
1026
 
                if to_entry.parent_id not in to_paths:
1027
 
                    # recurse up
1028
 
                    return get_to_path(self.target.inventory[to_entry.parent_id])
1029
 
                to_path = osutils.pathjoin(to_paths[to_entry.parent_id],
1030
 
                                           to_entry.name)
1031
 
            to_paths[to_entry.file_id] = to_path
1032
 
            return to_path
1033
 
 
 
1106
        # Yield all remaining source paths
1034
1107
        for path, from_entry in from_entries_by_dir:
1035
1108
            file_id = from_entry.file_id
1036
1109
            if file_id in to_paths:
1037
1110
                # already returned
1038
1111
                continue
1039
 
            if not file_id in self.target.all_file_ids():
 
1112
            if file_id not in self.target.all_file_ids():
1040
1113
                # common case - paths we have not emitted are not present in
1041
1114
                # target.
1042
1115
                to_path = None
1043
1116
            else:
1044
 
                to_path = get_to_path(self.target.inventory[file_id])
 
1117
                to_path = self.target.id2path(file_id)
1045
1118
            entry_count += 1
1046
1119
            if pb is not None:
1047
1120
                pb.update('comparing files', entry_count, num_entries)
1054
1127
            executable = (from_executable, None)
1055
1128
            changed_content = from_kind is not None
1056
1129
            # the parent's path is necessarily known at this point.
 
1130
            changed_file_ids.append(file_id)
1057
1131
            yield(file_id, (path, to_path), changed_content, versioned, parent,
1058
1132
                  name, kind, executable)
 
1133
        changed_file_ids = set(changed_file_ids)
 
1134
        if specific_file_ids is not None:
 
1135
            for result in self._handle_precise_ids(precise_file_ids,
 
1136
                changed_file_ids):
 
1137
                yield result
 
1138
 
 
1139
    def _get_entry(self, tree, file_id):
 
1140
        """Get an inventory entry from a tree, with missing entries as None.
 
1141
 
 
1142
        If the tree raises NotImplementedError on accessing .inventory, then
 
1143
        this is worked around using iter_entries_by_dir on just the file id
 
1144
        desired.
 
1145
 
 
1146
        :param tree: The tree to lookup the entry in.
 
1147
        :param file_id: The file_id to lookup.
 
1148
        """
 
1149
        try:
 
1150
            inventory = tree.inventory
 
1151
        except NotImplementedError:
 
1152
            # No inventory available.
 
1153
            try:
 
1154
                iterator = tree.iter_entries_by_dir(specific_file_ids=[file_id])
 
1155
                return iterator.next()[1]
 
1156
            except StopIteration:
 
1157
                return None
 
1158
        else:
 
1159
            try:
 
1160
                return inventory[file_id]
 
1161
            except errors.NoSuchId:
 
1162
                return None
 
1163
 
 
1164
    def _handle_precise_ids(self, precise_file_ids, changed_file_ids,
 
1165
        discarded_changes=None):
 
1166
        """Fill out a partial iter_changes to be consistent.
 
1167
 
 
1168
        :param precise_file_ids: The file ids of parents that were seen during
 
1169
            the iter_changes.
 
1170
        :param changed_file_ids: The file ids of already emitted items.
 
1171
        :param discarded_changes: An optional dict of precalculated
 
1172
            iter_changes items which the partial iter_changes had not output
 
1173
            but had calculated.
 
1174
        :return: A generator of iter_changes items to output.
 
1175
        """
 
1176
        # process parents of things that had changed under the users
 
1177
        # requested paths to prevent incorrect paths or parent ids which
 
1178
        # aren't in the tree.
 
1179
        while precise_file_ids:
 
1180
            precise_file_ids.discard(None)
 
1181
            # Don't emit file_ids twice
 
1182
            precise_file_ids.difference_update(changed_file_ids)
 
1183
            if not precise_file_ids:
 
1184
                break
 
1185
            # If the there was something at a given output path in source, we
 
1186
            # have to include the entry from source in the delta, or we would
 
1187
            # be putting this entry into a used path.
 
1188
            paths = []
 
1189
            for parent_id in precise_file_ids:
 
1190
                try:
 
1191
                    paths.append(self.target.id2path(parent_id))
 
1192
                except errors.NoSuchId:
 
1193
                    # This id has been dragged in from the source by delta
 
1194
                    # expansion and isn't present in target at all: we don't
 
1195
                    # need to check for path collisions on it.
 
1196
                    pass
 
1197
            for path in paths:
 
1198
                old_id = self.source.path2id(path)
 
1199
                precise_file_ids.add(old_id)
 
1200
            precise_file_ids.discard(None)
 
1201
            current_ids = precise_file_ids
 
1202
            precise_file_ids = set()
 
1203
            # We have to emit all of precise_file_ids that have been altered.
 
1204
            # We may have to output the children of some of those ids if any
 
1205
            # directories have stopped being directories.
 
1206
            for file_id in current_ids:
 
1207
                # Examine file_id
 
1208
                if discarded_changes:
 
1209
                    result = discarded_changes.get(file_id)
 
1210
                    old_entry = None
 
1211
                else:
 
1212
                    result = None
 
1213
                if result is None:
 
1214
                    old_entry = self._get_entry(self.source, file_id)
 
1215
                    new_entry = self._get_entry(self.target, file_id)
 
1216
                    result, changes = self._changes_from_entries(
 
1217
                        old_entry, new_entry)
 
1218
                else:
 
1219
                    changes = True
 
1220
                # Get this parents parent to examine.
 
1221
                new_parent_id = result[4][1]
 
1222
                precise_file_ids.add(new_parent_id)
 
1223
                if changes:
 
1224
                    if (result[6][0] == 'directory' and
 
1225
                        result[6][1] != 'directory'):
 
1226
                        # This stopped being a directory, the old children have
 
1227
                        # to be included.
 
1228
                        if old_entry is None:
 
1229
                            # Reusing a discarded change.
 
1230
                            old_entry = self._get_entry(self.source, file_id)
 
1231
                        for child in old_entry.children.values():
 
1232
                            precise_file_ids.add(child.file_id)
 
1233
                    changed_file_ids.add(result[0])
 
1234
                    yield result
1059
1235
 
1060
1236
 
1061
1237
class MultiWalker(object):