~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Andrew Bennetts
  • Date: 2010-10-13 00:26:41 UTC
  • mto: This revision was merged to the branch mainline in revision 5498.
  • Revision ID: andrew.bennetts@canonical.com-20101013002641-9tlh9k89mlj1666m
Keep docs-plain working.

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
31
31
lazy_import(globals(), """
32
32
import collections
33
33
import copy
 
34
import os
34
35
import re
35
36
import tarfile
36
37
 
42
43
    )
43
44
""")
44
45
 
45
 
from bzrlib import (
46
 
    lazy_regex,
47
 
    trace,
 
46
from bzrlib.errors import (
 
47
    BzrCheckError,
 
48
    BzrError,
48
49
    )
49
 
 
 
50
from bzrlib.trace import mutter
50
51
from bzrlib.static_tuple import StaticTuple
51
 
from bzrlib.symbol_versioning import (
52
 
    deprecated_in,
53
 
    deprecated_method,
54
 
    )
55
52
 
56
53
 
57
54
class InventoryEntry(object):
104
101
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
105
102
    >>> i.path2id('src/wibble')
106
103
    '2325'
 
104
    >>> '2325' in i
 
105
    True
107
106
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
108
107
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
109
108
    >>> i['2326']
172
171
        candidates = {}
173
172
        # identify candidate head revision ids.
174
173
        for inv in previous_inventories:
175
 
            if inv.has_id(self.file_id):
 
174
            if self.file_id in inv:
176
175
                ie = inv[self.file_id]
177
176
                if ie.revision in candidates:
178
177
                    # same revision value in two different inventories:
225
224
 
226
225
    def kind_character(self):
227
226
        """Return a short kind indicator useful for appending to names."""
228
 
        raise errors.BzrError('unknown kind %r' % self.kind)
 
227
        raise BzrError('unknown kind %r' % self.kind)
229
228
 
230
229
    known_kinds = ('file', 'directory', 'symlink')
231
230
 
251
250
        """
252
251
        if self.parent_id is not None:
253
252
            if not inv.has_id(self.parent_id):
254
 
                raise errors.BzrCheckError(
255
 
                    'missing parent {%s} in inventory for revision {%s}' % (
256
 
                        self.parent_id, rev_id))
 
253
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
 
254
                        % (self.parent_id, rev_id))
257
255
        checker._add_entry_to_text_key_references(inv, self)
258
256
        self._check(checker, rev_id)
259
257
 
541
539
        # FIXME: which _modified field should we use ? RBC 20051003
542
540
        text_modified = (self.symlink_target != old_entry.symlink_target)
543
541
        if text_modified:
544
 
            trace.mutter("    symlink target changed")
 
542
            mutter("    symlink target changed")
545
543
        meta_modified = False
546
544
        return text_modified, meta_modified
547
545
 
631
629
    inserted, other than through the Inventory API.
632
630
    """
633
631
 
634
 
    @deprecated_method(deprecated_in((2, 4, 0)))
635
632
    def __contains__(self, file_id):
636
633
        """True if this entry contains a file with given id.
637
634
 
638
635
        >>> inv = Inventory()
639
636
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
640
637
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
641
 
        >>> inv.has_id('123')
 
638
        >>> '123' in inv
642
639
        True
643
 
        >>> inv.has_id('456')
 
640
        >>> '456' in inv
644
641
        False
645
642
 
646
643
        Note that this method along with __iter__ are not encouraged for use as
721
718
                # if we finished all children, pop it off the stack
722
719
                stack.pop()
723
720
 
724
 
    def _preload_cache(self):
725
 
        """Populate any caches, we are about to access all items.
726
 
        
727
 
        The default implementation does nothing, because CommonInventory doesn't
728
 
        have a cache.
729
 
        """
730
 
        pass
731
 
    
732
721
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
733
722
        yield_parents=False):
734
723
        """Iterate over the entries in a directory first order.
747
736
            specific_file_ids = set(specific_file_ids)
748
737
        # TODO? Perhaps this should return the from_dir so that the root is
749
738
        # yielded? or maybe an option?
750
 
        if from_dir is None and specific_file_ids is None:
751
 
            # They are iterating from the root, and have not specified any
752
 
            # specific entries to look at. All current callers fully consume the
753
 
            # iterator, so we can safely assume we are accessing all entries
754
 
            self._preload_cache()
755
739
        if from_dir is None:
756
740
            if self.root is None:
757
741
                return
759
743
            if (not yield_parents and specific_file_ids is not None and
760
744
                len(specific_file_ids) == 1):
761
745
                file_id = list(specific_file_ids)[0]
762
 
                if self.has_id(file_id):
 
746
                if file_id in self:
763
747
                    yield self.id2path(file_id), self[file_id]
764
748
                return
765
749
            from_dir = self.root
775
759
            parents = set()
776
760
            byid = self
777
761
            def add_ancestors(file_id):
778
 
                if not byid.has_id(file_id):
 
762
                if file_id not in byid:
779
763
                    return
780
764
                parent_id = byid[file_id].parent_id
781
765
                if parent_id is None:
825
809
                    file_id, self[file_id]))
