~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: John Arbash Meinel
  • Date: 2010-07-13 07:44:02 UTC
  • mto: This revision was merged to the branch mainline in revision 5342.
  • Revision ID: john@arbash-meinel.com-20100713074402-wp3oh7cyx76fkvmm
Bump trunk to 2.3-dev1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 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
51
51
    )
52
52
from bzrlib.symbol_versioning import deprecated_in, deprecated_method
53
53
from bzrlib.trace import mutter
 
54
from bzrlib.static_tuple import StaticTuple
54
55
 
55
56
 
56
57
class InventoryEntry(object):
437
438
            self.text_id is not None):
438
439
            checker._report_items.append('directory {%s} has text in revision {%s}'
439
440
                                % (self.file_id, rev_id))
440
 
        # Directories are stored as ''.
 
441
        # In non rich root repositories we do not expect a file graph for the
 
442
        # root.
 
443
        if self.name == '' and not checker.rich_roots:
 
444
            return
 
445
        # Directories are stored as an empty file, but the file should exist
 
446
        # to provide a per-fileid log. The hash of every directory content is
 
447
        # "da..." below (the sha1sum of '').
441
448
        checker.add_pending_item(rev_id,
442
449
            ('texts', self.file_id, self.revision), 'text',
443
450
             'da39a3ee5e6b4b0d3255bfef95601890afd80709')
754
761
        >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
755
762
        >>> print i.id2path('foo-id')
756
763
        src/foo.c
 
764
 
 
765
        :raises NoSuchId: If file_id is not present in the inventory.
757
766
        """
758
767
        # get all names, skipping root
759
768
        return '/'.join(reversed(
950
959
        descend(self.root, u'')
951
960
        return accum
952
961
 
953
 
    def path2id(self, name):
 
962
    def path2id(self, relpath):
954
963
        """Walk down through directories to return entry of last component.
955
964
 
956
 
        names may be either a list of path components, or a single
957
 
        string, in which case it is automatically split.
 
965
        :param relpath: may be either a list of path components, or a single
 
966
            string, in which case it is automatically split.
958
967
 
959
968
        This returns the entry of the last component in the path,
960
969
        which may be either a file or a directory.
961
970
 
962
971
        Returns None IFF the path is not found.
963
972
        """
964
 
        if isinstance(name, basestring):
965
 
            name = osutils.splitpath(name)
966
 
 
967
 
        # mutter("lookup path %r" % name)
 
973
        if isinstance(relpath, basestring):
 
974
            names = osutils.splitpath(relpath)
 
975
        else:
 
976
            names = relpath
968
977
 
969
978
        try:
970
979
            parent = self.root
973
982
            return None
974
983
        if parent is None:
975
984
            return None
976
 
        for f in name:
 
985
        for f in names:
977
986
            try:
978
987
                children = getattr(parent, 'children', None)
979
988
                if children is None:
1189
1198
            raise errors.InconsistentDelta("<deleted>", parent_id,
1190
1199
                "The file id was deleted but its children were not deleted.")
1191
1200
 
 
1201
    def create_by_apply_delta(self, inventory_delta, new_revision_id,
 
1202
                              propagate_caches=False):
 
1203
        """See CHKInventory.create_by_apply_delta()"""
 
1204
        new_inv = self.copy()
 
1205
        new_inv.apply_delta(inventory_delta)
 
1206
        new_inv.revision_id = new_revision_id
 
1207
        return new_inv
 
1208
 
1192
1209
    def _set_root(self, ie):
1193
1210
        self.root = ie
1194
1211
        self._byid = {self.root.file_id: self.root}
1538
1555
        else:
1539
1556
            raise ValueError("unknown kind %r" % entry.kind)
1540
1557
 
 
1558
    def _expand_fileids_to_parents_and_children(self, file_ids):
 
1559
        """Give a more wholistic view starting with the given file_ids.
 
1560
 
 
1561
        For any file_id which maps to a directory, we will include all children
 
1562
        of that directory. We will also include all directories which are
 
1563
        parents of the given file_ids, but we will not include their children.
 
1564
 
 
1565
        eg:
 
1566
          /     # TREE_ROOT
 
1567
          foo/  # foo-id
 
1568
            baz # baz-id
 
1569
            frob/ # frob-id
 
1570
              fringle # fringle-id
 
