~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Patch Queue Manager
  • Date: 2016-04-21 04:10:52 UTC
  • mfrom: (6616.1.1 fix-en-user-guide)
  • Revision ID: pqm@pqm.ubuntu.com-20160421041052-clcye7ns1qcl2n7w
(richard-wilbur) Ensure build of English use guide always uses English text
 even when user's locale specifies a different language. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
 
26
28
# This should really be an id randomly assigned when the tree is
27
29
# created, but it's not for now.
28
30
ROOT_ID = "TREE_ROOT"
31
33
lazy_import(globals(), """
32
34
import collections
33
35
import copy
34
 
import os
35
36
import re
36
37
import tarfile
37
38
 
43
44
    )
44
45
""")
45
46
 
46
 
from bzrlib.errors import (
47
 
    BzrCheckError,
48
 
    BzrError,
 
47
from bzrlib import (
 
48
    lazy_regex,
 
49
    trace,
49
50
    )
50
 
from bzrlib.trace import mutter
 
51
 
51
52
from bzrlib.static_tuple import StaticTuple
 
53
from bzrlib.symbol_versioning import (
 
54
    deprecated_in,
 
55
    deprecated_method,
 
56
    )
52
57
 
53
58
 
54
59
class InventoryEntry(object):
101
106
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
102
107
    >>> i.path2id('src/wibble')
103
108
    '2325'
104
 
    >>> '2325' in i
105
 
    True
106
109
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
107
110
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None, revision=None)
108
111
    >>> i['2326']
171
174
        candidates = {}
172
175
        # identify candidate head revision ids.
173
176
        for inv in previous_inventories:
174
 
            if self.file_id in inv:
 
177
            if inv.has_id(self.file_id):
175
178
                ie = inv[self.file_id]
176
179
                if ie.revision in candidates:
177
180
                    # same revision value in two different inventories:
224
227
 
225
228
    def kind_character(self):
226
229
        """Return a short kind indicator useful for appending to names."""
227
 
        raise BzrError('unknown kind %r' % self.kind)
 
230
        raise errors.BzrError('unknown kind %r' % self.kind)
228
231
 
229
232
    known_kinds = ('file', 'directory', 'symlink')
230
233
 
250
253
        """
251
254
        if self.parent_id is not None:
252
255
            if not inv.has_id(self.parent_id):
253
 
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
254
 
                        % (self.parent_id, rev_id))
 
256
                raise errors.BzrCheckError(
 
257
                    'missing parent {%s} in inventory for revision {%s}' % (
 
258
                        self.parent_id, rev_id))
255
259
        checker._add_entry_to_text_key_references(inv, self)
256
260
        self._check(checker, rev_id)
257
261
 
539
543
        # FIXME: which _modified field should we use ? RBC 20051003
540
544
        text_modified = (self.symlink_target != old_entry.symlink_target)
541
545
        if text_modified:
542
 
            mutter("    symlink target changed")
 
546
            trace.mutter("    symlink target changed")
543
547
        meta_modified = False
544
548
        return text_modified, meta_modified
545
549
 
629
633
    inserted, other than through the Inventory API.
630
634
    """
631
635
 
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
 
 
649
636
    def has_filename(self, filename):
650
637
        return bool(self.path2id(filename))
651
638
 
718
705
                # if we finished all children, pop it off the stack
719
706
                stack.pop()
720
707
 
 
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
    
721
716
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
722
717
        yield_parents=False):
723
718
        """Iterate over the entries in a directory first order.
736
731
            specific_file_ids = set(specific_file_ids)
737
732
        # TODO? Perhaps this should return the from_dir so that the root is
738
733
        # 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 file_id in self:
 
746
                if self.has_id(file_id):
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 file_id not in byid:
 
762
                if not byid.has_id(file_id):
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
 
 
820
812
    def make_entry(self, kind, name, parent_id, file_id=None):
821
813
        """Simple thunk to bzrlib.inventory.make_entry."""
822
814
        return make_entry(kind, name, parent_id, file_id)
836
828
                if ie.kind == 'directory':
837
829
                    descend(ie, child_path)
838
830
 
839
 
        descend(self.root, u'')
840
 
        return accum
841
 
 
842
 
    def directories(self):
843
 
        """Return (path, entry) pairs for all directories, including the root.