826
810
        return delta
827
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
 
828
820
    def make_entry(self, kind, name, parent_id, file_id=None):
829
821
        """Simple thunk to bzrlib.inventory.make_entry."""
830
822
        return make_entry(kind, name, parent_id, file_id)
965
957
 
966
958
    >>> inv.path2id('hello.c')
967
959
    '123-123'
968
 
    >>> inv.has_id('123-123')
 
960
    >>> '123-123' in inv
969
961
    True
970
962
 
971
963
    There are iterators over the contents:
1128
1120
            other.add(entry.copy())
1129
1121
        return other
1130
1122
 
 
1123
    def _get_mutable_inventory(self):
 
1124
        """See CommonInventory._get_mutable_inventory."""
 
1125
        return copy.deepcopy(self)
 
1126
 
1131
1127
    def __iter__(self):
1132
1128
        """Iterate over all file-ids."""
1133
1129
        return iter(self._byid)
1173
1169
    def _add_child(self, entry):
1174
1170
        """Add an entry to the inventory, without adding it to its parent"""
1175
1171
        if entry.file_id in self._byid:
1176
 
            raise errors.BzrError(
1177
 
                "inventory already contains entry with id {%s}" %
1178
 
                entry.file_id)
 
1172
            raise BzrError("inventory already contains entry with id {%s}" %
 
1173
                           entry.file_id)
1179
1174
        self._byid[entry.file_id] = entry
1180
1175
        for child in getattr(entry, 'children', {}).itervalues():
1181
1176
            self._add_child(child)
1234
1229
        >>> inv = Inventory()
1235
1230
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1236
1231
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1237
 
        >>> inv.has_id('123')
 
1232
        >>> '123' in inv
1238
1233
        True
1239
1234
        >>> del inv['123']
1240
 
        >>> inv.has_id('123')
 
1235
        >>> '123' in inv
1241
1236
        False
1242
1237
        """
1243
1238
        ie = self[file_id]
1345
1340
        """
1346
1341
        new_name = ensure_normalized_name(new_name)
1347
1342
        if not is_valid_name(new_name):
1348
 
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
 
1343
            raise BzrError("not an acceptable filename: %r" % new_name)
1349
1344
 
1350
1345
        new_parent = self._byid[new_parent_id]
1351
1346
        if new_name in new_parent.children:
1352
 
            raise errors.BzrError("%r already exists in %r" %
1353
 
                (new_name, self.id2path(new_parent_id)))
 
1347
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
1354
1348
 
1355
1349
        new_parent_idpath = self.get_idpath(new_parent_id)
1356
1350
        if file_id in new_parent_idpath:
1357
 
            raise errors.BzrError(
1358
 
                "cannot move directory %r into a subdirectory of itself, %r"
 
1351
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
1359
1352
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1360
1353
 
1361
1354
        file_ie = self._byid[file_id]
1397
1390
    def __init__(self, search_key_name):
1398
1391
        CommonInventory.__init__(self)
1399
1392
        self._fileid_to_entry_cache = {}
1400
 
        self._fully_cached = False
1401
1393
        self._path_to_fileid_cache = {}
1402
1394
        self._search_key_name = search_key_name
1403
1395
        self.root_id = None
1610
1602
        self._fileid_to_entry_cache[result.file_id] = result
1611
1603
        return result
1612
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
 
1613
1613
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1614
1614
        propagate_caches=False):
1615
1615
        """Create a new CHKInventory by applying inventory_delta to this one.
1956
1956
 
1957
1957
    def iter_just_entries(self):
1958
1958
        """Iterate over all entries.