1571
          bar/  # bar-id
 
1572
            bing # bing-id
 
1573
 
 
1574
        if given [foo-id] we will include
 
1575
            TREE_ROOT as interesting parents
 
1576
        and 
 
1577
            foo-id, baz-id, frob-id, fringle-id
 
1578
        As interesting ids.
 
1579
        """
 
1580
        interesting = set()
 
1581
        # TODO: Pre-pass over the list of fileids to see if anything is already
 
1582
        #       deserialized in self._fileid_to_entry_cache
 
1583
 
 
1584
        directories_to_expand = set()
 
1585
        children_of_parent_id = {}
 
1586
        # It is okay if some of the fileids are missing
 
1587
        for entry in self._getitems(file_ids):
 
1588
            if entry.kind == 'directory':
 
1589
                directories_to_expand.add(entry.file_id)
 
1590
            interesting.add(entry.parent_id)
 
1591
            children_of_parent_id.setdefault(entry.parent_id, []
 
1592
                                             ).append(entry.file_id)
 
1593
 
 
1594
        # Now, interesting has all of the direct parents, but not the
 
1595
        # parents of those parents. It also may have some duplicates with
 
1596
        # specific_fileids
 
1597
        remaining_parents = interesting.difference(file_ids)
 
1598
        # When we hit the TREE_ROOT, we'll get an interesting parent of None,
 
1599
        # but we don't actually want to recurse into that
 
1600
        interesting.add(None) # this will auto-filter it in the loop
 
1601
        remaining_parents.discard(None) 
 
1602
        while remaining_parents:
 
1603
            next_parents = set()
 
1604
            for entry in self._getitems(remaining_parents):
 
1605
                next_parents.add(entry.parent_id)
 
1606
                children_of_parent_id.setdefault(entry.parent_id, []
 
1607
                                                 ).append(entry.file_id)
 
1608
            # Remove any search tips we've already processed
 
1609
            remaining_parents = next_parents.difference(interesting)
 
1610
            interesting.update(remaining_parents)
 
1611
            # We should probably also .difference(directories_to_expand)
 
1612
        interesting.update(file_ids)
 
1613
        interesting.discard(None)
 
1614
        while directories_to_expand:
 
1615
            # Expand directories by looking in the
 
1616
            # parent_id_basename_to_file_id map
 
1617
            keys = [StaticTuple(f,).intern() for f in directories_to_expand]
 
1618
            directories_to_expand = set()
 
1619
            items = self.parent_id_basename_to_file_id.iteritems(keys)
 
1620
            next_file_ids = set([item[1] for item in items])
 
1621
            next_file_ids = next_file_ids.difference(interesting)
 
1622
            interesting.update(next_file_ids)
 
1623
            for entry in self._getitems(next_file_ids):
 
1624
                if entry.kind == 'directory':
 
1625
                    directories_to_expand.add(entry.file_id)
 
1626
                children_of_parent_id.setdefault(entry.parent_id, []
 
1627
                                                 ).append(entry.file_id)
 
1628
        return interesting, children_of_parent_id
 
1629
 
 
1630
    def filter(self, specific_fileids):
 
1631
        """Get an inventory view filtered against a set of file-ids.
 
1632
 
 
1633
        Children of directories and parents are included.
 
1634
 
 
1635
        The result may or may not reference the underlying inventory
 
1636
        so it should be treated as immutable.
 
