~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-20 15:06:17 UTC
  • mto: This revision was merged to the branch mainline in revision 5836.
  • Revision ID: john@arbash-meinel.com-20110420150617-i41caxgemg32tq1r
Start adding tests that _worth_saving_limit works as expected.

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
20
import os
23
21
 
24
22
from bzrlib.lazy_import import lazy_import
37
35
    rules,
38
36
    trace,
39
37
    )
40
 
from bzrlib.i18n import gettext
41
38
""")
42
39
 
43
40
from bzrlib.decorators import needs_read_lock
44
41
from bzrlib.inter import InterObject
45
 
from bzrlib.symbol_versioning import (
46
 
    deprecated_in,
47
 
    deprecated_method,
48
 
    )
49
42
 
50
43
 
51
44
class Tree(object):
61
54
    trees or versioned trees.
62
55
    """
63
56
 
64
 
    def has_versioned_directories(self):
65
 
        """Whether this tree can contain explicitly versioned directories.
66
 
 
67
 
        This defaults to True, but some implementations may want to override
68
 
        it.
69
 
        """
70
 
        return True
71
 
 
72
57
    def changes_from(self, other, want_unchanged=False, specific_files=None,
73
58
        extra_trees=None, require_versioned=False, include_root=False,
74
59
        want_unversioned=False):
138
123
    def has_id(self, file_id):
139
124
        raise NotImplementedError(self.has_id)
140
125
 
141
 
    @deprecated_method(deprecated_in((2, 4, 0)))
142
126
    def __contains__(self, file_id):
143
127
        return self.has_id(file_id)
144
128
 
153
137
        """
154
138
        return False
155
139
 
 
140
    def __iter__(self):
 
141
        """Yield all file ids in this tree."""
 
142
        raise NotImplementedError(self.__iter__)
 
143
 
156
144
    def all_file_ids(self):
157
145
        """Iterate through all file ids, including ids for missing files."""
158
 
        raise NotImplementedError(self.all_file_ids)
 
146
        return set(self.inventory)
159
147
 
160
148
    def id2path(self, file_id):
161
149
        """Return the path for a file id.
186
174
             g
187
175
 
188
176
        The yield order (ignoring root) would be::
189
 
 
190
177
          a, f, a/b, a/d, a/b/c, a/d/e, f/g
191
178
 
192
179
        :param yield_parents: If True, yield the parents from the root leading
195
182
        """
196
183
        raise NotImplementedError(self.iter_entries_by_dir)
197
184
 
198
 
    def iter_child_entries(self, file_id, path=None):
199
 
        """Iterate over the children of a directory or tree reference.
200
 
 
201
 
        :param file_id: File id of the directory/tree-reference
202
 
        :param path: Optional path of the directory
203
 
        :raise NoSuchId: When the file_id does not exist
204
 
        :return: Iterator over entries in the directory
205
 
        """
206
 
        raise NotImplementedError(self.iter_child_entries)
207
 
 
208
185
    def list_files(self, include_root=False, from_dir=None, recursive=True):
209
186
        """List all files in this tree.
210
187
 
298
275
 
299
276
        :param file_id: The file_id of the file.
300
277
        :param path: The path of the file.
301
 
 
302
278
        If both file_id and path are supplied, an implementation may use
303
279
        either one.
304
 
 
305
 
        :returns: A single byte string for the whole file.
306
280
        """
307
281
        my_file = self.get_file(file_id, path)
308
282
        try:
315
289
 
316
290
        :param file_id: The file_id of the file.
317
291
        :param path: The path of the file.
318
 
 
319
292
        If both file_id and path are supplied, an implementation may use
320
293
        either one.
321
294
        """
322
295
        return osutils.split_lines(self.get_file_text(file_id, path))
323
296
 
324
 
    def get_file_verifier(self, file_id, path=None, stat_value=None):
325
 
        """Return a verifier for a file.
326
 
 
327
 
        The default implementation returns a sha1.
328
 
 
329
 
        :param file_id: The handle for this file.
330
 
        :param path: The path that this file can be found at.
331
 
            These must point to the same object.
332
 
        :param stat_value: Optional stat value for the object
333
 
        :return: Tuple with verifier name and verifier data
334
 
        """
