~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Vincent Ladeuil
  • Date: 2010-12-07 10:16:53 UTC
  • mto: (5575.1.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 5576.
  • Revision ID: v.ladeuil+lp@free.fr-20101207101653-20iiufih26buvmy3
Use assertLength as it provides a better ouput to debug tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 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
23
23
# But those depend on its position within a particular inventory, and
24
24
# it would be nice not to need to hold the backpointer here.
25
25
 
26
 
from __future__ import absolute_import
27
 
 
28
26
# This should really be an id randomly assigned when the tree is
29
27
# created, but it's not for now.
30
28
ROOT_ID = "TREE_ROOT"
33
31
lazy_import(globals(), """
34
32
import collections
35
33
import copy
 
34
import os
36
35
import re
37
36
import tarfile
38
37
 
44
43
    )
45
44
""")
46
45
 
47
 
from bzrlib import (
48
 
    lazy_regex,
49
 
    trace,
 
46
from bzrlib.errors import (
 
47
    BzrCheckError,
 
48
    BzrError,
50
49
    )
51
 
 
 
50
from bzrlib.trace import mutter
52
51
from bzrlib.static_tuple import StaticTuple
53
 
from bzrlib.symbol_versioning import (
54
 
    deprecated_in,
55
 
    deprecated_method,
56
 
    )
57
52
 
58
53
 
59
54
class InventoryEntry(object):
106
101
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
107
102
    >>> i.path2id('src/wibble')
108
103
    '2325'
 
104
    >>> '2325' in i
 
105
    True
109
106
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
110
107
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
111
108
    >>> i['2326']
174
171
        candidates = {}
175
172
        # identify candidate head revision ids.
176
173
        for inv in previous_inventories:
177
 
            if inv.has_id(self.file_id):
 
174
            if self.file_id in inv:
178
175
                ie = inv[self.file_id]
179
176
                if ie.revision in candidates:
180
177
                    # same revision value in two different inventories:
227
224
 
228
225
    def kind_character(self):
229
226
        """Return a short kind indicator useful for appending to names."""
230
 
        raise errors.BzrError('unknown kind %r' % self.kind)
 
227
        raise BzrError('unknown kind %r' % self.kind)
231
228
 
232
229
    known_kinds = ('file', 'directory', 'symlink')
233
230
 
253
250
        """
254
251
        if self.parent_id is not None:
255
252
            if not inv.has_id(self.parent_id):
256
 
                raise errors.BzrCheckError(
257
 
                    'missing parent {%s} in inventory for revision {%s}' % (
258
 
                        self.parent_id, rev_id))
 
253
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
 
254
                        % (self.parent_id, rev_id))
259
255
        checker._add_entry_to_text_key_references(inv, self)
260
256
        self._check(checker, rev_id)
261
257
 
543
539
        # FIXME: which _modified field should we use ? RBC 20051003
544
540
        text_modified = (self.symlink_target != old_entry.symlink_target)
545
541
        if text_modified:
546
 
            trace.mutter("    symlink target changed")
 
542
            mutter("    symlink target changed")
547
543
        meta_modified = False
548
544
        return text_modified, meta_modified
549
545
 
633
629
    inserted, other than through the Inventory API.
634
630
    """
635
631
 
 
632
    def __contains__(self, file_id):
 
633
        """True if this entry contains a file with given id.
 
634
 
 
635
        >>> inv = Inventory()
 
636
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
 
637
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
 
638
        >>> '123' in inv
 
639
        True
 
640
        >>> '456' in inv
 
641
        False
 
642
 
 
643
        Note that this method along with __iter__ are not encouraged for use as
 
644
        they are less clear than specific query methods - they may be rmeoved
 
645
        in the future.
 
646
        """
 
647
        return self.has_id(file_id)
 
648
 
636
649
    def has_filename(self, filename):
637
650
        return bool(self.path2id(filename))
638
651
 
705
718
                # if we finished all children, pop it off the stack
706
719
                stack.pop()
707
720
 
708
 
    def _preload_cache(self):
709
 
        """Populate any caches, we are about to access all items.
710
 
        
711
 
        The default implementation does nothing, because CommonInventory doesn't
712
 
        have a cache.
713
 
        """
714
 
        pass
715
 
    
716
721
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
717
722
        yield_parents=False):
718
723
        """Iterate over the entries in a directory first order.
731
736
            specific_file_ids = set(specific_file_ids)
732
737
        # TODO? Perhaps this should return the from_dir so that the root is
733
738
        # yielded? or maybe an option?
734
 
        if from_dir is None and specific_file_ids is None:
735
 
            # They are iterating from the root, and have not specified any
736
 
            # specific entries to look at. All current callers fully consume the
737
 
            # iterator, so we can safely assume we are accessing all entries
738
 
            self._preload_cache()
739
739
        if from_dir is None:
740
740
            if self.root is None:
741
741
                return
743
743
            if (not yield_parents and specific_file_ids is not None and
744
744
                len(specific_file_ids) == 1):
745
745
                file_id = list(specific_file_ids)[0]
746
 
                if self.has_id(file_id):
 
746
                if file_id in self:
747
747
                    yield self.id2path(file_id), self[file_id]
748
748
                return
749
749
            from_dir = self.root
759
759
            parents = set()
760
760
            byid = self
761
761
            def add_ancestors(file_id):
762
 
                if not byid.has_id(file_id):
 
762
                if file_id not in byid:
763
763
                    return
764
764
                parent_id = byid[file_id].parent_id
765
765
                if parent_id is None:
809
809
                    file_id, self[file_id]))
810
810
        return delta
811
811
 
 
812
    def _get_mutable_inventory(self):
 
813
        """Returns a mutable copy of the object.
 
814
 
 
815
        Some inventories are immutable, yet working trees, for example, needs
 
816
        to mutate exisiting inventories instead of creating a new one.
 
817
        """
 
818
        raise NotImplementedError(self._get_mutable_inventory)
 
819
 
812
820
    def make_entry(self, kind, name, parent_id, file_id=None):
813
821
        """Simple thunk to bzrlib.inventory.make_entry."""
814
822
        return make_entry(kind, name, parent_id, file_id)
832
840
            descend(self.root, u'')
833
841
        return accum
834
842
 
 
843
    def directories(self):
 
844
        """Return (path, entry) pairs for all directories, including the root.
 
845
        """
 
846
        accum = []
 
847
        def descend(parent_ie, parent_path):
 
848
            accum.append((parent_path, parent_ie))
 
849
 
 
850
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
 
851
            kids.sort()
 
852
 
 
853
            for name, child_ie in kids:
 
854
                child_path = osutils.pathjoin(parent_path, name)
 
855
                descend(child_ie, child_path)
 
856
        descend(self.root, u'')
 
857
        return accum
 
858
 
835
859
    def path2id(self, relpath):
836
860
        """Walk down through directories to return entry of last component.
837
861
 
933
957
 
934
958
    >>> inv.path2id('hello.c')
935
959
    '123-123'
936
 
    >>> inv.has_id('123-123')
 
960
    >>> '123-123' in inv
937
961
    True
938
962
 
939
963
    There are iterators over the contents:
1096
1120
            other.add(entry.copy())
1097
1121
        return other
1098
1122
 
 
1123
    def _get_mutable_inventory(self):
 
1124
        """See CommonInventory._get_mutable_inventory."""
 
1125
        return copy.deepcopy(self)
 
1126
 
1099
1127
    def __iter__(self):
1100
1128
        """Iterate over all file-ids."""
1101
1129
        return iter(self._byid)
1141
1169
    def _add_child(self, entry):
1142
1170
        """Add an entry to the inventory, without adding it to its parent"""
1143
1171
        if entry.file_id in self._byid:
1144
 
            raise errors.BzrError(
1145
 
                "inventory already contains entry with id {%s}" %
1146
 
                entry.file_id)
 
1172
            raise BzrError("inventory already contains entry with id {%s}" %
 
1173
                           entry.file_id)
1147
1174
        self._byid[entry.file_id] = entry
1148
1175
        for child in getattr(entry, 'children', {}).itervalues():
1149
1176
            self._add_child(child)
1202
1229
        >>> inv = Inventory()
1203
1230
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1204
1231
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1205
 
        >>> inv.has_id('123')
 
1232
        >>> '123' in inv
1206
1233
        True
1207
1234
        >>> del inv['123']
1208
 
        >>> inv.has_id('123')
 
1235
        >>> '123' in inv
1209
1236
        False
1210
1237
        """
1211
1238
        ie = self[file_id]
1313
1340
        """
1314
1341
        new_name = ensure_normalized_name(new_name)
1315
1342
        if not is_valid_name(new_name):
1316
 
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
 
1343
            raise BzrError("not an acceptable filename: %r" % new_name)
1317
1344
 
1318
1345
        new_parent = self._byid[new_parent_id]
1319
1346
        if new_name in new_parent.children:
1320
 
            raise errors.BzrError("%r already exists in %r" %
1321
 
                (new_name, self.id2path(new_parent_id)))
 
1347
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1322
1348
 
1323
1349
        new_parent_idpath = self.get_idpath(new_parent_id)
1324
1350
        if file_id in new_parent_idpath:
1325
 
            raise errors.BzrError(
1326
 
                "cannot move directory %r into a subdirectory of itself, %r"
 
1351
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
1327
1352
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1328
1353
 
1329
1354
        file_ie = self._byid[file_id]
1365
1390
    def __init__(self, search_key_name):
1366
1391
        CommonInventory.__init__(self)
1367
1392
        self._fileid_to_entry_cache = {}
1368
 
        self._fully_cached = False
1369
1393
        self._path_to_fileid_cache = {}
1370
1394
        self._search_key_name = search_key_name
1371
1395
        self.root_id = None
1458
1482
            if entry.kind == 'directory':
1459
1483
                directories_to_expand.add(entry.file_id)
1460
1484
            interesting.add(entry.parent_id)
1461
 
            children_of_parent_id.setdefault(entry.parent_id, set()
1462
 
                                             ).add(entry.file_id)
 
1485
            children_of_parent_id.setdefault(entry.parent_id, []
 
1486
                                             ).append(entry.file_id)
1463
1487
 
1464
1488
        # Now, interesting has all of the direct parents, but not the
1465
1489
        # parents of those parents. It also may have some duplicates with
1473
1497
            next_parents = set()
1474
1498
            for entry in self._getitems(remaining_parents):
1475
1499
                next_parents.add(entry.parent_id)
1476
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1477
 
                                                 ).add(entry.file_id)
 
1500
                children_of_parent_id.setdefault(entry.parent_id, []
 
1501
                                                 ).append(entry.file_id)
1478
1502
            # Remove any search tips we've already processed
1479
1503
            remaining_parents = next_parents.difference(interesting)
1480
1504
            interesting.update(remaining_parents)
1493
1517
            for entry in self._getitems(next_file_ids):
1494
1518
                if entry.kind == 'directory':
1495
1519
                    directories_to_expand.add(entry.file_id)
1496
 
                children_of_parent_id.setdefault(entry.parent_id, set()
1497
 
                                                 ).add(entry.file_id)
 
1520
                children_of_parent_id.setdefault(entry.parent_id, []
 
1521
                                                 ).append(entry.file_id)
1498
1522
        return interesting, children_of_parent_id
1499
1523
 
1500
1524
    def filter(self, specific_fileids):
1578
1602
        self._fileid_to_entry_cache[result.file_id] = result
1579
1603
        return result
1580
1604
 
 
1605
    def _get_mutable_inventory(self):
 
1606
        """See CommonInventory._get_mutable_inventory."""
 
1607
        entries = self.iter_entries()
 
1608
        inv = Inventory(None, self.revision_id)
 
1609
        for path, inv_entry in entries:
 
1610
            inv.add(inv_entry.copy())
 
1611
        return inv
 
1612
 
1581
1613
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1582
1614
        propagate_caches=False):
