~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: John Arbash Meinel
  • Date: 2008-10-30 00:55:00 UTC
  • mto: (3815.2.5 prepare-1.9)
  • mto: This revision was merged to the branch mainline in revision 3811.
  • Revision ID: john@arbash-meinel.com-20081030005500-r5cej1cxflqhs3io
Switch so that we are using a simple timestamp as the first action.

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
 
24
24
import bzrlib
25
25
from bzrlib import (
 
26
    conflicts as _mod_conflicts,
26
27
    delta,
27
28
    osutils,
28
29
    revision as _mod_revision,
29
 
    conflicts as _mod_conflicts,
 
30
    rules,
30
31
    symbol_versioning,
31
32
    )
32
33
from bzrlib.decorators import needs_read_lock
36
37
from bzrlib.inter import InterObject
37
38
from bzrlib.osutils import fingerprint_file
38
39
import bzrlib.revision
 
40
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
39
41
from bzrlib.trace import mutter, note
40
42
 
41
43
 
131
133
    def has_id(self, file_id):
132
134
        return self.inventory.has_id(file_id)
133
135
 
134
 
    __contains__ = has_id
 
136
    def __contains__(self, file_id):
 
137
        return self.has_id(file_id)
135
138
 
136
139
    def has_or_had_id(self, file_id):
137
140
        if file_id == self.inventory.root.file_id:
176
179
    def iter_entries_by_dir(self, specific_file_ids=None):
177
180
        """Walk the tree in 'by_dir' order.
178
181
 
179
 
        This will yield each entry in the tree as a (path, entry) tuple. The
180
 
        order that they are yielded is: the contents of a directory are 
181
 
        preceeded by the parent of a directory, and all the contents of a 
182
 
        directory are grouped together.
 
182
        This will yield each entry in the tree as a (path, entry) tuple.
 
183
        The order that they are yielded is:
 
184
 
 
185
        Directories are walked in a depth-first lexicographical order,
 
186
        however, whenever a directory is reached, all of its direct child
 
187
        nodes are yielded in  lexicographical order before yielding the
 
188
        grandchildren.
 
189
 
 
190
        For example, in the tree::
 
191
 
 
192
           a/
 
193
             b/
 
194
               c
 
195
             d/
 
196
               e
 
197
           f/
 
198
             g
 
199
 
 
200
        The yield order (ignoring root) would be::
 
201
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
183
202
        """
184
203
        return self.inventory.iter_entries_by_dir(
185
204
            specific_file_ids=specific_file_ids)
245
264
        """
246
265
        raise NotImplementedError(self.get_file)
247
266
 
 
267
    def get_file_text(self, file_id, path=None):
 
268
        """Return the byte content of a file.
 
269
 
 
270
        :param file_id: The file_id of the file.
 
271
        :param path: The path of the file.
 
272
        If both file_id and path are supplied, an implementation may use
 
273
        either one.
 
274
        """
 
275
        my_file = self.get_file(file_id, path)
 
276
        try:
 
277
            return my_file.read()
 
278
        finally:
 
279
            my_file.close()
 
280
 
 
281
    def get_file_lines(self, file_id, path=None):
 
282
        """Return the content of a file, as lines.
 
283
 
 
284
        :param file_id: The file_id of the file.
 
285
        :param path: The path of the file.
 
286
        If both file_id and path are supplied, an implementation may use
 
287
        either one.
 
288
        """
 
289
        return osutils.split_lines(self.get_file_text(file_id, path))
 
290
 
248
291
    def get_file_mtime(self, file_id, path=None):
249
292
        """Return the modification time for a file.
250
293
 
357
400
        return vf.plan_lca_merge(last_revision_a, last_revision_b,
358
401
                                 last_revision_base)
359
402
 
 
403
    def _iter_parent_trees(self):
 
404
        """Iterate through parent trees, defaulting to Tree.revision_tree."""
 
405
        for revision_id in self.get_parent_ids():
 
406
            try:
 
407
                yield self.revision_tree(revision_id)
 
408
            except errors.NoSuchRevisionInTree:
 
409
                yield self.repository.revision_tree(revision_id)
 
410
 
 
411
    @staticmethod
 
412
    def _file_revision(revision_tree, file_id):
 
413
        """Determine the revision associated with a file in a given tree."""
 
414
        revision_tree.lock_read()
 
415
        try:
 
416
            return revision_tree.inventory[file_id].revision
 
417
        finally:
 
418
            revision_tree.unlock()
 
419
 
360
420
    def _get_file_revision(self, file_id, vf, tree_revision):