335
 
        return ("SHA1", self.get_file_sha1(file_id, path=path,
336
 
            stat_value=stat_value))
337
 
 
338
 
    def get_file_sha1(self, file_id, path=None, stat_value=None):
 
297
    def get_file_sha1(self, file_id, path=None):
339
298
        """Return the SHA1 file for a file.
340
299
 
341
 
        :note: callers should use get_file_verifier instead
342
 
            where possible, as the underlying repository implementation may
343
 
            have quicker access to a non-sha1 verifier.
344
 
 
345
300
        :param file_id: The handle for this file.
346
301
        :param path: The path that this file can be found at.
347
302
            These must point to the same object.
348
 
        :param stat_value: Optional stat value for the object
349
303
        """
350
304
        raise NotImplementedError(self.get_file_sha1)
351
305
 
367
321
        """
368
322
        raise NotImplementedError(self.get_file_size)
369
323
 
 
324
    def get_file_by_path(self, path):
 
325
        raise NotImplementedError(self.get_file_by_path)
 
326
 
370
327
    def is_executable(self, file_id, path=None):
371
328
        """Check if a file is executable.
372
329
 
402
359
            cur_file = (self.get_file_text(file_id),)
403
360
            yield identifier, cur_file
404
361
 
405
 
    def get_symlink_target(self, file_id, path=None):
 
362
    def get_symlink_target(self, file_id):
406
363
        """Get the target for a given file_id.
407
364
 
408
365
        It is assumed that the caller already knows that file_id is referencing
409
366
        a symlink.
410
367
        :param file_id: Handle for the symlink entry.
411
 
        :param path: The path of the file.
412
 
        If both file_id and path are supplied, an implementation may use
413
 
        either one.
414
368
        :return: The path the symlink points to.
415
369
        """
416
370
        raise NotImplementedError(self.get_symlink_target)
417
371
 
 
372
 
418
373
    def get_root_id(self):
419
374
        """Return the file_id for the root of this tree."""
420
375
        raise NotImplementedError(self.get_root_id)
542
497
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
543
498
 
544
499
    def iter_children(self, file_id):
545
 
        """Iterate over the file ids of the children of an entry.
546
 
 
547
 
        :param file_id: File id of the entry
548
 
        :return: Iterator over child file ids.
549
 
        """
550
 
        raise NotImplementedError(self.iter_children)
 
500
        entry = self.iter_entries_by_dir([file_id]).next()[1]
 
501
        for child in getattr(entry, 'children', {}).itervalues():
 
502
            yield child.file_id
551
503
 
552
504
    def lock_read(self):
553
505
        """Lock this tree for multiple read only operations.
554
 
 
 
506
        
555
507
        :return: A bzrlib.lock.LogicalLockResult.
556
508
        """
557
509
        pass
644
596
        prefs = self.iter_search_rules([path], filter_pref_names).next()
645
597
        stk = filters._get_filter_stack_for(prefs)
646
598
        if 'filters' in debug.debug_flags:
647
 
            trace.note(gettext("*** {0} content-filter: {1} => {2!r}").format(path,prefs,stk))
 
599
            trace.note("*** %s content-filter: %s => %r" % (path,prefs,stk))
648
600
        return stk
649
601
 
650
602
    def _content_filter_stack_provider(self):
781
733
            yield cur_path
782
734
        # all done.
783
735
 
784
 
    @deprecated_method(deprecated_in((2, 5, 0)))
785
736
    def _get_inventory(self):
786
737
        return self._inventory
787
738
 
788
739
    inventory = property(_get_inventory,
789
740
                         doc="Inventory of this Tree")
790
741
 
791
 
    def _get_root_inventory(self):
792
 
        return self._inventory
793
 
 
794
 
    root_inventory = property(_get_root_inventory,
795
 
        doc="Root inventory of this tree")
796
 
 
797
 
    def _unpack_file_id(self, file_id):