1583
1615
        """Create a new CHKInventory by applying inventory_delta to this one.
1924
1956
 
1925
1957
    def iter_just_entries(self):
1926
1958
        """Iterate over all entries.
1927
 
 
 
1959
        
1928
1960
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1929
1961
        and the order of entries is undefined.
1930
1962
 
1938
1970
                self._fileid_to_entry_cache[file_id] = ie
1939
1971
            yield ie
1940
1972
 
1941
 
    def _preload_cache(self):
1942
 
        """Make sure all file-ids are in _fileid_to_entry_cache"""
1943
 
        if self._fully_cached:
1944
 
            return # No need to do it again
1945
 
        # The optimal sort order is to use iteritems() directly
1946
 
        cache = self._fileid_to_entry_cache
1947
 
        for key, entry in self.id_to_entry.iteritems():
1948
 
            file_id = key[0]
1949
 
            if file_id not in cache:
1950
 
                ie = self._bytes_to_entry(entry)
1951
 
                cache[file_id] = ie
1952
 
            else:
1953
 
                ie = cache[file_id]
1954
 
        last_parent_id = last_parent_ie = None
1955
 
        pid_items = self.parent_id_basename_to_file_id.iteritems()
1956
 
        for key, child_file_id in pid_items:
1957
 
            if key == ('', ''): # This is the root