844
 
        """
845
 
        accum = []
846
 
        def descend(parent_ie, parent_path):
847
 
            accum.append((parent_path, parent_ie))
848
 
 
849
 
            kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
850
 
            kids.sort()
851
 
 
852
 
            for name, child_ie in kids:
853
 
                child_path = osutils.pathjoin(parent_path, name)
854
 
                descend(child_ie, child_path)
855
 
        descend(self.root, u'')
 
831
        if self.root is not None:
 
832
            descend(self.root, u'')
856
833
        return accum
857
834
 
858
835
    def path2id(self, relpath):
956
933
 
957
934
    >>> inv.path2id('hello.c')
958
935
    '123-123'
959
 
    >>> '123-123' in inv
 
936
    >>> inv.has_id('123-123')
960
937
    True
961
938
 
962
939
    There are iterators over the contents:
1119
1096
            other.add(entry.copy())
1120
1097
        return other
1121
1098
 
1122
 
    def _get_mutable_inventory(self):
1123
 
        """See CommonInventory._get_mutable_inventory."""
1124
 
        return copy.deepcopy(self)
1125
 
 
1126
1099
    def __iter__(self):
1127
1100
        """Iterate over all file-ids."""
1128
1101
        return iter(self._byid)
1168
1141
    def _add_child(self, entry):
1169
1142
        """Add an entry to the inventory, without adding it to its parent"""
1170
1143
        if entry.file_id in self._byid:
1171
 
            raise BzrError("inventory already contains entry with id {%s}" %
1172
 
                           entry.file_id)
 
1144
            raise errors.BzrError(
 
1145
                "inventory already contains entry with id {%s}" %
 
1146
                entry.file_id)
1173
1147
        self._byid[entry.file_id] = entry
1174
1148
        for child in getattr(entry, 'children', {}).itervalues():
1175
1149
            self._add_child(child)
1228
1202
        >>> inv = Inventory()
1229
1203
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1230
1204
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1231
 
        >>> '123' in inv
 
1205
        >>> inv.has_id('123')
1232
1206
        True
1233
1207
        >>> del inv['123']
1234
 
        >>> '123' in inv
 
1208
        >>> inv.has_id('123')
1235
1209
        False
1236
1210
        """
1237
1211
        ie = self[file_id]
