/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 bzrlib/tree.py

Merge with extras

Show diffs side-by-side

added added

removed removed

Lines of Context:
562
562
    def _get_rules_searcher(self, default_searcher):
563
563
        """Get the RulesSearcher for this tree given the default one."""
564
564
        searcher = default_searcher
565
 
        file_id = self.path2id(rules.RULES_TREE_FILENAME)
566
 
        if file_id is not None:
567
 
            ini_file = self.get_file(file_id)
568
 
            searcher = rules._StackedRulesSearcher(
569
 
                [rules._IniBasedRulesSearcher(ini_file), default_searcher])
570
565
        return searcher
571
566
 
572
567
 
957
952
                self.source._comparison_data(from_entry, path)
958
953
            kind = (from_kind, None)
959
954
            executable = (from_executable, None)
960
 
            changed_content = True
 
955
            changed_content = from_kind is not None
961
956
            # the parent's path is necessarily known at this point.
962
957
            yield(file_id, (path, to_path), changed_content, versioned, parent,
963
958
                  name, kind, executable)
 
959
 
 
960
 
 
961
class MultiWalker(object):
 
962
    """Walk multiple trees simultaneously, getting combined results."""
 
963
 
 
964
    # Note: This could be written to not assume you can do out-of-order
 
965
    #       lookups. Instead any nodes that don't match in all trees could be
 
966
    #       marked as 'deferred', and then returned in the final cleanup loop.
 
967
    #       For now, I think it is "nicer" to return things as close to the
 
968
    #       "master_tree" order as we can.
 
969
 
 
970
    def __init__(self, master_tree, other_trees):
 
971
        """Create a new MultiWalker.
 
972
 
 
973
        All trees being walked must implement "iter_entries_by_dir()", such
 
974
        that they yield (path, object) tuples, where that object will have a
 
975
        '.file_id' member, that can be used to check equality.
 
976
 
 
977
        :param master_tree: All trees will be 'slaved' to the master_tree such
 
978
            that nodes in master_tree will be used as 'first-pass' sync points.
 
979
            Any nodes that aren't in master_tree will be merged in a second
 
980
            pass.
 
981
        :param other_trees: A list of other trees to walk simultaneously.
 
982
        """
 
983
        self._master_tree = master_tree
 
984
        self._other_trees = other_trees
 
985
 
 
986
        # Keep track of any nodes that were properly processed just out of
 
987
        # order, that way we don't return them at the end, we don't have to
 
988
        # track *all* processed file_ids, just the out-of-order ones
 
989
        self._out_of_order_processed = set()
 
990
 
 
991
    @staticmethod
 
992
    def _step_one(iterator):
 
993
        """Step an iter_entries_by_dir iterator.
 
994
 
 
995
        :return: (has_more, path, ie)
 
996
            If has_more is False, path and ie will be None.
 
997
        """
 
998
        try:
 
999
            path, ie = iterator.next()
 
1000
        except StopIteration:
 
1001
            return False, None, None
 
1002
        else:
 
1003
            return True, path, ie
 
1004
 
 
1005
    @staticmethod
 
1006
    def _cmp_path_by_dirblock(path1, path2):
 
1007
        """Compare two paths based on what directory they are in.
 
1008
 
 
1009
        This generates a sort order, such that all children of a directory are
 
1010
        sorted together, and grandchildren are in the same order as the
 
1011
        children appear. But all grandchildren come after all children.
 
1012
 
 
1013
        :param path1: first path
 
1014
        :param path2: the second path
 
1015
        :return: negative number if ``path1`` comes first,
 
1016
            0 if paths are equal
 
1017
            and a positive number if ``path2`` sorts first
 
1018
        """
 
1019
        # Shortcut this special case
 
1020
        if path1 == path2:
 
1021
            return 0
 
1022
        # This is stolen from _dirstate_helpers_py.py, only switching it to
 
1023
        # Unicode objects. Consider using encode_utf8() and then using the
 
1024
        # optimized versions, or maybe writing optimized unicode versions.
 
1025
        if not isinstance(path1, unicode):
 
1026
            raise TypeError("'path1' must be a unicode string, not %s: %r"
 
1027
                            % (type(path1), path1))
 