1958
 
                if child_file_id != self.root_id:
1959
 
                    raise ValueError('Data inconsistency detected.'
1960
 
                        ' We expected data with key ("","") to match'
1961
 
                        ' the root id, but %s != %s'
1962
 
                        % (child_file_id, self.root_id))
1963
 
                continue
1964
 
            parent_id, basename = key
1965
 
            ie = cache[child_file_id]
1966
 
            if parent_id == last_parent_id:
1967
 
                parent_ie = last_parent_ie
1968
 
            else:
1969
 
                parent_ie = cache[parent_id]
1970
 
            if parent_ie.kind != 'directory':
1971
 
                raise ValueError('Data inconsistency detected.'
1972
 
                    ' An entry in the parent_id_basename_to_file_id map'
1973
 
                    ' has parent_id {%s} but the kind of that object'
1974
 
                    ' is %r not "directory"' % (parent_id, parent_ie.kind))
1975
 
            if parent_ie._children is None:
1976
 
                parent_ie._children = {}
1977
 
            basename = basename.decode('utf-8')
1978
 
            if basename in parent_ie._children:
1979
 
                existing_ie = parent_ie._children[basename]
1980
 
                if existing_ie != ie:
1981
 
                    raise ValueError('Data inconsistency detected.'
1982
 
                        ' Two entries with basename %r were found'
1983
 
                        ' in the parent entry {%s}'
1984
 
                        % (basename, parent_id))