361
 
        def file_revision(revision_tree):
362
 
            revision_tree.lock_read()
363
 
            try:
364
 
                return revision_tree.inventory[file_id].revision
365
 
            finally:
366
 
                revision_tree.unlock()
367
 
 
368
 
        def iter_parent_trees():
369
 
            for revision_id in self.get_parent_ids():
370
 
                try:
371
 
                    yield self.revision_tree(revision_id)
372
 
                except:
373
 
                    yield self.repository.revision_tree(revision_id)
374
 
 
375
 
        if getattr(self, '_get_weave', None) is None:
 
421
        """Ensure that file_id, tree_revision is in vf to plan the merge."""
 
422
 
 
423
        if getattr(self, '_repository', None) is None:
376
424
            last_revision = tree_revision
377
 
            parent_revisions = [file_revision(t) for t in iter_parent_trees()]
378
 
            vf.add_lines(last_revision, parent_revisions,
 
425
            parent_keys = [(file_id, self._file_revision(t, file_id)) for t in
 
426
                self._iter_parent_trees()]
 
427
            vf.add_lines((file_id, last_revision), parent_keys,
379
428
                         self.get_file(file_id).readlines())
380
429
            repo = self.branch.repository
381
 
            transaction = repo.get_transaction()
382
 
            base_vf = repo.weave_store.get_weave(file_id, transaction)
 
430
            base_vf = repo.texts
383
431
        else:
384
 
            last_revision = file_revision(self)
385
 
            base_vf = self._get_weave(file_id)
386
 
        vf.fallback_versionedfiles.append(base_vf)
 
432
            last_revision = self._file_revision(self, file_id)
 
433
            base_vf = self._repository.texts
 
434
        if base_vf not in vf.fallback_versionedfiles:
 
435
            vf.fallback_versionedfiles.append(base_vf)
387
436
        return last_revision
388
437
 
389
438
    inventory = property(_get_inventory,
432
481
        """
433
482
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
434
483
 
 
484
    def iter_children(self, file_id):
 
485
        entry = self.iter_entries_by_dir([file_id]).next()[1]
 
486
        for child in getattr(entry, 'children', {}).itervalues():
 
487
            yield child.file_id
 
488
 
 
489
    @symbol_versioning.deprecated_method(symbol_versioning.one_six)
435
490
    def print_file(self, file_id):
436
491
        """Print file with id `file_id` to stdout."""
437
492
        import sys
509
564
        """
510
565
        raise NotImplementedError(self.walkdirs)
511
566
 
 
567
    def iter_search_rules(self, path_names, pref_names=None,
 
568
        _default_searcher=rules._per_user_searcher):
 
569
        """Find the preferences for filenames in a tree.
 
570
 
 
571
        :param path_names: an iterable of paths to find attributes for.
 
572
          Paths are given relative to the root of the tree.
 
573
        :param pref_names: the list of preferences to lookup - None for all
 
574
        :param _default_searcher: private parameter to assist testing - don't use
 
575
        :return: an iterator of tuple sequences, one per path-name.
 
576
          See _RulesSearcher.get_items for details on the tuple sequence.
 
577
        """
 
578
        searcher = self._get_rules_searcher(_default_searcher)
 
579
        if searcher is not None:
 
580
            if pref_names is not None:
 
581
                for path in path_names:
 
582
                    yield searcher.get_selected_items(path, pref_names)
 
583
            else:
 
584
                for path in path_names:
 
585
                    yield searcher.get_items(path)
 
586
 
 
587
    @needs_read_lock
 
588
    def _get_rules_searcher(self, default_searcher):
 
589
        """Get the RulesSearcher for this tree given the default one."""
 
590
        searcher = default_searcher
 
591
        return searcher
 
592
 
512
593
 
513
594
class EmptyTree(Tree):
514
595
 
593
674
    return 'wtf?'
594
675
 
595
676
    
596
 
 
 
677
@deprecated_function(deprecated_in((1, 9, 0)))
597
678
def find_renames(old_inv, new_inv):
598
679
    for file_id in old_inv:
599
680
        if file_id not in new_inv:
602
683
        new_name = new_inv.id2path(file_id)
603
684
        if old_name != new_name:
604
685
            yield (old_name, new_name)
605
 
            
 
686
 
606
687
 
607
688
def find_ids_across_trees(filenames, trees, require_versioned=True):
608
689
    """Find the ids corresponding to specified filenames.
669
750
            for tree in trees:
670
751
                if not tree.has_id(file_id):
671
752
                    continue
672
 
                entry = tree.inventory[file_id]
673
 
                for child in getattr(entry, 'children', {}).itervalues():
674
 
                    if child.file_id not in interesting_ids:
675
 
                        new_pending.add(child.file_id)
 
753
                for child_id in tree.iter_children(file_id):
 
754
                    if child_id not in interesting_ids:
 
755
                        new_pending.add(child_id)
676
756
        interesting_ids.update(new_pending)
677
757
        pending = new_pending
678
758
    return interesting_ids
781
861
        else:
782
862
            all_unversioned = deque()
783
863
        to_paths = {}
784
 
        from_entries_by_dir = list(self.source.inventory.iter_entries_by_dir(
 
864
        from_entries_by_dir = list(self.source.iter_entries_by_dir(
785
865
            specific_file_ids=specific_file_ids))
786
866
        from_data = dict((e.file_id, (p, e)) for p, e in from_entries_by_dir)
787
 
        to_entries_by_dir = list(self.target.inventory.iter_entries_by_dir(
 
867
        to_entries_by_dir = list(self.target.iter_entries_by_dir(
788
868
            specific_file_ids=specific_file_ids))
789
869
        num_entries = len(from_entries_by_dir) + len(to_entries_by_dir)
790
870
        entry_count = 0
882
962
            if file_id in to_paths:
883
963
                # already returned
884
964
                continue
885
 
            if not file_id in self.target.inventory:
 
965
            if not file_id in self.target.all_file_ids():
886
966
                # common case - paths we have not emitted are not present in
887
967
                # target.
888
968
                to_path = None
898
978
                self.source._comparison_data(from_entry, path)
899
979
            kind = (from_kind, None)
900
980
            executable = (from_executable, None)
901
 
            changed_content = True
 
981
            changed_content = from_kind is not None
902
982
            # the parent's path is necessarily known at this point.
903
983
            yield(file_id, (path, to_path), changed_content, versioned, parent,
904
984
                  name, kind, executable)
 
985
 
 
986
 
 
987
class MultiWalker(object):
 
988
    """Walk multiple trees simultaneously, getting combined results."""
 
989
 
 
990
    # Note: This could be written to not assume you can do out-of-order
 
991
    #       lookups. Instead any nodes that don't match in all trees could be
 
992
    #       marked as 'deferred', and then returned in the final cleanup loop.
 
993
    #       For now, I think it is "nicer" to return things as close to the
 
994
    #       "master_tree" order as we can.
 
995
 
 
996
    def __init__(self, master_tree, other_trees):
 
997
        """Create a new MultiWalker.
 
998
 
 
999
        All trees being walked must implement "iter_entries_by_dir()", such
 
1000
        that they yield (path, object) tuples, where that object will have a
 
1001
        '.file_id' member, that can be used to check equality.
 
1002
 
 
1003
        :param master_tree: All trees will be 'slaved' to the master_tree such
 
1004
            that nodes in master_tree will be used as 'first-pass' sync points.
 
1005
            Any nodes that aren't in master_tree will be merged in a second
 
1006
            pass.
 
1007
        :param other_trees: A list of other trees to walk simultaneously.
 
1008
        """
 
1009
        self._master_tree = master_tree
 
1010
        self._other_trees = other_trees
 
1011
 
 
1012
        # Keep track of any nodes that were properly processed just out of
 
1013
        # order, that way we don't return them at the end, we don't have to
 
1014
        # track *all* processed file_ids, just the out-of-order ones
 
1015
        self._out_of_order_processed = set()
 
1016
 
 
1017
    @staticmethod
 
1018
    def _step_one(iterator):
 
1019
        """Step an iter_entries_by_dir iterator.
 
1020
 
 
1021
        :return: (has_more, path, ie)
 
1022
            If has_more is False, path and ie will be None.
 
1023
        """
 
1024
        try:
 
1025
            path, ie = iterator.next()
 
1026
        except StopIteration:
 
1027
            return False, None, None
 
1028
        else:
 
1029
            return True, path, ie
 
1030
 
 
1031
    @staticmethod
 
1032
    def _cmp_path_by_dirblock(path1, path2):
 
1033
        """Compare two paths based on what directory they are in.
 
1034
 
 
1035
        This generates a sort order, such that all children of a directory are
 
1036
        sorted together, and grandchildren are in the same order as the
 
1037
        children appear. But all grandchildren come after all children.
 
1038
 
 
1039
        :param path1: first path
 
1040
        :param path2: the second path
 
1041
        :return: negative number if ``path1`` comes first,
 
1042
            0 if paths are equal
 
1043
            and a positive number if ``path2`` sorts first
 
1044
        """
 
1045
        # Shortcut this special case
 
1046
        if path1 == path2:
 
1047
            return 0
 
1048
        # This is stolen from _dirstate_helpers_py.py, only switching it to
 
1049
        # Unicode objects. Consider using encode_utf8() and then using the
 
1050
        # optimized versions, or maybe writing optimized unicode versions.
 
1051
        if not isinstance(path1, unicode):
 
1052
            raise TypeError("'path1' must be a unicode string, not %s: %r"
 
1053
                            % (type(path1), path1))
 
1054
        if not isinstance(path2, unicode):
 
1055
            raise TypeError("'path2' must be a unicode string, not %s: %r"
 
1056
                            % (type(path2), path2))
 
1057
        return cmp(MultiWalker._path_to_key(path1),
 
1058
                   MultiWalker._path_to_key(path2))
 
1059
 
 
1060
    @staticmethod
 
1061
    def _path_to_key(path):
 
1062
        dirname, basename = osutils.split(path)
 
1063
        return (dirname.split(u'/'), basename)
 
1064
 
 
1065
    def _lookup_by_file_id(self, extra_entries, other_tree, file_id):
 
1066
        """Lookup an inventory entry by file_id.
 
1067
 
 
1068
        This is called when an entry is missing in the normal order.
 
1069
        Generally this is because a file was either renamed, or it was
 
1070
        deleted/added. If the entry was found in the inventory and not in
 
1071
        extra_entries, it will be added to self._out_of_order_processed
 
1072
 
 
1073
        :param extra_entries: A dictionary of {file_id: (path, ie)}.  This
 
1074
            should be filled with entries that were found before they were
 
1075
            used. If file_id is present, it will be removed from the
 
1076
            dictionary.
 
1077
        :param other_tree: The Tree to search, in case we didn't find the entry
 
1078
            yet.
 
1079
        :param file_id: The file_id to look for
 
1080
        :return: (path, ie) if found or (None, None) if not present.
 
1081
        """
 
1082
        if file_id in extra_entries:
 
1083
            return extra_entries.pop(file_id)
 
1084
        # TODO: Is id2path better as the first call, or is
 
1085
        #       inventory[file_id] better as a first check?
 
1086
        try:
 
1087
            cur_path = other_tree.id2path(file_id)
 
1088
        except errors.NoSuchId:
 
1089
            cur_path = None
 
1090
        if cur_path is None:
 
1091
            return (None, None)
 
1092
        else:
 
1093
            self._out_of_order_processed.add(file_id)
 
1094
            cur_ie = other_tree.inventory[file_id]
 
1095
            return (cur_path, cur_ie)
 
1096
 
 
1097
    def iter_all(self):
 
1098
        """Match up the values in the different trees."""
 
1099
        for result in self._walk_master_tree():
 
1100
            yield result
 
1101
        self._finish_others()
 
1102
        for result in self._walk_others():
 
1103
            yield result
 
1104
 
 
1105
    def _walk_master_tree(self):
 
1106
        """First pass, walk all trees in lock-step.
 
1107
        
 
1108
        When we are done, all nodes in the master_tree will have been
 
1109
        processed. _other_walkers, _other_entries, and _others_extra will be
 
1110
        set on 'self' for future processing.
 
1111
        """
 
1112
        # This iterator has the most "inlining" done, because it tends to touch
 
1113
        # every file in the tree, while the others only hit nodes that don't
 
1114
        # match.
 
1115
        master_iterator = self._master_tree.iter_entries_by_dir()
 
1116
 
 
1117
        other_walkers = [other.iter_entries_by_dir()
 
1118
                         for other in self._other_trees]
 
1119
        other_entries = [self._step_one(walker) for walker in other_walkers]
 
1120
        # Track extra nodes in the other trees
 
1121
        others_extra = [{} for i in xrange(len(self._other_trees))]
 
1122
 
 
1123
        master_has_more = True
 
1124
        step_one = self._step_one
 
1125
        lookup_by_file_id = self._lookup_by_file_id
 
1126
        out_of_order_processed = self._out_of_order_processed
 
1127
 
 
1128
        while master_has_more:
 
1129
            (master_has_more, path, master_ie) = step_one(master_iterator)
 
1130
            if not master_has_more:
 
1131
                break
 
1132
 
 
1133
            file_id = master_ie.file_id
 
1134
            other_values = []
 
1135
            other_values_append = other_values.append
 
1136
            next_other_entries = []
 
1137
            next_other_entries_append = next_other_entries.append
 
1138
            for idx, (other_has_more, other_path, other_ie) in enumerate(other_entries):
 
1139
                if not other_has_more:
 
1140
                    other_values_append(lookup_by_file_id(
 
1141
                        others_extra[idx], self._other_trees[idx], file_id))
 
1142
                    next_other_entries_append((False, None, None))
 
1143
                elif file_id == other_ie.file_id:
 
1144
                    # This is the critical code path, as most of the entries
 
1145
                    # should match between most trees.
 
1146
                    other_values_append((other_path, other_ie))
 
1147
                    next_other_entries_append(step_one(other_walkers[idx]))
 
1148
                else:
 
1149
                    # This walker did not match, step it until it either
 
1150
                    # matches, or we know we are past the current walker.
 
1151
                    other_walker = other_walkers[idx]
 
1152
                    other_extra = others_extra[idx]
 
1153
                    while (other_has_more and
 
1154
                           self._cmp_path_by_dirblock(other_path, path) < 0):
 
1155
                        other_file_id = other_ie.file_id
 
1156
                        if other_file_id not in out_of_order_processed:
 
1157
                            other_extra[other_file_id] = (other_path, other_ie)
 
1158
                        other_has_more, other_path, other_ie = \
 
1159
                            step_one(other_walker)
 
1160
                    if other_has_more and other_ie.file_id == file_id:
 
1161
                        # We ended up walking to this point, match and step
 
1162
                        # again
 
1163
                        other_values_append((other_path, other_ie))
 
1164
                        other_has_more, other_path, other_ie = \
 
1165
                            step_one(other_walker)
 
1166
                    else:
 
1167
                        # This record isn't in the normal order, see if it
 
1168
                        # exists at all.
 
1169
                        other_values_append(lookup_by_file_id(
 
1170
                            other_extra, self._other_trees[idx], file_id))
 
1171
                    next_other_entries_append((other_has_more, other_path,
 
1172
                                               other_ie))
 
1173
            other_entries = next_other_entries
 
1174
 
 
1175
            # We've matched all the walkers, yield this datapoint
 
1176
            yield path, file_id, master_ie, other_values
 
1177
        self._other_walkers = other_walkers
 
1178
        self._other_entries = other_entries
 
1179
        self._others_extra = others_extra
 
1180
 
 
1181
    def _finish_others(self):
 
1182
        """Finish walking the other iterators, so we get all entries."""
 
1183
        for idx, info in enumerate(self._other_entries):
 
1184
            other_extra = self._others_extra[idx]
 
1185
            (other_has_more, other_path, other_ie) = info
 
1186
            while other_has_more:
 
1187
                other_file_id = other_ie.file_id
 
1188
                if other_file_id not in self._out_of_order_processed:
 
1189
                    other_extra[other_file_id] = (other_path, other_ie)
 
1190
                other_has_more, other_path, other_ie = \
 
1191
                    self._step_one(self._other_walkers[idx])
 
1192
        del self._other_entries
 
1193
 
 
1194
    def _walk_others(self):
 
1195
        """Finish up by walking all the 'deferred' nodes."""
 
1196
        # TODO: One alternative would be to grab all possible unprocessed
 
1197
        #       file_ids, and then sort by path, and then yield them. That
 
1198
        #       might ensure better ordering, in case a caller strictly
 
1199
        #       requires parents before children.
 
1200
        for idx, other_extra in enumerate(self._others_extra):
 
1201
            others = sorted(other_extra.itervalues(),
 
1202
                            key=lambda x: self._path_to_key(x[0]))
 
1203
            for other_path, other_ie in others:
 
1204
                file_id = other_ie.file_id
 
1205
                # We don't need to check out_of_order_processed here, because
 
1206
                # the lookup_by_file_id will be removing anything processed
 
1207
                # from the extras cache
 
1208
                other_extra.pop(file_id)
 
1209
                other_values = [(None, None) for i in xrange(idx)]
 
1210
                other_values.append((other_path, other_ie))
 
1211
                for alt_idx, alt_extra in enumerate(self._others_extra[idx+1:]):
 
1212
                    alt_idx = alt_idx + idx + 1
 
1213
                    alt_extra = self._others_extra[alt_idx]
 
1214
                    alt_tree = self._other_trees[alt_idx]
 
1215
                    other_values.append(self._lookup_by_file_id(
 
1216
                                            alt_extra, alt_tree, file_id))
 
1217
                yield other_path, file_id, None, other_values