798
 
        """Find the inventory and inventory file id for a tree file id.
799
 
 
800
 
        :param file_id: The tree file id, as bytestring or tuple
801
 
        :return: Inventory and inventory file id
802
 
        """
803
 
        if isinstance(file_id, tuple):
804
 
            if len(file_id) != 1:
805
 
                raise ValueError("nested trees not yet supported: %r" % file_id)
806
 
            file_id = file_id[0]
807
 
        return self.root_inventory, file_id
808
 
 
809
742
    @needs_read_lock
810
743
    def path2id(self, path):
811
744
        """Return the id for path in this tree."""
812
 
        return self._path2inv_file_id(path)[1]
813
 
 
814
 
    def _path2inv_file_id(self, path):
815
 
        """Lookup a inventory and inventory file id by path.
816
 
 
817
 
        :param path: Path to look up
818
 
        :return: tuple with inventory and inventory file id
819
 
        """
820
 
        # FIXME: Support nested trees
821
 
        return self.root_inventory, self.root_inventory.path2id(path)
 
745
        return self._inventory.path2id(path)
822
746
 
823
747
    def id2path(self, file_id):
824
748
        """Return the path for a file id.
825
749
 
826
750
        :raises NoSuchId:
827
751
        """
828
 
        inventory, file_id = self._unpack_file_id(file_id)
829
 
        return inventory.id2path(file_id)
 
752
        return self.inventory.id2path(file_id)
830
753
 
831
754
    def has_id(self, file_id):
832
 
        inventory, file_id = self._unpack_file_id(file_id)
833
 
        return inventory.has_id(file_id)
 
755
        return self.inventory.has_id(file_id)
834
756
 
835
757
    def has_or_had_id(self, file_id):
836
 
        inventory, file_id = self._unpack_file_id(file_id)
837
 
        return inventory.has_id(file_id)
838
 
 
839
 
    def all_file_ids(self):
840
 
        return set(
841
 
            [entry.file_id for path, entry in self.iter_entries_by_dir()])
842
 
 
843
 
    @deprecated_method(deprecated_in((2, 4, 0)))
 
758
        return self.inventory.has_id(file_id)
 
759
 
844
760
    def __iter__(self):
845
 
        return iter(self.all_file_ids())
 
761
        return iter(self.inventory)
846
762
 
847
763
    def filter_unversioned_files(self, paths):
848
764
        """Filter out paths that are versioned.
852
768
        # NB: we specifically *don't* call self.has_filename, because for
853
769
        # WorkingTrees that can indicate files that exist on disk but that
854
770
        # are not versioned.
855
 
        return set((p for p in paths if self.path2id(p) is None))
 
771
        pred = self.inventory.has_filename
 
772
        return set((p for p in paths if not pred(p)))
856
773
 
857
774
    @needs_read_lock
858
775
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
867
784
            down to specific_file_ids that have been requested. This has no
868
785
            impact if specific_file_ids is None.
869
786
        """
870
 
        if specific_file_ids is None:
871
 
            inventory_file_ids = None
872
 
        else:
873
 
            inventory_file_ids = []
874
 
            for tree_file_id in specific_file_ids:
875
 
                inventory, inv_file_id = self._unpack_file_id(tree_file_id)
876
 
                if not inventory is self.root_inventory: # for now
877
 
                    raise AssertionError("%r != %r" % (
878
 
                        inventory, self.root_inventory))
879
 
                inventory_file_ids.append(inv_file_id)
880
 
        # FIXME: Handle nested trees
881
 
        return self.root_inventory.iter_entries_by_dir(
882
 
            specific_file_ids=inventory_file_ids, yield_parents=yield_parents)
883
 
 
884
 
    @needs_read_lock
885
 
    def iter_child_entries(self, file_id, path=None):
886
 
        inv, inv_file_id = self._unpack_file_id(file_id)
887
 
        return inv[inv_file_id].children.itervalues()
888
 
 
889
 
    @deprecated_method(deprecated_in((2, 5, 0)))
 
787
        return self.inventory.iter_entries_by_dir(
 
788
            specific_file_ids=specific_file_ids, yield_parents=yield_parents)
 
789
 