1985
 
            if basename != ie.name:
1986
 
                raise ValueError('Data inconsistency detected.'
1987
 
                    ' In the parent_id_basename_to_file_id map, file_id'
1988
 
                    ' {%s} is listed as having basename %r, but in the'
1989
 
                    ' id_to_entry map it is %r'
1990
 
                    % (child_file_id, basename, ie.name))
1991
 
            parent_ie._children[basename] = ie
1992
 
        self._fully_cached = True
1993
 
 
1994
1973
    def iter_changes(self, basis):
1995
1974
        """Generate a Tree.iter_changes change list between this and basis.
1996
1975
 
2093
2072
    def path2id(self, relpath):
2094
2073
        """See CommonInventory.path2id()."""
2095
2074
        # TODO: perhaps support negative hits?
2096
 
        if isinstance(relpath, basestring):
2097
 
            names = osutils.splitpath(relpath)
2098
 
        else:
2099
 
            names = relpath
2100
 
            if relpath == []:
2101
 
                relpath = [""]
2102
 
            relpath = osutils.pathjoin(*relpath)
2103
2075
        result = self._path_to_fileid_cache.get(relpath, None)
2104
2076
        if result is not None:
2105
2077
            return result
 
2078
        if isinstance(relpath, basestring):
 
2079
            names = osutils.splitpath(relpath)
 
2080
        else:
 
2081
            names = relpath
2106
2082
        current_id = self.root_id
2107
2083
        if current_id is None:
2108
2084
            return None
2256
2232
    return name
2257
2233
 
2258
2234
 
2259
 
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
 
2235
_NAME_RE = None
2260
2236
 
2261
2237
def is_valid_name(name):
 
2238
    global _NAME_RE
 
2239
    if _NAME_RE is None:
 
2240
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
2241
 
2262
2242
    return bool(_NAME_RE.match(name))
2263
2243
 
2264
2244
 
2354
2334
            raise errors.InconsistentDelta(new_path, item[1],
2355
2335
                "new_path with no entry")
2356
2336
        yield item
2357
 
 
2358
 
 
2359
 
def mutable_inventory_from_tree(tree):
2360
 
    """Create a new inventory that has the same contents as a specified tree.
2361
 
 
2362
 
    :param tree: Revision tree to create inventory from
2363
 
    """
2364
 
    entries = tree.iter_entries_by_dir()
2365
 
    inv = Inventory(None, tree.get_revision_id())
2366
 
    for path, inv_entry in entries:
2367
 
        inv.add(inv_entry.copy())
2368
 
    return inv