1637
        """
 
1638
        (interesting,
 
1639
         parent_to_children) = self._expand_fileids_to_parents_and_children(
 
1640
                                specific_fileids)
 
1641
        # There is some overlap here, but we assume that all interesting items
 
1642
        # are in the _fileid_to_entry_cache because we had to read them to
 
1643
        # determine if they were a dir we wanted to recurse, or just a file
 
1644
        # This should give us all the entries we'll want to add, so start
 
1645
        # adding
 
1646
        other = Inventory(self.root_id)
 
1647
        other.root.revision = self.root.revision
 
1648
        other.revision_id = self.revision_id
 
1649
        if not interesting or not parent_to_children:
 
1650
            # empty filter, or filtering entrys that don't exist
 
1651
            # (if even 1 existed, then we would have populated
 
1652
            # parent_to_children with at least the tree root.)
 
1653
            return other
 
1654
        cache = self._fileid_to_entry_cache
 
1655
        remaining_children = collections.deque(parent_to_children[self.root_id])
 
1656
        while remaining_children:
 
1657
            file_id = remaining_children.popleft()
 
1658
            ie = cache[file_id]
 
1659
            if ie.kind == 'directory':
 
1660
                ie = ie.copy() # We create a copy to depopulate the .children attribute
 
1661
            # TODO: depending on the uses of 'other' we should probably alwyas
 
1662
            #       '.copy()' to prevent someone from mutating other and
 
1663
            #       invaliding our internal cache
 
1664
            other.add(ie)
 
1665
            if file_id in parent_to_children:
 
1666
                remaining_children.extend(parent_to_children[file_id])
 
1667
        return other
 
1668
 
1541
1669
    @staticmethod
1542
1670
    def _bytes_to_utf8name_key(bytes):
1543
1671
        """Get the file_id, revision_id key out of bytes."""
1545
1673
        # to filter out empty names because of non rich-root...
1546
1674
        sections = bytes.split('\n')
1547
1675
        kind, file_id = sections[0].split(': ')
1548
 
        return (sections[2], file_id, sections[3])
 
1676
        return (sections[2], intern(file_id), intern(sections[3]))
1549
1677
 
1550
1678
    def _bytes_to_entry(self, bytes):
1551
1679
        """Deserialise a serialised entry."""
1573
1701
            result.reference_revision = sections[4]
1574
1702
        else:
1575
1703
            raise ValueError("Not a serialised entry %r" % bytes)
1576
 
        result.revision = sections[3]
 
1704
        result.file_id = intern(result.file_id)
 
1705
        result.revision = intern(sections[3])
1577
1706
        if result.parent_id == '':
1578
1707
            result.parent_id = None
1579
1708
        self._fileid_to_entry_cache[result.file_id] = result
1677
1806
                        pass
1678
1807
                deletes.add(file_id)
1679
1808
            else:
1680
 
                new_key = (file_id,)
 
1809
                new_key = StaticTuple(file_id,)
1681
1810
                new_value = result._entry_to_bytes(entry)
1682
1811
                # Update caches. It's worth doing this whether
1683
1812
                # we're propagating the old caches or not.
1686
1815
            if old_path is None:
1687
1816
                old_key = None
1688
1817
            else:
1689
 
                old_key = (file_id,)
 
1818
                old_key = StaticTuple(file_id,)
1690
1819
                if self.id2path(file_id) != old_path:
1691
1820
                    raise errors.InconsistentDelta(old_path, file_id,
1692
1821
                        "Entry was at wrong other path %r." %
1693
1822
                        self.id2path(file_id))
1694
1823
                altered.add(file_id)
1695
 
            id_to_entry_delta.append((old_key, new_key, new_value))
 
1824
            id_to_entry_delta.append(StaticTuple(old_key, new_key, new_value))
1696
1825
            if result.parent_id_basename_to_file_id is not None:
1697
1826
                # parent_id, basename changes
1698
1827
                if old_path is None:
1785
1914
                raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1786
1915
                                      % (key, bytes))
1787
1916
            info[key] = value
1788
 
        revision_id = info['revision_id']
1789
 
        root_id = info['root_id']
1790
 
        search_key_name = info.get('search_key_name', 'plain')
1791
 
        parent_id_basename_to_file_id = info.get(
1792
 
            'parent_id_basename_to_file_id', None)
 
1917
        revision_id = intern(info['revision_id'])
 
1918
        root_id = intern(info['root_id'])
 
1919
        search_key_name = intern(info.get('search_key_name', 'plain'))
 
1920
        parent_id_basename_to_file_id = intern(info.get(
 
1921
            'parent_id_basename_to_file_id', None))
 
1922
        if not parent_id_basename_to_file_id.startswith('sha1:'):
 
1923
            raise ValueError('parent_id_basename_to_file_id should be a sha1'
 
1924
                             ' key not %r' % (parent_id_basename_to_file_id,))
1793
1925
        id_to_entry = info['id_to_entry']
 
1926
        if not id_to_entry.startswith('sha1:'):
 
1927
            raise ValueError('id_to_entry should be a sha1'
 
1928
                             ' key not %r' % (id_to_entry,))
1794
1929
 
1795
1930
        result = CHKInventory(search_key_name)
1796
1931
        result.revision_id = revision_id
1799
1934
                            result._search_key_name)
1800
1935
        if parent_id_basename_to_file_id is not None:
1801
1936
            result.parent_id_basename_to_file_id = chk_map.CHKMap(
1802
 
                chk_store, (parent_id_basename_to_file_id,),
 
1937
                chk_store, StaticTuple(parent_id_basename_to_file_id,),
1803
1938
                search_key_func=search_key_func)
1804
1939
        else:
1805
1940
            result.parent_id_basename_to_file_id = None
1806
1941
 
1807
 
        result.id_to_entry = chk_map.CHKMap(chk_store, (id_to_entry,),
 
1942
        result.id_to_entry = chk_map.CHKMap(chk_store,
 
1943
                                            StaticTuple(id_to_entry,),
1808
1944
                                            search_key_func=search_key_func)
1809
1945
        if (result.revision_id,) != expected_revision_id:
1810
1946
            raise ValueError("Mismatched revision id and expected: %r, %r" %
1832
1968
        id_to_entry_dict = {}
1833
1969
        parent_id_basename_dict = {}
1834
1970
        for path, entry in inventory.iter_entries():
1835
 
            id_to_entry_dict[(entry.file_id,)] = entry_to_bytes(entry)
 
1971
            key = StaticTuple(entry.file_id,).intern()
 
1972
            id_to_entry_dict[key] = entry_to_bytes(entry)
1836
1973
            p_id_key = parent_id_basename_key(entry)
1837
1974
            parent_id_basename_dict[p_id_key] = entry.file_id
1838
1975
 
1861
1998
            parent_id = entry.parent_id
1862
1999
        else:
1863
2000
            parent_id = ''
1864
 
        return parent_id, entry.name.encode('utf8')
 
2001
        return StaticTuple(parent_id, entry.name.encode('utf8')).intern()
1865
2002
 
1866
2003
    def __getitem__(self, file_id):
1867
2004
        """map a single file_id -> InventoryEntry."""
1872
2009
            return result
1873
2010
        try:
1874
2011
            return self._bytes_to_entry(
1875
 
                self.id_to_entry.iteritems([(file_id,)]).next()[1])
 
2012
                self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
1876
2013
        except StopIteration:
1877
2014
            # really we're passing an inventory, not a tree...
1878
2015
            raise errors.NoSuchId(self, file_id)
1879
2016
 
 
2017
    def _getitems(self, file_ids):
 
2018
        """Similar to __getitem__, but lets you query for multiple.
 