890
790
    def get_file_by_path(self, path):
891
 
        return self.get_file(self.path2id(path), path)
892
 
 
893
 
    def iter_children(self, file_id, path=None):
894
 
        """See Tree.iter_children."""
895
 
        entry = self.iter_entries_by_dir([file_id]).next()[1]
896
 
        for child in getattr(entry, 'children', {}).itervalues():
897
 
            yield child.file_id
 
791
        return self.get_file(self._inventory.path2id(path), path)
 
792
 
 
793
 
 
794
######################################################################
 
795
# diff
 
796
 
 
797
# TODO: Merge these two functions into a single one that can operate
 
798
# on either a whole tree or a set of files.
 
799
 
 
800
# TODO: Return the diff in order by filename, not by category or in
 
801
# random order.  Can probably be done by lock-stepping through the
 
802
# filenames from both trees.
 
803
 
 
804
 
 
805
def file_status(filename, old_tree, new_tree):
 
806
    """Return single-letter status, old and new names for a file.
 
807
 
 
808
    The complexity here is in deciding how to represent renames;
 
809
    many complex cases are possible.
 
810
    """
 
811
    old_inv = old_tree.inventory
 
812
    new_inv = new_tree.inventory
 
813
    new_id = new_inv.path2id(filename)
 
814
    old_id = old_inv.path2id(filename)
 
815
 
 
816
    if not new_id and not old_id:
 
817
        # easy: doesn't exist in either; not versioned at all
 
818
        if new_tree.is_ignored(filename):
 
819
            return 'I', None, None
 
820
        else:
 
821
            return '?', None, None
 
822
    elif new_id:
 
823
        # There is now a file of this name, great.
 
824
        pass
 
825
    else:
 
826
        # There is no longer a file of this name, but we can describe
 
827
        # what happened to the file that used to have
 
828
        # this name.  There are two possibilities: either it was
 
829
        # deleted entirely, or renamed.
 
830
        if new_inv.has_id(old_id):
 
831
            return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
 
832
        else:
 
833
            return 'D', old_inv.id2path(old_id), None
 
834
 
 
835
    # if the file_id is new in this revision, it is added
 
836
    if new_id and not old_inv.has_id(new_id):
 
837
        return 'A'
 
838
 
 
839
    # if there used to be a file of this name, but that ID has now
 
840
    # disappeared, it is deleted
 
841
    if old_id and not new_inv.has_id(old_id):
 
842
        return 'D'
 
843
 
 
844
    return 'wtf?'
898
845
 
899
846
 
900
847
def find_ids_across_trees(filenames, trees, require_versioned=True):
907
854
        None)
908
855
    :param trees: The trees to find file_ids within
909
856
    :param require_versioned: if true, all specified filenames must occur in
910
 
        at least one tree.
 
857
    at least one tree.
911
858
    :return: a set of file ids for the specified filenames and their children.
912
859
    """
913
860
    if not filenames:
989
936
 
990
937
    _optimisers = []
991
938
 
992
 
    @classmethod
993
 
    def is_compatible(kls, source, target):
994
 
        # The default implementation is naive and uses the public API, so
995
 
        # it works for all trees.
996
 
        return True
997
 
 
998
939
    def _changes_from_entries(self, source_entry, target_entry,
999
940
        source_path=None, target_path=None):
1000
941
        """Generate a iter_changes tuple between source_entry and target_entry.
1048
989
        if source_kind != target_kind:
1049
990
            changed_content = True
1050
991
        elif source_kind == 'file':
1051
 
            if not self.file_content_matches(file_id, file_id, source_path,
1052
 
                    target_path, source_stat, target_stat):
 
992
            if (self.source.get_file_sha1(file_id, source_path, source_stat) !=
 
993
                self.target.get_file_sha1(file_id, target_path, target_stat)):
1053
994
                changed_content = True
1054
995
        elif source_kind == 'symlink':
1055
996
            if (self.source.get_symlink_target(file_id) !=
1056
997
                self.target.get_symlink_target(file_id)):
1057
998
                changed_content = True
1058
 
        elif source_kind == 'tree-reference':