1028
        if not isinstance(path2, unicode):
 
1029
            raise TypeError("'path2' must be a unicode string, not %s: %r"
 
1030
                            % (type(path2), path2))
 
1031
        return cmp(MultiWalker._path_to_key(path1),
 
1032
                   MultiWalker._path_to_key(path2))
 
1033
 
 
1034
    @staticmethod
 
1035
    def _path_to_key(path):
 
1036
        dirname, basename = osutils.split(path)
 
1037
        return (dirname.split(u'/'), basename)
 
1038
 
 
1039
    def _lookup_by_file_id(self, extra_entries, other_tree, file_id):
 
1040
        """Lookup an inventory entry by file_id.
 
1041
 
 
1042
        This is called when an entry is missing in the normal order.
 
1043
        Generally this is because a file was either renamed, or it was
 
1044
        deleted/added. If the entry was found in the inventory and not in
 
1045
        extra_entries, it will be added to self._out_of_order_processed
 
1046
 
 
1047
        :param extra_entries: A dictionary of {file_id: (path, ie)}.  This
 
1048
            should be filled with entries that were found before they were
 
1049
            used. If file_id is present, it will be removed from the
 
1050
            dictionary.
 
1051
        :param other_tree: The Tree to search, in case we didn't find the entry
 
1052
            yet.
 
1053
        :param file_id: The file_id to look for
 
1054
        :return: (path, ie) if found or (None, None) if not present.
 
1055
        """
 
1056
        if file_id in extra_entries:
 
1057
            return extra_entries.pop(file_id)
 
1058
        # TODO: Is id2path better as the first call, or is
 
1059
        #       inventory[file_id] better as a first check?
 
1060
        try:
 
1061
            cur_path = other_tree.id2path(file_id)
 
1062
        except errors.NoSuchId:
 
1063
            cur_path = None
 
1064
        if cur_path is None:
 
1065
            return (None, None)
 
1066
        else:
 
1067
            self._out_of_order_processed.add(file_id)
 
1068
            cur_ie = other_tree.inventory[file_id]
 
1069
            return (cur_path, cur_ie)
 
1070
 
 
1071
    def iter_all(self):
 
1072
        """Match up the values in the different trees."""
 
1073
        for result in self._walk_master_tree():
 
1074
            yield result
 
1075
        self._finish_others()
 
1076
        for result in self._walk_others():
 
1077
            yield result
 
1078
 
 
1079
    def _walk_master_tree(self):
 
1080
        """First pass, walk all trees in lock-step.
 
1081
        
 
1082
        When we are done, all nodes in the master_tree will have been
 
1083
        processed. _other_walkers, _other_entries, and _others_extra will be
 
1084
        set on 'self' for future processing.
 
1085
        """
 
1086
        # This iterator has the most "inlining" done, because it tends to touch
 
1087
        # every file in the tree, while the others only hit nodes that don't
 
1088
        # match.
 
1089
        master_iterator = self._master_tree.iter_entries_by_dir()
 
1090
 
 
1091
        other_walkers = [other.iter_entries_by_dir()
 
1092
                         for other in self._other_trees]
 
1093
        other_entries = [self._step_one(walker) for walker in other_walkers]
 
1094
        # Track extra nodes in the other trees
 
1095
        others_extra = [{} for i in xrange(len(self._other_trees))]
 
1096
 
 
1097
        master_has_more = True
 
1098
        step_one = self._step_one
 
1099
        lookup_by_file_id = self._lookup_by_file_id
 
1100
        out_of_order_processed = self._out_of_order_processed
 
1101
 
 
1102
        while master_has_more:
 
1103
            (master_has_more, path, master_ie) = step_one(master_iterator)
 
1104
            if not master_has_more:
 
1105
                break
 
1106
 
 
1107
            file_id = master_ie.file_id
 
1108
            other_values = []
 
1109
            other_values_append = other_values.append
 
1110
            next_other_entries = []
 
1111
            next_other_entries_append = next_other_entries.append
 
1112
            for idx, (other_has_more, other_path, other_ie) in enumerate(other_entries):
 
1113
                if not other_has_more:
 
1114
                    other_values_append(lookup_by_file_id(
 
1115
                        others_extra[idx], self._other_trees[idx], file_id))
 
