~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: Patch Queue Manager
  • Date: 2013-07-14 10:59:28 UTC
  • mfrom: (6579.2.1 1195783-platform-utf8)
  • Revision ID: pqm@pqm.ubuntu.com-20130714105928-78j748r1djstxmo1
(vila) Make 'bzr version' support utf8 platform names. (Vincent Ladeuil)

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
 
20
22
import os
21
23
 
22
24
from bzrlib.lazy_import import lazy_import
35
37
    rules,
36
38
    trace,
37
39
    )
 
40
from bzrlib.i18n import gettext
38
41
""")
39
42
 
40
43
from bzrlib.decorators import needs_read_lock
58
61
    trees or versioned trees.
59
62
    """
60
63
 
 
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
 
61
72
    def changes_from(self, other, want_unchanged=False, specific_files=None,
62
73
        extra_trees=None, require_versioned=False, include_root=False,
63
74
        want_unversioned=False):
127
138
    def has_id(self, file_id):
128
139
        raise NotImplementedError(self.has_id)
129
140
 
 
141
    @deprecated_method(deprecated_in((2, 4, 0)))
130
142
    def __contains__(self, file_id):
131
143
        return self.has_id(file_id)
132
144
 
183
195
        """
184
196
        raise NotImplementedError(self.iter_entries_by_dir)
185
197
 
 
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
 
186
208
    def list_files(self, include_root=False, from_dir=None, recursive=True):
187
209
        """List all files in this tree.
188
210
 
276
298
 
277
299
        :param file_id: The file_id of the file.
278
300
        :param path: The path of the file.
 
301
 
279
302
        If both file_id and path are supplied, an implementation may use
280
303
        either one.
 
304
 
 
305
        :returns: A single byte string for the whole file.
281
306
        """
282
307
        my_file = self.get_file(file_id, path)
283
308
        try:
296
321
        """
297
322
        return osutils.split_lines(self.get_file_text(file_id, path))
298
323
 
 
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
 
299
338
    def get_file_sha1(self, file_id, path=None, stat_value=None):
300
339
        """Return the SHA1 file for a file.
301
340
 
 
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
 
302
345
        :param file_id: The handle for this file.
303
346
        :param path: The path that this file can be found at.
304
347
            These must point to the same object.
324
367
        """
325
368
        raise NotImplementedError(self.get_file_size)
326
369
 
327
 
    def get_file_by_path(self, path):
328
 
        raise NotImplementedError(self.get_file_by_path)
329
 
 
330
370
    def is_executable(self, file_id, path=None):
331
371
        """Check if a file is executable.
332
372
 
502
542
        return find_ids_across_trees(paths, [self] + list(trees), require_versioned)
503
543
 
504
544
    def iter_children(self, file_id):
505
 
        entry = self.iter_entries_by_dir([file_id]).next()[1]
506
 
        for child in getattr(entry, 'children', {}).itervalues():
507
 
            yield child.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)
508
551
 
509
552
    def lock_read(self):
510
553
        """Lock this tree for multiple read only operations.
511
 
        
 
554
 
512
555
        :return: A bzrlib.lock.LogicalLockResult.
513
556
        """
514
557
        pass
601
644
        prefs = self.iter_search_rules([path], filter_pref_names).next()
602
645
        stk = filters._get_filter_stack_for(prefs)
603
646
        if 'filters' in debug.debug_flags:
604
 
            trace.note("*** %s content-filter: %s => %r" % (path,prefs,stk))
 
647
            trace.note(gettext("*** {0} content-filter: {1} => {2!r}").format(path,prefs,stk))
605
648
        return stk
606
649
 
607
650
    def _content_filter_stack_provider(self):
738
781
            yield cur_path
739
782
        # all done.
740
783
 
 
784
    @deprecated_method(deprecated_in((2, 5, 0)))
741
785
    def _get_inventory(self):
742
786
        return self._inventory
743
787
 
744
788
    inventory = property(_get_inventory,
745
789
                         doc="Inventory of this Tree")
746
790
 
 
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
 
747
809
    @needs_read_lock
748
810
    def path2id(self, path):
749
811
        """Return the id for path in this tree."""
750
 
        return self._inventory.path2id(path)
 
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)
751
822
 
752
823
    def id2path(self, file_id):
753
824
        """Return the path for a file id.
754
825
 
755
826
        :raises NoSuchId:
756
827
        """
757
 
        return self.inventory.id2path(file_id)
 
828
        inventory, file_id = self._unpack_file_id(file_id)
 
829
        return inventory.id2path(file_id)
758
830
 
759
831
    def has_id(self, file_id):
760
 
        return self.inventory.has_id(file_id)
 
832
        inventory, file_id = self._unpack_file_id(file_id)
 
833
        return inventory.has_id(file_id)
761
834
 
762
835
    def has_or_had_id(self, file_id):
763
 
        return self.inventory.has_id(file_id)
 
836
        inventory, file_id = self._unpack_file_id(file_id)
 
837
        return inventory.has_id(file_id)
764
838
 
765
839
    def all_file_ids(self):
766
 
        return set(self.inventory)
 
840
        return set(
 
841
            [entry.file_id for path, entry in self.iter_entries_by_dir()])
767
842
 
768
843
    @deprecated_method(deprecated_in((2, 4, 0)))
769
844
    def __iter__(self):
770
 
        return iter(self.inventory)
 
845
        return iter(self.all_file_ids())
771
846
 
772
847
    def filter_unversioned_files(self, paths):
773
848
        """Filter out paths that are versioned.