1059
 
            if (self.source.get_reference_revision(file_id, source_path)
1060
 
                != self.target.get_reference_revision(file_id, target_path)):
 
999
            # XXX: Yes, the indentation below is wrong. But fixing it broke
 
1000
            # test_merge.TestMergerEntriesLCAOnDisk.
 
1001
            # test_nested_tree_subtree_renamed_and_modified. We'll wait for
 
1002
            # the fix from bzr.dev -- vila 2009026
 
1003
            elif source_kind == 'tree-reference':
 
1004
                if (self.source.get_reference_revision(file_id, source_path)
 
1005
                    != self.target.get_reference_revision(file_id, target_path)):
1061
1006
                    changed_content = True
1062
1007
        parent = (source_parent, target_parent)
1063
1008
        name = (source_name, target_name)
1278
1223
        :param file_id: The file_id to lookup.
1279
1224
        """
1280
1225
        try:
1281
 
            inventory = tree.root_inventory
 
1226
            inventory = tree.inventory
1282
1227
        except NotImplementedError:
1283
1228
            # No inventory available.
1284
1229
            try:
1359
1304
                        if old_entry is None:
1360
1305
                            # Reusing a discarded change.
1361
1306
                            old_entry = self._get_entry(self.source, file_id)
1362
 
                        precise_file_ids.update(
1363
 
                                self.source.iter_children(file_id))
 
1307
                        for child in old_entry.children.values():
 
1308
                            precise_file_ids.add(child.file_id)
1364
1309
                    changed_file_ids.add(result[0])
1365
1310
                    yield result
1366
1311
 
1367
 
    @needs_read_lock
1368
 
    def file_content_matches(self, source_file_id, target_file_id,
1369
 
            source_path=None, target_path=None, source_stat=None, target_stat=None):
1370
 
        """Check if two files are the same in the source and target trees.
1371
 
 
1372
 
        This only checks that the contents of the files are the same,
1373
 
        it does not touch anything else.
1374
 
 
1375
 
        :param source_file_id: File id of the file in the source tree
1376
 
        :param target_file_id: File id of the file in the target tree
1377
 
        :param source_path: Path of the file in the source tree
1378
 
        :param target_path: Path of the file in the target tree
1379
 
        :param source_stat: Optional stat value of the file in the source tree
1380
 
        :param target_stat: Optional stat value of the file in the target tree
1381
 
        :return: Boolean indicating whether the files have the same contents
1382
 
        """
1383
 
        source_verifier_kind, source_verifier_data = self.source.get_file_verifier(
1384
 
            source_file_id, source_path, source_stat)
1385
 
        target_verifier_kind, target_verifier_data = self.target.get_file_verifier(
1386
 
            target_file_id, target_path, target_stat)
1387
 
        if source_verifier_kind == target_verifier_kind:
1388
 
            return (source_verifier_data == target_verifier_data)
1389
 
        # Fall back to SHA1 for now
1390
 
        if source_verifier_kind != "SHA1":
1391
 
            source_sha1 = self.source.get_file_sha1(source_file_id,
1392
 
                    source_path, source_stat)
1393
 
        else:
1394
 
            source_sha1 = source_verifier_data
1395
 
        if target_verifier_kind != "SHA1":
1396
 
            target_sha1 = self.target.get_file_sha1(target_file_id,
1397
 
                    target_path, target_stat)
1398
 
        else:
1399
 
            target_sha1 = target_verifier_data
1400
 
        return (source_sha1 == target_sha1)
1401
 
 
1402
 
InterTree.register_optimiser(InterTree)
1403
 
 
1404
1312
 
1405
1313
class MultiWalker(object):
1406
1314
    """Walk multiple trees simultaneously, getting combined results."""
1509
1417
            return (None, None)
1510
1418
        else:
1511
1419
            self._out_of_order_processed.add(file_id)
1512
 
            cur_ie = other_tree.root_inventory[file_id]
 
1420
            cur_ie = other_tree.inventory[file_id]
1513
1421
            return (cur_path, cur_ie)
1514
1422
 
1515
1423
    def iter_all(self):