2019
        
 
2020
        The returned order is undefined. And currently if an item doesn't
 
2021
        exist, it isn't included in the output.
 
2022
        """
 
2023
        result = []
 
2024
        remaining = []
 
2025
        for file_id in file_ids:
 
2026
            entry = self._fileid_to_entry_cache.get(file_id, None)
 
2027
            if entry is None:
 
2028
                remaining.append(file_id)
 
2029
            else:
 
2030
                result.append(entry)
 
2031
        file_keys = [StaticTuple(f,).intern() for f in remaining]
 
2032
        for file_key, value in self.id_to_entry.iteritems(file_keys):
 
2033
            entry = self._bytes_to_entry(value)
 
2034
            result.append(entry)
 
2035
            self._fileid_to_entry_cache[entry.file_id] = entry
 
2036
        return result
 
2037
 
1880
2038
    def has_id(self, file_id):
1881
2039
        # Perhaps have an explicit 'contains' method on CHKMap ?
1882
2040
        if self._fileid_to_entry_cache.get(file_id, None) is not None:
1883
2041
            return True
1884
 
        return len(list(self.id_to_entry.iteritems([(file_id,)]))) == 1
 
2042
        return len(list(
 
2043
            self.id_to_entry.iteritems([StaticTuple(file_id,)]))) == 1
1885
2044
 
1886
2045
    def is_root(self, file_id):
1887
2046
        return file_id == self.root_id
2016
2175
            delta.append((old_path, new_path, file_id, entry))
2017
2176
        return delta
2018
2177
 
2019
 
    def path2id(self, name):
 
2178
    def path2id(self, relpath):
2020
2179
        """See CommonInventory.path2id()."""
2021
2180
        # TODO: perhaps support negative hits?
2022
 
        result = self._path_to_fileid_cache.get(name, None)
 
2181
        result = self._path_to_fileid_cache.get(relpath, None)
2023
2182
        if result is not None:
2024
2183
            return result
2025
 
        if isinstance(name, basestring):
2026
 
            names = osutils.splitpath(name)
 
2184
        if isinstance(relpath, basestring):
 
2185
            names = osutils.splitpath(relpath)
2027
2186
        else:
2028
 
            names = name
 
2187
            names = relpath
2029
2188
        current_id = self.root_id
2030
2189
        if current_id is None:
2031
2190
            return None
2032
2191
        parent_id_index = self.parent_id_basename_to_file_id
 
2192
        cur_path = None
2033
2193
        for basename in names:
2034
 
            # TODO: Cache each path we figure out in this function.
 
2194
            if cur_path is None:
 
2195
                cur_path = basename
 
2196
            else:
 
2197
                cur_path = cur_path + '/' + basename
2035
2198
            basename_utf8 = basename.encode('utf8')
2036
 
            key_filter = [(current_id, basename_utf8)]
2037
 
            file_id = None
2038
 
            for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2039
 
                key_filter=key_filter):
2040
 
                if parent_id != current_id or name_utf8 != basename_utf8:
2041
 
                    raise errors.BzrError("corrupt inventory lookup! "
2042
 
                        "%r %r %r %r" % (parent_id, current_id, name_utf8,
2043
 
                        basename_utf8))
 
2199
            file_id = self._path_to_fileid_cache.get(cur_path, None)
2044
2200
            if file_id is None:
2045
 
                return None
 
2201
                key_filter = [StaticTuple(current_id, basename_utf8)]
 
2202
                items = parent_id_index.iteritems(key_filter)
 
2203
                for (parent_id, name_utf8), file_id in items:
 
2204
                    if parent_id != current_id or name_utf8 != basename_utf8:
 
2205
                        raise errors.BzrError("corrupt inventory lookup! "
 
2206
                            "%r %r %r %r" % (parent_id, current_id, name_utf8,
 
2207
                            basename_utf8))
 
2208
                if file_id is None:
 
2209
                    return None
 
2210
                else:
 
2211
                    self._path_to_fileid_cache[cur_path] = file_id
2046
2212
            current_id = file_id
2047
 
        self._path_to_fileid_cache[name] = current_id
2048
2213
        return current_id
2049
2214
 
2050
2215
    def to_lines(self):
2055
2220
            lines.append('search_key_name: %s\n' % (self._search_key_name,))
2056
2221
            lines.append("root_id: %s\n" % self.root_id)
2057
2222
            lines.append('parent_id_basename_to_file_id: %s\n' %
2058
 
                self.parent_id_basename_to_file_id.key())
 
2223
                (self.parent_id_basename_to_file_id.key()[0],))
2059
2224
            lines.append("revision_id: %s\n" % self.revision_id)
2060
 
            lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
 
2225
            lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2061
2226
        else:
2062
2227
            lines.append("revision_id: %s\n" % self.revision_id)
2063
2228
            lines.append("root_id: %s\n" % self.root_id)
2064
2229
            if self.parent_id_basename_to_file_id is not None:
2065
2230
                lines.append('parent_id_basename_to_file_id: %s\n' %
2066
 
                    self.parent_id_basename_to_file_id.key())
2067
 
            lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
 
2231
                    (self.parent_id_basename_to_file_id.key()[0],))
 
2232
            lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2068
2233
        return lines
2069
2234
 
2070
2235
    @property
2111
2276
        parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
2112
2277
        child_keys = set()
2113
2278
        for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2114
 
            key_filter=[(self.file_id,)]):
2115
 
            child_keys.add((file_id,))
 
2279
            key_filter=[StaticTuple(self.file_id,)]):
 
2280
            child_keys.add(StaticTuple(file_id,))
2116
2281
        cached = set()
2117
2282
        for file_id_key in child_keys:
2118
2283
            entry = self._chk_inventory._fileid_to_entry_cache.get(
2151
2316
    try:
2152
2317
        factory = entry_factory[kind]
2153
2318
    except KeyError:
2154
 
        raise BzrError("unknown kind %r" % kind)
 
2319
        raise errors.BadFileKindError(name, kind)
2155
2320
    return factory(file_id, name, parent_id)
2156
2321
 
2157
2322