777
852
        # NB: we specifically *don't* call self.has_filename, because for
778
853
        # WorkingTrees that can indicate files that exist on disk but that
779
854
        # are not versioned.
780
 
        pred = self.inventory.has_filename
781
 
        return set((p for p in paths if not pred(p)))
 
855
        return set((p for p in paths if self.path2id(p) is None))
782
856
 
783
857
    @needs_read_lock
784
858
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
793
867
            down to specific_file_ids that have been requested. This has no
794
868
            impact if specific_file_ids is None.
795
869
        """
796
 
        return self.inventory.iter_entries_by_dir(
797
 
            specific_file_ids=specific_file_ids, yield_parents=yield_parents)
798
 
 
 
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)))
799
890
    def get_file_by_path(self, path):
800
 
        return self.get_file(self._inventory.path2id(path), path)
801
 
 
802
 
 
803
 
######################################################################
804
 
# diff
805
 
 
806
 
# TODO: Merge these two functions into a single one that can operate
807
 
# on either a whole tree or a set of files.
808
 
 
809
 
# TODO: Return the diff in order by filename, not by category or in
810
 
# random order.  Can probably be done by lock-stepping through the
811
 
# filenames from both trees.
812
 
 
813
 
 
814
 
def file_status(filename, old_tree, new_tree):
815
 
    """Return single-letter status, old and new names for a file.
816
 
 
817
 
    The complexity here is in deciding how to represent renames;
818
 
    many complex cases are possible.
819
 
    """
820
 
    old_inv = old_tree.inventory
821
 
    new_inv = new_tree.inventory
822
 
    new_id = new_inv.path2id(filename)
823
 
    old_id = old_inv.path2id(filename)
824
 
 
825
 
    if not new_id and not old_id:
826
 
        # easy: doesn't exist in either; not versioned at all
827
 
        if new_tree.is_ignored(filename):
828
 
            return 'I', None, None
829
 
        else:
830
 
            return '?', None, None
831
 
    elif new_id:
832
 
        # There is now a file of this name, great.
833
 
        pass
834
 
    else:
835
 
        # There is no longer a file of this name, but we can describe
836
 
        # what happened to the file that used to have
837
 
        # this name.  There are two possibilities: either it was
838
 
        # deleted entirely, or renamed.
839
 
        if new_inv.has_id(old_id):
840
 
            return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
841
 
        else:
842
 
            return 'D', old_inv.id2path(old_id), None
843
 
 
844
 
    # if the file_id is new in this revision, it is added
845
 
    if new_id and not old_inv.has_id(new_id):
846
 
        return 'A'
847
 
 
848
 
    # if there used to be a file of this name, but that ID has now
849
 
    # disappeared, it is deleted
850
 
    if old_id and not new_inv.has_id(old_id):
851
 
        return 'D'
852
 
 
853
 
    return 'wtf?'
 
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
854
898
 
855
899
 
856
900
def find_ids_across_trees(filenames, trees, require_versioned=True):
1004
1048
        if source_kind != target_kind:
1005
1049
            changed_content = True
1006
1050
        elif source_kind == 'file':
1007
 
            if (self.source.get_file_sha1(file_id, source_path, source_stat) !=
1008
 
                self.target.get_file_sha1(file_id, target_path, target_stat)):
 
1051
            if not self.file_content_matches(file_id, file_id, source_path,
 
1052
                    target_path, source_stat, target_stat):
1009
1053
                changed_content = True
1010
1054
        elif source_kind == 'symlink':
1011
1055
            if (self.source.get_symlink_target(file_id) !=
1012
1056
                self.target.get_symlink_target(file_id)):
1013
1057
                changed_content = True
1014
 
            # XXX: Yes, the indentation below is wrong. But fixing it broke
1015
 
            # test_merge.TestMergerEntriesLCAOnDisk.
1016
 
            # test_nested_tree_subtree_renamed_and_modified. We'll wait for
1017
 
            # the fix from bzr.dev -- vila 2009026
1018
 
            elif source_kind == 'tree-reference':
1019
 
                if (self.source.get_reference_revision(file_id, source_path)
1020
 
                    != self.target.get_reference_revision(file_id, target_path)):
 
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)):
1021
1061
                    changed_content = True
1022
1062
        parent = (source_parent, target_parent)
1023
1063
        name = (source_name, target_name)
1238
1278
        :param file_id: The file_id to lookup.
1239
1279
        """
1240
1280
        try:
1241
 
            inventory = tree.inventory
 
1281
            inventory = tree.root_inventory
1242
1282
        except NotImplementedError:
1243
1283
            # No inventory available.
1244
1284
            try:
1319
1359
                        if old_entry is None:
1320
1360
                            # Reusing a discarded change.
1321
1361
                            old_entry = self._get_entry(self.source, file_id)
1322
 
                        for child in old_entry.children.values():
1323
 
                            precise_file_ids.add(child.file_id)
 
1362
                        precise_file_ids.update(
 
1363
                                self.source.iter_children(file_id))
1324
1364
                    changed_file_ids.add(result[0])
1325
1365
                    yield result
1326
1366
 
 
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)
1327
1401
 
1328
1402
InterTree.register_optimiser(InterTree)
1329
1403
 
1435
1509
            return (None, None)
1436
1510
        else:
1437
1511
            self._out_of_order_processed.add(file_id)
1438
 
            cur_ie = other_tree.inventory[file_id]
 
1512
            cur_ie = other_tree.root_inventory[file_id]
1439
1513
            return (cur_path, cur_ie)
1440
1514
 
1441
1515
    def iter_all(self):