1116
                    next_other_entries_append((False, None, None))
 
1117
                elif file_id == other_ie.file_id:
 
1118
                    # This is the critical code path, as most of the entries
 
1119
                    # should match between most trees.
 
1120
                    other_values_append((other_path, other_ie))
 
1121
                    next_other_entries_append(step_one(other_walkers[idx]))
 
1122
                else:
 
1123
                    # This walker did not match, step it until it either
 
1124
                    # matches, or we know we are past the current walker.
 
1125
                    other_walker = other_walkers[idx]
 
1126
                    other_extra = others_extra[idx]
 
1127
                    while (other_has_more and
 
1128
                           self._cmp_path_by_dirblock(other_path, path) < 0):
 
1129
                        other_file_id = other_ie.file_id
 
1130
                        if other_file_id not in out_of_order_processed:
 
1131
                            other_extra[other_file_id] = (other_path, other_ie)
 
1132
                        other_has_more, other_path, other_ie = \
 
1133
                            step_one(other_walker)
 
1134
                    if other_has_more and other_ie.file_id == file_id:
 
1135
                        # We ended up walking to this point, match and step
 
1136
                        # again
 
1137
                        other_values_append((other_path, other_ie))
 
1138
                        other_has_more, other_path, other_ie = \
 
1139
                            step_one(other_walker)
 
1140
                    else:
 
1141
                        # This record isn't in the normal order, see if it
 
1142
                        # exists at all.
 
1143
                        other_values_append(lookup_by_file_id(
 
1144
                            other_extra, self._other_trees[idx], file_id))
 
1145
                    next_other_entries_append((other_has_more, other_path,
 
1146
                                               other_ie))
 
1147
            other_entries = next_other_entries
 
1148
 
 
1149
            # We've matched all the walkers, yield this datapoint
 
1150
            yield path, file_id, master_ie, other_values
 
1151
        self._other_walkers = other_walkers
 
1152
        self._other_entries = other_entries
 
1153
        self._others_extra = others_extra
 
1154
 
 
1155
    def _finish_others(self):
 
1156
        """Finish walking the other iterators, so we get all entries."""
 
1157
        for idx, info in enumerate(self._other_entries):
 
1158
            other_extra = self._others_extra[idx]
 
1159
            (other_has_more, other_path, other_ie) = info
 
1160
            while other_has_more:
 
1161
                other_file_id = other_ie.file_id
 
1162
                if other_file_id not in self._out_of_order_processed:
 
1163
                    other_extra[other_file_id] = (other_path, other_ie)
 
1164
                other_has_more, other_path, other_ie = \
 
1165
                    self._step_one(self._other_walkers[idx])
 
1166
        del self._other_entries
 
1167
 
 
1168
    def _walk_others(self):
 
1169
        """Finish up by walking all the 'deferred' nodes."""
 
1170
        # TODO: One alternative would be to grab all possible unprocessed
 
1171
        #       file_ids, and then sort by path, and then yield them. That
 
1172
        #       might ensure better ordering, in case a caller strictly
 
1173
        #       requires parents before children.
 
1174
        for idx, other_extra in enumerate(self._others_extra):
 
1175
            others = sorted(other_extra.itervalues(),
 
1176
                            key=lambda x: self._path_to_key(x[0]))
 
1177
            for other_path, other_ie in others:
 
1178
                file_id = other_ie.file_id
 
1179
                # We don't need to check out_of_order_processed here, because
 
1180
                # the lookup_by_file_id will be removing anything processed
 
1181
                # from the extras cache
 
1182
                other_extra.pop(file_id)
 
1183
                other_values = [(None, None) for i in xrange(idx)]
 
1184
                other_values.append((other_path, other_ie))
 
1185
                for alt_idx, alt_extra in enumerate(self._others_extra[idx+1:]):
 
1186
                    alt_idx = alt_idx + idx + 1
 
1187
                    alt_extra = self._others_extra[alt_idx]
 
1188
                    alt_tree = self._other_trees[alt_idx]
 
1189
                    other_values.append(self._lookup_by_file_id(
 
1190
                                            alt_extra, alt_tree, file_id))
 
1191
                yield other_path, file_id, None, other_values