1339
1313
        """
1340
1314
        new_name = ensure_normalized_name(new_name)
1341
1315
        if not is_valid_name(new_name):
1342
 
            raise BzrError("not an acceptable filename: %r" % new_name)
 
1316
            raise errors.BzrError("not an acceptable filename: %r" % new_name)
1343
1317
 
1344
1318
        new_parent = self._byid[new_parent_id]
1345
1319
        if new_name in new_parent.children:
1346
 
            raise BzrError("%r already exists in %r" % (new_name, self.id2path(new_parent_id)))
 
1320
            raise errors.BzrError("%r already exists in %r" %
 
1321
                (new_name, self.id2path(new_parent_id)))
1347
1322
 
1348
1323
        new_parent_idpath = self.get_idpath(new_parent_id)
1349
1324
        if file_id in new_parent_idpath:
1350
 
            raise BzrError("cannot move directory %r into a subdirectory of itself, %r"
 
1325
            raise errors.BzrError(
 
1326
                "cannot move directory %r into a subdirectory of itself, %r"
1351
1327
                    % (self.id2path(file_id), self.id2path(new_parent_id)))
1352
1328
 
1353
1329
        file_ie = self._byid[file_id]
1389
1365
    def __init__(self, search_key_name):
1390
1366
        CommonInventory.__init__(self)
1391
1367
        self._fileid_to_entry_cache = {}
 
1368
        self._fully_cached = False
1392
1369
        self._path_to_fileid_cache = {}
1393
1370
        self._search_key_name = search_key_name
1394
1371
        self.root_id = None
1481
1458
            if entry.kind == 'directory':
1482
1459
                directories_to_expand.add(entry.file_id)
1483
1460
            interesting.add(entry.parent_id)
1484
 
            children_of_parent_id.setdefault(entry.parent_id, []
1485
 
                                             ).append(entry.file_id)
 
1461
            children_of_parent_id.setdefault(entry.parent_id, set()
 
1462
                                             ).add(entry.file_id)
1486
1463
 
1487
1464
        # Now, interesting has all of the direct parents, but not the
1488
1465
        # parents of those parents. It also may have some duplicates with
1496
1473
            next_parents = set()
1497
1474
            for entry in self._getitems(remaining_parents):
1498
1475
                next_parents.add(entry.parent_id)
1499
 
                children_of_parent_id.setdefault(entry.parent_id, []
1500
 
                                                 ).append(entry.file_id)
 
1476
                children_of_parent_id.setdefault(entry.parent_id, set()
 
1477
                                                 ).add(entry.file_id)
1501
1478
            # Remove any search tips we've already processed
1502
1479
            remaining_parents = next_parents.difference(interesting)
1503
1480
            interesting.update(remaining_parents)
1516
1493
            for entry in self._getitems(next_file_ids):
1517
1494
                if entry.kind == 'directory':
1518
1495
                    directories_to_expand.add(entry.file_id)
1519
 
                children_of_parent_id.setdefault(entry.parent_id, []
1520
 
                                                 ).append(entry.file_id)
 
1496
                children_of_parent_id.setdefault(entry.parent_id, set()
 
1497
                                                 ).add(entry.file_id)
1521
1498
        return interesting, children_of_parent_id
1522
1499
 
1523
1500
    def filter(self, specific_fileids):
1601
1578
        self._fileid_to_entry_cache[result.file_id] = result
1602
1579
        return result
1603
1580
 
1604
 
    def _get_mutable_inventory(self):
1605
 
        """See CommonInventory._get_mutable_inventory."""
1606
 
        entries = self.iter_entries()
1607
 
        inv = Inventory(None, self.revision_id)
1608
 
        for path, inv_entry in entries:
1609
 
            inv.add(inv_entry.copy())
1610
 
        return inv
1611
 
 
1612
1581
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
1613
1582
        propagate_caches=False):
1614
1583
        """Create a new CHKInventory by applying inventory_delta to this one.
1955
1924
 
1956
1925
    def iter_just_entries(self):
1957
1926
        """Iterate over all entries.
1958
 
        
 
1927
 
1959
1928
        Unlike iter_entries(), just the entries are returned (not (path, ie))
1960
1929
        and the order of entries is undefined.
1961
1930
 
1969
1938
                self._fileid_to_entry_cache[file_id] = ie
1970
1939
            yield ie
1971
1940
 
 
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
 
1972
1994
    def iter_changes(self, basis):
1973
1995
        """Generate a Tree.iter_changes change list between this and basis.
1974
1996
 
2071
2093
    def path2id(self, relpath):
2072
2094
        """See CommonInventory.path2id()."""
2073
2095
        # 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)
2074
2103
        result = self._path_to_fileid_cache.get(relpath, None)
2075
2104
        if result is not None:
2076
2105
            return result
2077
 
        if isinstance(relpath, basestring):
2078
 
            names = osutils.splitpath(relpath)
2079
 
        else:
2080
 
            names = relpath
2081
2106
        current_id = self.root_id
2082
2107
        if current_id is None:
2083
2108
            return None
2231
2256
    return name
2232
2257
 
2233
2258
 
2234
 
_NAME_RE = None
 
2259
_NAME_RE = lazy_regex.lazy_compile(r'^[^/\\]+$')
2235
2260
 
2236
2261
def is_valid_name(name):
2237
 
    global _NAME_RE
2238
 
    if _NAME_RE is None:
2239
 
        _NAME_RE = re.compile(r'^[^/\\]+$')
2240
 
 
2241
2262
    return bool(_NAME_RE.match(name))
2242
2263
 
2243
2264
 
2333
2354
            raise errors.InconsistentDelta(new_path, item[1],
2334
2355
                "new_path with no entry")
2335
2356
        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