1959
 
 
 
1959
        
1960
1960
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1961
1961
        and the order of entries is undefined.
1962
1962
 
1970
1970
                self._fileid_to_entry_cache[file_id] = ie
1971
1971
            yield ie
1972
1972
 
1973
 
    def _preload_cache(self):
1974
 
        """Make sure all file-ids are in _fileid_to_entry_cache"""
1975
 
        if self._fully_cached:
1976
 
            return # No need to do it again
1977
 
        # The optimal sort order is to use iteritems() directly
1978
 
        cache = self._fileid_to_entry_cache
1979
 
        for key, entry in self.id_to_entry.iteritems():
1980
 
            file_id = key[0]
1981
 
            if file_id not in cache:
1982
 
                ie = self._bytes_to_entry(entry)
1983
 
                cache[file_id] = ie
1984
 
            else:
1985
 
                ie = cache[file_id]
1986
 
        last_parent_id = last_parent_ie = None
1987
 
        pid_items = self.parent_id_basename_to_file_id.iteritems()
1988
 
        for key, child_file_id in pid_items:
1989
 
            if key == ('', ''): # This is the root
1990
 
                if child_file_id != self.root_id:
1991
 
                    raise ValueError('Data inconsistency detected.'
1992
 
                        ' We expected data with key ("","") to match'
1993
 
                        ' the root id, but %s != %s'
1994
 
                        % (child_file_id, self.root_id))
1995
 
                continue
1996
 
            parent_id, basename = key
1997
 
            ie = cache[child_file_id]
1998
 
            if parent_id == last_parent_id:
1999
 
                parent_ie = last_parent_ie
2000
 
            else:
2001
 
                parent_ie = cache[parent_id]
2002
 
            if parent_ie.kind != 'directory':
2003
 
                raise ValueError('Data inconsistency detected.'
2004
 
                    ' An entry in the parent_id_basename_to_file_id map'
2005
 
                    ' has parent_id {%s} but the kind of that object'
2006
 
                    ' is %r not "directory"' % (parent_id, parent_ie.kind))
2007
 
            if parent_ie._children is None:
2008
 
                parent_ie._children = {}
2009
 
            basename = basename.decode('utf-8')
2010
 
            if basename in parent_ie._children:
2011
 
                existing_ie = parent_ie._children[basename]
2012
 
                if existing_ie != ie:
2013
 
                    raise ValueError('Data inconsistency detected.'
2014
 
                        ' Two entries with basename %r were found'
2015
 
                        ' in the parent entry {%s}'
2016
 
                        % (basename, parent_id))
2017
 
            if basename != ie.name:
2018
 
                raise ValueError('Data inconsistency detected.'
2019
 
                    ' In the parent_id_basename_to_file_id map, file_id'
2020
 
                    ' {%s} is listed as having basename %r, but in the'
2021
 
                    ' id_to_entry map it is %r'
2022
 
                    % (child_file_id, basename, ie.name))
2023
 
            parent_ie._children[basename] = ie
2024
 
        self._fully_cached = True
2025
 
 
2026
1973
    def iter_changes(self, basis):
2027
1974
        """Generate a Tree.iter_changes change list between this and basis.
2028
1975
 
2285
2232
    return name
2286
2233
 
2287
2234
 
2288
 
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
 
2235
_NAME_RE = None
2289
2236
 
2290
2237
def is_valid_name(name):
 
2238
    global _NAME_RE
 
2239
    if _NAME_RE is None:
 
2240
        _NAME_RE = re.compile(r'^[^/\\]+$')
 
2241
 
2291
2242
    return bool(_NAME_RE.match(name))
2292
2243
 
2293
2244
 
2383
2334
            raise errors.InconsistentDelta(new_path, item[1],
2384
2335
                "new_path with no entry")
2385
2336
        yield item
2386
 
 
2387
 
 
2388
 
def mutable_inventory_from_tree(tree):
2389
 
    """Create a new inventory that has the same contents as a specified tree.
2390
 
 
2391
 
    :param tree: Revision tree to create inventory from
2392
 
    """
2393
 
    entries = tree.iter_entries_by_dir()
2394
 
    inv = Inventory(None, tree.get_revision_id())
2395
 
    for path, inv_entry in entries:
2396
 
        inv.add(inv_entry.copy())
2397
 
    return inv