1594
1538
result.reference_revision = sections[4]
1596
1540
raise ValueError("Not a serialised entry %r" % bytes)
1597
result.file_id = intern(result.file_id)
1598
result.revision = intern(sections[3])
1599
if result.parent_id == '':
1600
result.parent_id = None
1601
self._fileid_to_entry_cache[result.file_id] = result
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())
1612
def create_by_apply_delta(self, inventory_delta, new_revision_id,
1613
propagate_caches=False):
1614
"""Create a new CHKInventory by applying inventory_delta to this one.
1616
See the inventory developers documentation for the theory behind
1619
:param inventory_delta: The inventory delta to apply. See
1620
Inventory.apply_delta for details.
1621
:param new_revision_id: The revision id of the resulting CHKInventory.
1622
:param propagate_caches: If True, the caches for this inventory are
1623
copied to and updated for the result.
1624
:return: The new CHKInventory.
1626
split = osutils.split
1627
result = CHKInventory(self._search_key_name)
1628
if propagate_caches:
1629
# Just propagate the path-to-fileid cache for now
1630
result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
1631
search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1632
self.id_to_entry._ensure_root()
1633
maximum_size = self.id_to_entry._root_node.maximum_size
1634
result.revision_id = new_revision_id
1635
result.id_to_entry = chk_map.CHKMap(
1636
self.id_to_entry._store,
1637
self.id_to_entry.key(),
1638
search_key_func=search_key_func)
1639
result.id_to_entry._ensure_root()
1640
result.id_to_entry._root_node.set_maximum_size(maximum_size)
1641
# Change to apply to the parent_id_basename delta. The dict maps
1642
# (parent_id, basename) -> (old_key, new_value). We use a dict because
1643
# when a path has its id replaced (e.g. the root is changed, or someone
1644
# does bzr mv a b, bzr mv c a, we should output a single change to this
1645
# map rather than two.
1646
parent_id_basename_delta = {}
1647
if self.parent_id_basename_to_file_id is not None:
1648
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1649
self.parent_id_basename_to_file_id._store,
1650
self.parent_id_basename_to_file_id.key(),
1651
search_key_func=search_key_func)
1652
result.parent_id_basename_to_file_id._ensure_root()
1653
self.parent_id_basename_to_file_id._ensure_root()
1654
result_p_id_root = result.parent_id_basename_to_file_id._root_node
1655
p_id_root = self.parent_id_basename_to_file_id._root_node
1656
result_p_id_root.set_maximum_size(p_id_root.maximum_size)
1657
result_p_id_root._key_width = p_id_root._key_width
1659
result.parent_id_basename_to_file_id = None
1660
result.root_id = self.root_id
1661
id_to_entry_delta = []
1662
# inventory_delta is only traversed once, so we just update the
1664
# Check for repeated file ids
1665
inventory_delta = _check_delta_unique_ids(inventory_delta)
1666
# Repeated old paths
1667
inventory_delta = _check_delta_unique_old_paths(inventory_delta)
1668
# Check for repeated new paths
1669
inventory_delta = _check_delta_unique_new_paths(inventory_delta)
1670
# Check for entries that don't match the fileid
1671
inventory_delta = _check_delta_ids_match_entry(inventory_delta)
1672
# Check for nonsense fileids
1673
inventory_delta = _check_delta_ids_are_valid(inventory_delta)
1674
# Check for new_path <-> entry consistency
1675
inventory_delta = _check_delta_new_path_entry_both_or_None(
1677
# All changed entries need to have their parents be directories and be
1678
# at the right path. This set contains (path, id) tuples.
1680
# When we delete an item, all the children of it must be either deleted
1681
# or altered in their own right. As we batch process the change via
1682
# CHKMap.apply_delta, we build a set of things to use to validate the
1686
for old_path, new_path, file_id, entry in inventory_delta:
1689
result.root_id = file_id
1690
if new_path is None:
1695
if propagate_caches:
1697
del result._path_to_fileid_cache[old_path]
1700
deletes.add(file_id)
1702
new_key = StaticTuple(file_id,)
1703
new_value = result._entry_to_bytes(entry)
1704
# Update caches. It's worth doing this whether
1705
# we're propagating the old caches or not.
1706
result._path_to_fileid_cache[new_path] = file_id
1707
parents.add((split(new_path)[0], entry.parent_id))
1708
if old_path is None:
1711
old_key = StaticTuple(file_id,)
1712
if self.id2path(file_id) != old_path:
1713
raise errors.InconsistentDelta(old_path, file_id,
1714
"Entry was at wrong other path %r." %
1715
self.id2path(file_id))
1716
altered.add(file_id)
1717
id_to_entry_delta.append(StaticTuple(old_key, new_key, new_value))
1718
if result.parent_id_basename_to_file_id is not None:
1719
# parent_id, basename changes
1720
if old_path is None:
1723
old_entry = self[file_id]
1724
old_key = self._parent_id_basename_key(old_entry)
1725
if new_path is None:
1729
new_key = self._parent_id_basename_key(entry)
1731
# If the two keys are the same, the value will be unchanged
1732
# as its always the file id for this entry.
1733
if old_key != new_key:
1734
# Transform a change into explicit delete/add preserving
1735
# a possible match on the key from a different file id.
1736
if old_key is not None:
1737
parent_id_basename_delta.setdefault(
1738
old_key, [None, None])[0] = old_key
1739
if new_key is not None:
1740
parent_id_basename_delta.setdefault(
1741
new_key, [None, None])[1] = new_value
1742
# validate that deletes are complete.
1743
for file_id in deletes:
1744
entry = self[file_id]
1745
if entry.kind != 'directory':
1747
# This loop could potentially be better by using the id_basename
1748
# map to just get the child file ids.
1749
for child in entry.children.values():
1750
if child.file_id not in altered:
1751
raise errors.InconsistentDelta(self.id2path(child.file_id),
1752
child.file_id, "Child not deleted or reparented when "
1754
result.id_to_entry.apply_delta(id_to_entry_delta)
1755
if parent_id_basename_delta:
1756
# Transform the parent_id_basename delta data into a linear delta
1757
# with only one record for a given key. Optimally this would allow
1758
# re-keying, but its simpler to just output that as a delete+add
1759
# to spend less time calculating the delta.
1761
for key, (old_key, value) in parent_id_basename_delta.iteritems():
1762
if value is not None:
1763
delta_list.append((old_key, key, value))
1765
delta_list.append((old_key, None, None))
1766
result.parent_id_basename_to_file_id.apply_delta(delta_list)
1767
parents.discard(('', None))
1768
for parent_path, parent in parents:
1770
if result[parent].kind != 'directory':
1771
raise errors.InconsistentDelta(result.id2path(parent), parent,
1772
'Not a directory, but given children')
1773
except errors.NoSuchId:
1774
raise errors.InconsistentDelta("<unknown>", parent,
1775
"Parent is not present in resulting inventory.")
1776
if result.path2id(parent_path) != parent:
1777
raise errors.InconsistentDelta(parent_path, parent,
1778
"Parent has wrong path %r." % result.path2id(parent_path))
1782
def deserialise(klass, chk_store, bytes, expected_revision_id):
1783
"""Deserialise a CHKInventory.
1785
:param chk_store: A CHK capable VersionedFiles instance.
1786
:param bytes: The serialised bytes.
1787
:param expected_revision_id: The revision ID we think this inventory is
1789
:return: A CHKInventory
1791
lines = bytes.split('\n')
1793
raise AssertionError('bytes to deserialize must end with an eol')
1795
if lines[0] != 'chkinventory:':
1796
raise ValueError("not a serialised CHKInventory: %r" % bytes)
1798
allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
1799
'parent_id_basename_to_file_id',
1801
for line in lines[1:]:
1802
key, value = line.split(': ', 1)
1803
if key not in allowed_keys:
1804
raise errors.BzrError('Unknown key in inventory: %r\n%r'
1807
raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1810
revision_id = intern(info['revision_id'])
1811
root_id = intern(info['root_id'])
1812
search_key_name = intern(info.get('search_key_name', 'plain'))
1813
parent_id_basename_to_file_id = intern(info.get(
1814
'parent_id_basename_to_file_id', None))
1815
if not parent_id_basename_to_file_id.startswith('sha1:'):
1816
raise ValueError('parent_id_basename_to_file_id should be a sha1'
1817
' key not %r' % (parent_id_basename_to_file_id,))
1818
id_to_entry = info['id_to_entry']
1819
if not id_to_entry.startswith('sha1:'):
1820
raise ValueError('id_to_entry should be a sha1'
1821
' key not %r' % (id_to_entry,))
1823
result = CHKInventory(search_key_name)
1824
result.revision_id = revision_id
1825
result.root_id = root_id
1826
search_key_func = chk_map.search_key_registry.get(
1827
result._search_key_name)
1828
if parent_id_basename_to_file_id is not None:
1829
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1830
chk_store, StaticTuple(parent_id_basename_to_file_id,),
1831
search_key_func=search_key_func)
1833
result.parent_id_basename_to_file_id = None
1835
result.id_to_entry = chk_map.CHKMap(chk_store,
1836
StaticTuple(id_to_entry,),
1837
search_key_func=search_key_func)
1838
if (result.revision_id,) != expected_revision_id:
1839
raise ValueError("Mismatched revision id and expected: %r, %r" %
1840
(result.revision_id, expected_revision_id))
1844
def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name='plain'):
1845
"""Create a CHKInventory from an existing inventory.
1847
The content of inventory is copied into the chk_store, and a
1848
CHKInventory referencing that is returned.
1850
:param chk_store: A CHK capable VersionedFiles instance.
1851
:param inventory: The inventory to copy.
1852
:param maximum_size: The CHKMap node size limit.
1853
:param search_key_name: The identifier for the search key function
1855
result = klass(search_key_name)
1856
result.revision_id = inventory.revision_id
1857
result.root_id = inventory.root.file_id
1859
entry_to_bytes = result._entry_to_bytes
1860
parent_id_basename_key = result._parent_id_basename_key
1861
id_to_entry_dict = {}
1862
parent_id_basename_dict = {}
1863
for path, entry in inventory.iter_entries():
1864
key = StaticTuple(entry.file_id,).intern()
1865
id_to_entry_dict[key] = entry_to_bytes(entry)
1866
p_id_key = parent_id_basename_key(entry)
1867
parent_id_basename_dict[p_id_key] = entry.file_id
1869
result._populate_from_dicts(chk_store, id_to_entry_dict,
1870
parent_id_basename_dict, maximum_size=maximum_size)
1873
def _populate_from_dicts(self, chk_store, id_to_entry_dict,
1874
parent_id_basename_dict, maximum_size):
1875
search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1876
root_key = chk_map.CHKMap.from_dict(chk_store, id_to_entry_dict,
1877
maximum_size=maximum_size, key_width=1,
1878
search_key_func=search_key_func)
1879
self.id_to_entry = chk_map.CHKMap(chk_store, root_key,
1881
root_key = chk_map.CHKMap.from_dict(chk_store,
1882
parent_id_basename_dict,
1883
maximum_size=maximum_size, key_width=2,
1884
search_key_func=search_key_func)
1885
self.parent_id_basename_to_file_id = chk_map.CHKMap(chk_store,
1886
root_key, search_key_func)
1888
def _parent_id_basename_key(self, entry):
1889
"""Create a key for a entry in a parent_id_basename_to_file_id index."""
1890
if entry.parent_id is not None:
1891
parent_id = entry.parent_id
1894
return StaticTuple(parent_id, entry.name.encode('utf8')).intern()
1896
def __getitem__(self, file_id):
1897
"""map a single file_id -> InventoryEntry."""
1899
raise errors.NoSuchId(self, file_id)
1900
result = self._fileid_to_entry_cache.get(file_id, None)
1901
if result is not None:
1904
return self._bytes_to_entry(
1905
self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
1906
except StopIteration:
1907
# really we're passing an inventory, not a tree...
1908
raise errors.NoSuchId(self, file_id)
1910
def _getitems(self, file_ids):
1911
"""Similar to __getitem__, but lets you query for multiple.
1913
The returned order is undefined. And currently if an item doesn't
1914
exist, it isn't included in the output.
1918
for file_id in file_ids:
1919
entry = self._fileid_to_entry_cache.get(file_id, None)
1921
remaining.append(file_id)
1923
result.append(entry)
1924
file_keys = [StaticTuple(f,).intern() for f in remaining]
1925
for file_key, value in self.id_to_entry.iteritems(file_keys):
1926
entry = self._bytes_to_entry(value)
1927
result.append(entry)
1928
self._fileid_to_entry_cache[entry.file_id] = entry
1931
def has_id(self, file_id):
1932
# Perhaps have an explicit 'contains' method on CHKMap ?
1933
if self._fileid_to_entry_cache.get(file_id, None) is not None:
1936
self.id_to_entry.iteritems([StaticTuple(file_id,)]))) == 1
1938
def is_root(self, file_id):
1939
return file_id == self.root_id
1941
def _iter_file_id_parents(self, file_id):
1942
"""Yield the parents of file_id up to the root."""
1943
while file_id is not None:
1947
raise errors.NoSuchId(tree=self, file_id=file_id)
1949
file_id = ie.parent_id
1952
"""Iterate over all file-ids."""
1953
for key, _ in self.id_to_entry.iteritems():
1956
def iter_just_entries(self):
1957
"""Iterate over all entries.
1959
Unlike iter_entries(), just the entries are returned (not (path, ie))
1960
and the order of entries is undefined.
1962
XXX: We may not want to merge this into bzr.dev.
1964
for key, entry in self.id_to_entry.iteritems():
1966
ie = self._fileid_to_entry_cache.get(file_id, None)
1968
ie = self._bytes_to_entry(entry)
1969
self._fileid_to_entry_cache[file_id] = ie
1972
def iter_changes(self, basis):
1973
"""Generate a Tree.iter_changes change list between this and basis.
1975
:param basis: Another CHKInventory.
1976
:return: An iterator over the changes between self and basis, as per
1977
tree.iter_changes().
1979
# We want: (file_id, (path_in_source, path_in_target),
1980
# changed_content, versioned, parent, name, kind,
1982
for key, basis_value, self_value in \
1983
self.id_to_entry.iter_changes(basis.id_to_entry):
1985
if basis_value is not None:
1986
basis_entry = basis._bytes_to_entry(basis_value)
1987
path_in_source = basis.id2path(file_id)
1988
basis_parent = basis_entry.parent_id
1989
basis_name = basis_entry.name
1990
basis_executable = basis_entry.executable
1992
path_in_source = None
1995
basis_executable = None
1996
if self_value is not None:
1997
self_entry = self._bytes_to_entry(self_value)
1998
path_in_target = self.id2path(file_id)
1999
self_parent = self_entry.parent_id
2000
self_name = self_entry.name
2001
self_executable = self_entry.executable
2003
path_in_target = None
2006
self_executable = None
2007
if basis_value is None:
2009
kind = (None, self_entry.kind)
2010
versioned = (False, True)
2011
elif self_value is None:
2013
kind = (basis_entry.kind, None)
2014
versioned = (True, False)
2016
kind = (basis_entry.kind, self_entry.kind)
2017
versioned = (True, True)
2018
changed_content = False
2019
if kind[0] != kind[1]:
2020
changed_content = True
2021
elif kind[0] == 'file':
2022
if (self_entry.text_size != basis_entry.text_size or
2023
self_entry.text_sha1 != basis_entry.text_sha1):
2024
changed_content = True
2025
elif kind[0] == 'symlink':
2026
if self_entry.symlink_target != basis_entry.symlink_target:
2027
changed_content = True
2028
elif kind[0] == 'tree-reference':
2029
if (self_entry.reference_revision !=
2030
basis_entry.reference_revision):
2031
changed_content = True
2032
parent = (basis_parent, self_parent)
2033
name = (basis_name, self_name)
2034
executable = (basis_executable, self_executable)
2035
if (not changed_content
2036
and parent[0] == parent[1]
2037
and name[0] == name[1]
2038
and executable[0] == executable[1]):
2039
# Could happen when only the revision changed for a directory
2042
yield (file_id, (path_in_source, path_in_target), changed_content,
2043
versioned, parent, name, kind, executable)
2046
"""Return the number of entries in the inventory."""
2047
return len(self.id_to_entry)
2049
def _make_delta(self, old):
2050
"""Make an inventory delta from two inventories."""
2051
if type(old) != CHKInventory:
2052
return CommonInventory._make_delta(self, old)
2054
for key, old_value, self_value in \
2055
self.id_to_entry.iter_changes(old.id_to_entry):
2057
if old_value is not None:
2058
old_path = old.id2path(file_id)
2061
if self_value is not None:
2062
entry = self._bytes_to_entry(self_value)
2063
self._fileid_to_entry_cache[file_id] = entry
2064
new_path = self.id2path(file_id)
2068
delta.append((old_path, new_path, file_id, entry))
2071
def path2id(self, relpath):
2072
"""See CommonInventory.path2id()."""
2073
# TODO: perhaps support negative hits?
2074
result = self._path_to_fileid_cache.get(relpath, None)
2075
if result is not None:
2077
if isinstance(relpath, basestring):
2078
names = osutils.splitpath(relpath)
2081
current_id = self.root_id
2082
if current_id is None:
2084
parent_id_index = self.parent_id_basename_to_file_id
2086
for basename in names:
2087
if cur_path is None:
2090
cur_path = cur_path + '/' + basename
2091
basename_utf8 = basename.encode('utf8')
2092
file_id = self._path_to_fileid_cache.get(cur_path, None)
2094
key_filter = [StaticTuple(current_id, basename_utf8)]
2095
items = parent_id_index.iteritems(key_filter)
2096
for (parent_id, name_utf8), file_id in items:
2097
if parent_id != current_id or name_utf8 != basename_utf8:
2098
raise errors.BzrError("corrupt inventory lookup! "
2099
"%r %r %r %r" % (parent_id, current_id, name_utf8,
2104
self._path_to_fileid_cache[cur_path] = file_id
2105
current_id = file_id
2109
"""Serialise the inventory to lines."""
2110
lines = ["chkinventory:\n"]
2111
if self._search_key_name != 'plain':
2112
# custom ordering grouping things that don't change together
2113
lines.append('search_key_name: %s\n' % (self._search_key_name,))
2114
lines.append("root_id: %s\n" % self.root_id)
2115
lines.append('parent_id_basename_to_file_id: %s\n' %
2116
(self.parent_id_basename_to_file_id.key()[0],))
2117
lines.append("revision_id: %s\n" % self.revision_id)
2118
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2120
lines.append("revision_id: %s\n" % self.revision_id)
2121
lines.append("root_id: %s\n" % self.root_id)
2122
if self.parent_id_basename_to_file_id is not None:
2123
lines.append('parent_id_basename_to_file_id: %s\n' %
2124
(self.parent_id_basename_to_file_id.key()[0],))
2125
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2130
"""Get the root entry."""
2131
return self[self.root_id]
2134
class CHKInventoryDirectory(InventoryDirectory):
2135
"""A directory in an inventory."""
2137
__slots__ = ['_children', '_chk_inventory']
2139
def __init__(self, file_id, name, parent_id, chk_inventory):
2140
# Don't call InventoryDirectory.__init__ - it isn't right for this
2142
InventoryEntry.__init__(self, file_id, name, parent_id)
2143
self._children = None
2144
self._chk_inventory = chk_inventory
2148
"""Access the list of children of this directory.
2150
With a parent_id_basename_to_file_id index, loads all the children,
2151
without loads the entire index. Without is bad. A more sophisticated
2152
proxy object might be nice, to allow partial loading of children as
2153
well when specific names are accessed. (So path traversal can be
2154
written in the obvious way but not examine siblings.).
2156
if self._children is not None:
2157
return self._children
2158
# No longer supported
2159
if self._chk_inventory.parent_id_basename_to_file_id is None:
2160
raise AssertionError("Inventories without"
2161
" parent_id_basename_to_file_id are no longer supported")
2163
# XXX: Todo - use proxy objects for the children rather than loading
2164
# all when the attribute is referenced.
2165
parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
2167
for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2168
key_filter=[StaticTuple(self.file_id,)]):
2169
child_keys.add(StaticTuple(file_id,))
2171
for file_id_key in child_keys:
2172
entry = self._chk_inventory._fileid_to_entry_cache.get(
2173
file_id_key[0], None)
2174
if entry is not None:
2175
result[entry.name] = entry
2176
cached.add(file_id_key)
2177
child_keys.difference_update(cached)
2178
# populate; todo: do by name
2179
id_to_entry = self._chk_inventory.id_to_entry
2180
for file_id_key, bytes in id_to_entry.iteritems(child_keys):
2181
entry = self._chk_inventory._bytes_to_entry(bytes)
2182
result[entry.name] = entry
2183
self._chk_inventory._fileid_to_entry_cache[file_id_key[0]] = entry
2184
self._children = result
1541
result.revision = sections[3]
1542
if result.parent_id == '':
1543
result.parent_id = None
1544
self._fileid_to_entry_cache[result.file_id] = result
1547
def _get_mutable_inventory(self):
1548
"""See CommonInventory._get_mutable_inventory."""
1549
entries = self.iter_entries()
1550
if self.root_id is not None:
1552
inv = Inventory(self.root_id, self.revision_id)
1553
for path, inv_entry in entries:
1557
def create_by_apply_delta(self, inventory_delta, new_revision_id,
1558
propagate_caches=False):
1559
"""Create a new CHKInventory by applying inventory_delta to this one.
1561
:param inventory_delta: The inventory delta to apply. See
1562
Inventory.apply_delta for details.
1563
:param new_revision_id: The revision id of the resulting CHKInventory.
1564
:param propagate_caches: If True, the caches for this inventory are
1565
copied to and updated for the result.
1566
:return: The new CHKInventory.
1568
result = CHKInventory(self._search_key_name)
1569
if propagate_caches:
1570
# Just propagate the path-to-fileid cache for now
1571
result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
1572
search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1573
self.id_to_entry._ensure_root()
1574
maximum_size = self.id_to_entry._root_node.maximum_size
1575
result.revision_id = new_revision_id
1576
result.id_to_entry = chk_map.CHKMap(
1577
self.id_to_entry._store,
1578
self.id_to_entry.key(),
1579
search_key_func=search_key_func)
1580
result.id_to_entry._ensure_root()
1581
result.id_to_entry._root_node.set_maximum_size(maximum_size)
1582
parent_id_basename_delta = []
1583
if self.parent_id_basename_to_file_id is not None:
1584
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1585
self.parent_id_basename_to_file_id._store,
1586
self.parent_id_basename_to_file_id.key(),
1587
search_key_func=search_key_func)
1588
result.parent_id_basename_to_file_id._ensure_root()
1589
self.parent_id_basename_to_file_id._ensure_root()
1590
result_p_id_root = result.parent_id_basename_to_file_id._root_node
1591
p_id_root = self.parent_id_basename_to_file_id._root_node
1592
result_p_id_root.set_maximum_size(p_id_root.maximum_size)
1593
result_p_id_root._key_width = p_id_root._key_width
1595
result.parent_id_basename_to_file_id = None
1596
result.root_id = self.root_id
1597
id_to_entry_delta = []
1598
for old_path, new_path, file_id, entry in inventory_delta:
1601
result.root_id = file_id
1602
if new_path is None:
1607
if propagate_caches:
1609
del result._path_to_fileid_cache[old_path]
1613
new_key = (file_id,)
1614
new_value = result._entry_to_bytes(entry)
1615
# Update caches. It's worth doing this whether
1616
# we're propagating the old caches or not.
1617
result._path_to_fileid_cache[new_path] = file_id
1618
if old_path is None:
1621
old_key = (file_id,)
1622
id_to_entry_delta.append((old_key, new_key, new_value))
1623
if result.parent_id_basename_to_file_id is not None:
1624
# parent_id, basename changes
1625
if old_path is None:
1628
old_entry = self[file_id]
1629
old_key = self._parent_id_basename_key(old_entry)
1630
if new_path is None:
1634
new_key = self._parent_id_basename_key(entry)
1636
if old_key != new_key:
1637
# If the two keys are the same, the value will be unchanged
1638
# as its always the file id.
1639
parent_id_basename_delta.append((old_key, new_key, new_value))
1640
result.id_to_entry.apply_delta(id_to_entry_delta)
1641
if parent_id_basename_delta:
1642
result.parent_id_basename_to_file_id.apply_delta(parent_id_basename_delta)
1646
def deserialise(klass, chk_store, bytes, expected_revision_id):
1647
"""Deserialise a CHKInventory.
1649
:param chk_store: A CHK capable VersionedFiles instance.
1650
:param bytes: The serialised bytes.
1651
:param expected_revision_id: The revision ID we think this inventory is
1653
:return: A CHKInventory
1655
lines = bytes.split('\n')
1656
assert lines[-1] == ''
1658
if lines[0] != 'chkinventory:':
1659
raise ValueError("not a serialised CHKInventory: %r" % bytes)
1661
allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
1662
'parent_id_basename_to_file_id',
1664
for line in lines[1:]:
1665
key, value = line.split(': ', 1)
1666
if key not in allowed_keys:
1667
raise errors.BzrError('Unknown key in inventory: %r\n%r'
1670
raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1673
revision_id = info['revision_id']
1674
root_id = info['root_id']
1675
search_key_name = info.get('search_key_name', 'plain')
1676
parent_id_basename_to_file_id = info.get(
1677
'parent_id_basename_to_file_id', None)
1678
id_to_entry = info['id_to_entry']
1680
result = CHKInventory(search_key_name)
1681
result.revision_id = revision_id
1682
result.root_id = root_id
1683
search_key_func = chk_map.search_key_registry.get(
1684
result._search_key_name)
1685
if parent_id_basename_to_file_id is not None:
1686
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1687
chk_store, (parent_id_basename_to_file_id,),
1688
search_key_func=search_key_func)
1690
result.parent_id_basename_to_file_id = None
1692
result.id_to_entry = chk_map.CHKMap(chk_store, (id_to_entry,),
1693
search_key_func=search_key_func)
1694
if (result.revision_id,) != expected_revision_id:
1695
raise ValueError("Mismatched revision id and expected: %r, %r" %
1696
(result.revision_id, expected_revision_id))
1700
def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name='plain'):
1701
"""Create a CHKInventory from an existing inventory.
1703
The content of inventory is copied into the chk_store, and a
1704
CHKInventory referencing that is returned.
1706
:param chk_store: A CHK capable VersionedFiles instance.
1707
:param inventory: The inventory to copy.
1708
:param maximum_size: The CHKMap node size limit.
1709
:param search_key_name: The identifier for the search key function
1711
result = CHKInventory(search_key_name)
1712
result.revision_id = inventory.revision_id
1713
result.root_id = inventory.root.file_id
1714
search_key_func = chk_map.search_key_registry.get(search_key_name)
1715
result.id_to_entry = chk_map.CHKMap(chk_store, None, search_key_func)
1716
result.id_to_entry._root_node.set_maximum_size(maximum_size)
1718
result.parent_id_basename_to_file_id = chk_map.CHKMap(chk_store,
1719
None, search_key_func)
1720
result.parent_id_basename_to_file_id._root_node.set_maximum_size(
1722
result.parent_id_basename_to_file_id._root_node._key_width = 2
1723
parent_id_delta = []
1724
for path, entry in inventory.iter_entries():
1725
file_id_delta.append((None, (entry.file_id,),
1726
result._entry_to_bytes(entry)))
1727
parent_id_delta.append(
1728
(None, result._parent_id_basename_key(entry),
1730
result.id_to_entry.apply_delta(file_id_delta)
1731
result.parent_id_basename_to_file_id.apply_delta(parent_id_delta)
1734
def _parent_id_basename_key(self, entry):
1735
"""Create a key for a entry in a parent_id_basename_to_file_id index."""
1736
if entry.parent_id is not None:
1737
parent_id = entry.parent_id
1740
return parent_id, entry.name.encode('utf8')
1742
def __getitem__(self, file_id):
1743
"""map a single file_id -> InventoryEntry."""
1745
raise errors.NoSuchId(self, file_id)
1746
result = self._fileid_to_entry_cache.get(file_id, None)
1747
if result is not None:
1750
return self._bytes_to_entry(
1751
self.id_to_entry.iteritems([(file_id,)]).next()[1])
1752
except StopIteration:
1753
# really we're passing an inventory, not a tree...
1754
raise errors.NoSuchId(self, file_id)
1756
def has_id(self, file_id):
1757
# Perhaps have an explicit 'contains' method on CHKMap ?
1758
if self._fileid_to_entry_cache.get(file_id, None) is not None:
1760
return len(list(self.id_to_entry.iteritems([(file_id,)]))) == 1
1762
def is_root(self, file_id):
1763
return file_id == self.root_id
1765
def _iter_file_id_parents(self, file_id):
1766
"""Yield the parents of file_id up to the root."""
1767
while file_id is not None:
1771
raise errors.NoSuchId(tree=self, file_id=file_id)
1773
file_id = ie.parent_id
1776
"""Iterate over all file-ids."""
1777
for key, _ in self.id_to_entry.iteritems():
1780
def iter_just_entries(self):
1781
"""Iterate over all entries.
1783
Unlike iter_entries(), just the entries are returned (not (path, ie))
1784
and the order of entries is undefined.
1786
XXX: We may not want to merge this into bzr.dev.
1788
for key, entry in self.id_to_entry.iteritems():
1790
ie = self._fileid_to_entry_cache.get(file_id, None)
1792
ie = self._bytes_to_entry(entry)
1793
self._fileid_to_entry_cache[file_id] = ie
1796
def iter_changes(self, basis):
1797
"""Generate a Tree.iter_changes change list between this and basis.
1799
:param basis: Another CHKInventory.
1800
:return: An iterator over the changes between self and basis, as per
1801
tree.iter_changes().
1803
# We want: (file_id, (path_in_source, path_in_target),
1804
# changed_content, versioned, parent, name, kind,
1806
for key, basis_value, self_value in \
1807
self.id_to_entry.iter_changes(basis.id_to_entry):
1809
if basis_value is not None:
1810
basis_entry = basis._bytes_to_entry(basis_value)
1811
path_in_source = basis.id2path(file_id)
1812
basis_parent = basis_entry.parent_id
1813
basis_name = basis_entry.name
1814
basis_executable = basis_entry.executable
1816
path_in_source = None
1819
basis_executable = None
1820
if self_value is not None:
1821
self_entry = self._bytes_to_entry(self_value)
1822
path_in_target = self.id2path(file_id)
1823
self_parent = self_entry.parent_id
1824
self_name = self_entry.name
1825
self_executable = self_entry.executable
1827
path_in_target = None
1830
self_executable = None
1831
if basis_value is None:
1833
kind = (None, self_entry.kind)
1834
versioned = (False, True)
1835
elif self_value is None:
1837
kind = (basis_entry.kind, None)
1838
versioned = (True, False)
1840
kind = (basis_entry.kind, self_entry.kind)
1841
versioned = (True, True)
1842
changed_content = False
1843
if kind[0] != kind[1]:
1844
changed_content = True
1845
elif kind[0] == 'file':
1846
if (self_entry.text_size != basis_entry.text_size or
1847
self_entry.text_sha1 != basis_entry.text_sha1):
1848
changed_content = True
1849
elif kind[0] == 'symlink':
1850
if self_entry.symlink_target != basis_entry.symlink_target:
1851
changed_content = True
1852
elif kind[0] == 'tree-reference':
1853
if (self_entry.reference_revision !=
1854
basis_entry.reference_revision):
1855
changed_content = True
1856
parent = (basis_parent, self_parent)
1857
name = (basis_name, self_name)
1858
executable = (basis_executable, self_executable)
1859
if (not changed_content
1860
and parent[0] == parent[1]
1861
and name[0] == name[1]
1862
and executable[0] == executable[1]):
1863
# Could happen when only the revision changed for a directory
1866
yield (file_id, (path_in_source, path_in_target), changed_content,
1867
versioned, parent, name, kind, executable)
1870
"""Return the number of entries in the inventory."""
1871
return len(self.id_to_entry)
1873
def _make_delta(self, old):
1874
"""Make an inventory delta from two inventories."""
1875
if type(old) != CHKInventory:
1876
return CommonInventory._make_delta(self, old)
1878
for key, old_value, self_value in \
1879
self.id_to_entry.iter_changes(old.id_to_entry):
1881
if old_value is not None:
1882
old_path = old.id2path(file_id)
1885
if self_value is not None:
1886
entry = self._bytes_to_entry(self_value)
1887
self._fileid_to_entry_cache[file_id] = entry
1888
new_path = self.id2path(file_id)
1892
delta.append((old_path, new_path, file_id, entry))
1895
def path2id(self, name):
1896
"""See CommonInventory.path2id()."""
1897
result = self._path_to_fileid_cache.get(name, None)
1899
result = CommonInventory.path2id(self, name)
1900
self._path_to_fileid_cache[name] = result
1904
"""Serialise the inventory to lines."""
1905
lines = ["chkinventory:\n"]
1906
if self._search_key_name != 'plain':
1907
# custom ordering grouping things that don't change together
1908
lines.append('search_key_name: %s\n' % (self._search_key_name,))
1909
lines.append("root_id: %s\n" % self.root_id)
1910
lines.append('parent_id_basename_to_file_id: %s\n' %
1911
self.parent_id_basename_to_file_id.key())
1912
lines.append("revision_id: %s\n" % self.revision_id)
1913
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
1915
lines.append("revision_id: %s\n" % self.revision_id)
1916
lines.append("root_id: %s\n" % self.root_id)
1917
if self.parent_id_basename_to_file_id is not None:
1918
lines.append('parent_id_basename_to_file_id: %s\n' %
1919
self.parent_id_basename_to_file_id.key())
1920
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
1925
"""Get the root entry."""
1926
return self[self.root_id]
1929
class CHKInventoryDirectory(InventoryDirectory):
1930
"""A directory in an inventory."""
1932
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
1933
'text_id', 'parent_id', '_children', 'executable',
1934
'revision', 'symlink_target', 'reference_revision',
1937
def __init__(self, file_id, name, parent_id, chk_inventory):
1938
# Don't call InventoryDirectory.__init__ - it isn't right for this
1940
InventoryEntry.__init__(self, file_id, name, parent_id)
1941
self._children = None
1942
self.kind = 'directory'
1943
self._chk_inventory = chk_inventory
1947
"""Access the list of children of this directory.
1949
With a parent_id_basename_to_file_id index, loads all the children,
1950
without loads the entire index. Without is bad. A more sophisticated
1951
proxy object might be nice, to allow partial loading of children as
1952
well when specific names are accessed. (So path traversal can be
1953
written in the obvious way but not examine siblings.).
1955
if self._children is not None:
1956
return self._children
1957
# No longer supported
1958
if self._chk_inventory.parent_id_basename_to_file_id is None:
1959
raise AssertionError("Inventories without"
1960
" parent_id_basename_to_file_id are no longer supported")
1962
# XXX: Todo - use proxy objects for the children rather than loading
1963
# all when the attribute is referenced.
1964
parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
1966
for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
1967
key_filter=[(self.file_id,)]):
1968
child_keys.add((file_id,))
1970
for file_id_key in child_keys:
1971
entry = self._chk_inventory._fileid_to_entry_cache.get(
1972
file_id_key[0], None)
1973
if entry is not None:
1974
result[entry.name] = entry
1975
cached.add(file_id_key)
1976
child_keys.difference_update(cached)
1977
# populate; todo: do by name
1978
id_to_entry = self._chk_inventory.id_to_entry
1979
for file_id_key, bytes in id_to_entry.iteritems(child_keys):
1980
entry = self._chk_inventory._bytes_to_entry(bytes)
1981
result[entry.name] = entry
1982
self._chk_inventory._fileid_to_entry_cache[file_id_key[0]] = entry
1983
self._children = result
1987
class CHKInventory(CommonInventory):
1988
"""An inventory persisted in a CHK store.
1990
By design, a CHKInventory is immutable so many of the methods
1991
supported by Inventory - add, rename, apply_delta, etc - are *not*
1992
supported. To create a new CHKInventory, use create_by_apply_delta()
1993
or from_inventory(), say.
1995
Internally, a CHKInventory has one or two CHKMaps:
1997
* id_to_entry - a map from (file_id,) => InventoryEntry as bytes
1998
* parent_id_basename_to_file_id - a map from (parent_id, basename_utf8)
2001
The second map is optional and not present in early CHkRepository's.
2003
No caching is performed: every method call or item access will perform
2004
requests to the storage layer. As such, keep references to objects you
2008
def __init__(self, search_key_name):
2009
CommonInventory.__init__(self)
2010
self._fileid_to_entry_cache = {}
2011
self._path_to_fileid_cache = {}
2012
self._search_key_name = search_key_name
2014
def _entry_to_bytes(self, entry):
2015
"""Serialise entry as a single bytestring.
2017
:param Entry: An inventory entry.
2018
:return: A bytestring for the entry.
2021
ENTRY ::= FILE | DIR | SYMLINK | TREE
2022
FILE ::= "file: " COMMON SEP SHA SEP SIZE SEP EXECUTABLE
2023
DIR ::= "dir: " COMMON
2024
SYMLINK ::= "symlink: " COMMON SEP TARGET_UTF8
2025
TREE ::= "tree: " COMMON REFERENCE_REVISION
2026
COMMON ::= FILE_ID SEP PARENT_ID SEP NAME_UTF8 SEP REVISION
2029
if entry.parent_id is not None:
2030
parent_str = entry.parent_id
2033
name_str = entry.name.encode("utf8")
2034
if entry.kind == 'file':
2035
if entry.executable:
2039
return "file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
2040
entry.file_id, parent_str, name_str, entry.revision,
2041
entry.text_sha1, entry.text_size, exec_str)
2042
elif entry.kind == 'directory':
2043
return "dir: %s\n%s\n%s\n%s" % (
2044
entry.file_id, parent_str, name_str, entry.revision)
2045
elif entry.kind == 'symlink':
2046
return "symlink: %s\n%s\n%s\n%s\n%s" % (
2047
entry.file_id, parent_str, name_str, entry.revision,
2048
entry.symlink_target.encode("utf8"))
2049
elif entry.kind == 'tree-reference':
2050
return "tree: %s\n%s\n%s\n%s\n%s" % (
2051
entry.file_id, parent_str, name_str, entry.revision,
2052
entry.reference_revision)
2054
raise ValueError("unknown kind %r" % entry.kind)
2056
def _bytes_to_entry(self, bytes):
2057
"""Deserialise a serialised entry."""
2058
sections = bytes.split('\n')
2059
if sections[0].startswith("file: "):
2060
result = InventoryFile(sections[0][6:],
2061
sections[2].decode('utf8'),
2063
result.text_sha1 = sections[4]
2064
result.text_size = int(sections[5])
2065
result.executable = sections[6] == "Y"
2066
elif sections[0].startswith("dir: "):
2067
result = CHKInventoryDirectory(sections[0][5:],
2068
sections[2].decode('utf8'),
2070
elif sections[0].startswith("symlink: "):
2071
result = InventoryLink(sections[0][9:],
2072
sections[2].decode('utf8'),
2074
result.symlink_target = sections[4]
2075
elif sections[0].startswith("tree: "):
2076
result = TreeReference(sections[0][6:],
2077
sections[2].decode('utf8'),
2079
result.reference_revision = sections[4]
2081
raise ValueError("Not a serialised entry %r" % bytes)
2082
result.revision = sections[3]
2083
if result.parent_id == '':
2084
result.parent_id = None
2085
self._fileid_to_entry_cache[result.file_id] = result
2088
def _get_mutable_inventory(self):
2089
"""See CommonInventory._get_mutable_inventory."""
2090
entries = self.iter_entries()
2091
if self.root_id is not None:
2093
inv = Inventory(self.root_id, self.revision_id)
2094
for path, inv_entry in entries:
2098
def create_by_apply_delta(self, inventory_delta, new_revision_id,
2099
propagate_caches=False):
2100
"""Create a new CHKInventory by applying inventory_delta to this one.
2102
:param inventory_delta: The inventory delta to apply. See
2103
Inventory.apply_delta for details.
2104
:param new_revision_id: The revision id of the resulting CHKInventory.
2105
:param propagate_caches: If True, the caches for this inventory are
2106
copied to and updated for the result.
2107
:return: The new CHKInventory.
2109
result = CHKInventory(self._search_key_name)
2110
if propagate_caches:
2111
# Just propagate the path-to-fileid cache for now
2112
result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
2113
search_key_func = chk_map.search_key_registry.get(self._search_key_name)
2114
self.id_to_entry._ensure_root()
2115
maximum_size = self.id_to_entry._root_node.maximum_size
2116
result.revision_id = new_revision_id
2117
result.id_to_entry = chk_map.CHKMap(
2118
self.id_to_entry._store,
2119
self.id_to_entry.key(),
2120
search_key_func=search_key_func)
2121
result.id_to_entry._ensure_root()
2122
result.id_to_entry._root_node.set_maximum_size(maximum_size)
2123
parent_id_basename_delta = []
2124
if self.parent_id_basename_to_file_id is not None:
2125
result.parent_id_basename_to_file_id = chk_map.CHKMap(
2126
self.parent_id_basename_to_file_id._store,
2127
self.parent_id_basename_to_file_id.key(),
2128
search_key_func=search_key_func)
2129
result.parent_id_basename_to_file_id._ensure_root()
2130
self.parent_id_basename_to_file_id._ensure_root()
2131
result_p_id_root = result.parent_id_basename_to_file_id._root_node
2132
p_id_root = self.parent_id_basename_to_file_id._root_node
2133
result_p_id_root.set_maximum_size(p_id_root.maximum_size)
2134
result_p_id_root._key_width = p_id_root._key_width
2136
result.parent_id_basename_to_file_id = None
2137
result.root_id = self.root_id
2138
id_to_entry_delta = []
2139
for old_path, new_path, file_id, entry in inventory_delta:
2142
result.root_id = file_id
2143
if new_path is None:
2148
if propagate_caches:
2150
del result._path_to_fileid_cache[old_path]
2154
new_key = (file_id,)
2155
new_value = result._entry_to_bytes(entry)
2156
# Update caches. It's worth doing this whether
2157
# we're propagating the old caches or not.
2158
result._path_to_fileid_cache[new_path] = file_id
2159
if old_path is None:
2162
old_key = (file_id,)
2163
id_to_entry_delta.append((old_key, new_key, new_value))
2164
if result.parent_id_basename_to_file_id is not None:
2165
# parent_id, basename changes
2166
if old_path is None:
2169
old_entry = self[file_id]
2170
old_key = self._parent_id_basename_key(old_entry)
2171
if new_path is None:
2175
new_key = self._parent_id_basename_key(entry)
2177
if old_key != new_key:
2178
# If the two keys are the same, the value will be unchanged
2179
# as its always the file id.
2180
parent_id_basename_delta.append((old_key, new_key, new_value))
2181
result.id_to_entry.apply_delta(id_to_entry_delta)
2182
if parent_id_basename_delta:
2183
result.parent_id_basename_to_file_id.apply_delta(parent_id_basename_delta)
2187
def deserialise(klass, chk_store, bytes, expected_revision_id):
2188
"""Deserialise a CHKInventory.
2190
:param chk_store: A CHK capable VersionedFiles instance.
2191
:param bytes: The serialised bytes.
2192
:param expected_revision_id: The revision ID we think this inventory is
2194
:return: A CHKInventory
2196
lines = bytes.split('\n')
2197
assert lines[-1] == ''
2199
if lines[0] != 'chkinventory:':
2200
raise ValueError("not a serialised CHKInventory: %r" % bytes)
2202
allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
2203
'parent_id_basename_to_file_id',
2205
for line in lines[1:]:
2206
key, value = line.split(': ', 1)
2207
if key not in allowed_keys:
2208
raise errors.BzrError('Unknown key in inventory: %r\n%r'
2211
raise errors.BzrError('Duplicate key in inventory: %r\n%r'
2214
revision_id = info['revision_id']
2215
root_id = info['root_id']
2216
search_key_name = info.get('search_key_name', 'plain')
2217
parent_id_basename_to_file_id = info.get(
2218
'parent_id_basename_to_file_id', None)
2219
id_to_entry = info['id_to_entry']
2221
result = CHKInventory(search_key_name)
2222
result.revision_id = revision_id
2223
result.root_id = root_id
2224
search_key_func = chk_map.search_key_registry.get(
2225
result._search_key_name)
2226
if parent_id_basename_to_file_id is not None:
2227
result.parent_id_basename_to_file_id = chk_map.CHKMap(
2228
chk_store, (parent_id_basename_to_file_id,),
2229
search_key_func=search_key_func)
2231
result.parent_id_basename_to_file_id = None
2233
result.id_to_entry = chk_map.CHKMap(chk_store, (id_to_entry,),
2234
search_key_func=search_key_func)
2235
if (result.revision_id,) != expected_revision_id:
2236
raise ValueError("Mismatched revision id and expected: %r, %r" %
2237
(result.revision_id, expected_revision_id))
2241
def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name='plain'):
2242
"""Create a CHKInventory from an existing inventory.
2244
The content of inventory is copied into the chk_store, and a
2245
CHKInventory referencing that is returned.
2247
:param chk_store: A CHK capable VersionedFiles instance.
2248
:param inventory: The inventory to copy.
2249
:param maximum_size: The CHKMap node size limit.
2250
:param search_key_name: The identifier for the search key function
2252
result = CHKInventory(search_key_name)
2253
result.revision_id = inventory.revision_id
2254
result.root_id = inventory.root.file_id
2255
search_key_func = chk_map.search_key_registry.get(search_key_name)
2256
result.id_to_entry = chk_map.CHKMap(chk_store, None, search_key_func)
2257
result.id_to_entry._root_node.set_maximum_size(maximum_size)
2259
result.parent_id_basename_to_file_id = chk_map.CHKMap(chk_store,
2260
None, search_key_func)
2261
result.parent_id_basename_to_file_id._root_node.set_maximum_size(
2263
result.parent_id_basename_to_file_id._root_node._key_width = 2
2264
parent_id_delta = []
2265
for path, entry in inventory.iter_entries():
2266
file_id_delta.append((None, (entry.file_id,),
2267
result._entry_to_bytes(entry)))
2268
parent_id_delta.append(
2269
(None, result._parent_id_basename_key(entry),
2271
result.id_to_entry.apply_delta(file_id_delta)
2272
result.parent_id_basename_to_file_id.apply_delta(parent_id_delta)
2275
def _parent_id_basename_key(self, entry):
2276
"""Create a key for a entry in a parent_id_basename_to_file_id index."""
2277
if entry.parent_id is not None:
2278
parent_id = entry.parent_id
2281
return parent_id, entry.name.encode('utf8')
2283
def __getitem__(self, file_id):
2284
"""map a single file_id -> InventoryEntry."""
2286
raise errors.NoSuchId(self, file_id)
2287
result = self._fileid_to_entry_cache.get(file_id, None)
2288
if result is not None:
2291
return self._bytes_to_entry(
2292
self.id_to_entry.iteritems([(file_id,)]).next()[1])
2293
except StopIteration:
2294
# really we're passing an inventory, not a tree...
2295
raise errors.NoSuchId(self, file_id)
2297
def has_id(self, file_id):
2298
# Perhaps have an explicit 'contains' method on CHKMap ?
2299
if self._fileid_to_entry_cache.get(file_id, None) is not None:
2301
return len(list(self.id_to_entry.iteritems([(file_id,)]))) == 1
2303
def is_root(self, file_id):
2304
return file_id == self.root_id
2306
def _iter_file_id_parents(self, file_id):
2307
"""Yield the parents of file_id up to the root."""
2308
while file_id is not None:
2312
raise errors.NoSuchId(tree=self, file_id=file_id)
2314
file_id = ie.parent_id
2317
"""Iterate over all file-ids."""
2318
for key, _ in self.id_to_entry.iteritems():
2321
def iter_just_entries(self):
2322
"""Iterate over all entries.
2324
Unlike iter_entries(), just the entries are returned (not (path, ie))
2325
and the order of entries is undefined.
2327
XXX: We may not want to merge this into bzr.dev.
2329
for key, entry in self.id_to_entry.iteritems():
2331
ie = self._fileid_to_entry_cache.get(file_id, None)
2333
ie = self._bytes_to_entry(entry)
2334
self._fileid_to_entry_cache[file_id] = ie
2337
def iter_changes(self, basis):
2338
"""Generate a Tree.iter_changes change list between this and basis.
2340
:param basis: Another CHKInventory.
2341
:return: An iterator over the changes between self and basis, as per
2342
tree.iter_changes().
2344
# We want: (file_id, (path_in_source, path_in_target),
2345
# changed_content, versioned, parent, name, kind,
2347
for key, basis_value, self_value in \
2348
self.id_to_entry.iter_changes(basis.id_to_entry):
2350
if basis_value is not None:
2351
basis_entry = basis._bytes_to_entry(basis_value)
2352
path_in_source = basis.id2path(file_id)
2353
basis_parent = basis_entry.parent_id
2354
basis_name = basis_entry.name
2355
basis_executable = basis_entry.executable
2357
path_in_source = None
2360
basis_executable = None
2361
if self_value is not None:
2362
self_entry = self._bytes_to_entry(self_value)
2363
path_in_target = self.id2path(file_id)
2364
self_parent = self_entry.parent_id
2365
self_name = self_entry.name
2366
self_executable = self_entry.executable
2368
path_in_target = None
2371
self_executable = None
2372
if basis_value is None:
2374
kind = (None, self_entry.kind)
2375
versioned = (False, True)
2376
elif self_value is None:
2378
kind = (basis_entry.kind, None)
2379
versioned = (True, False)
2381
kind = (basis_entry.kind, self_entry.kind)
2382
versioned = (True, True)
2383
changed_content = False
2384
if kind[0] != kind[1]:
2385
changed_content = True
2386
elif kind[0] == 'file':
2387
if (self_entry.text_size != basis_entry.text_size or
2388
self_entry.text_sha1 != basis_entry.text_sha1):
2389
changed_content = True
2390
elif kind[0] == 'symlink':
2391
if self_entry.symlink_target != basis_entry.symlink_target:
2392
changed_content = True
2393
elif kind[0] == 'tree-reference':
2394
if (self_entry.reference_revision !=
2395
basis_entry.reference_revision):
2396
changed_content = True
2397
parent = (basis_parent, self_parent)
2398
name = (basis_name, self_name)
2399
executable = (basis_executable, self_executable)
2400
if (not changed_content
2401
and parent[0] == parent[1]
2402
and name[0] == name[1]
2403
and executable[0] == executable[1]):
2404
# Could happen when only the revision changed for a directory
2407
yield (file_id, (path_in_source, path_in_target), changed_content,
2408
versioned, parent, name, kind, executable)
2411
"""Return the number of entries in the inventory."""
2412
return len(self.id_to_entry)
2414
def _make_delta(self, old):
2415
"""Make an inventory delta from two inventories."""
2416
if type(old) != CHKInventory:
2417
return CommonInventory._make_delta(self, old)
2419
for key, old_value, self_value in \
2420
self.id_to_entry.iter_changes(old.id_to_entry):
2422
if old_value is not None:
2423
old_path = old.id2path(file_id)
2426
if self_value is not None:
2427
entry = self._bytes_to_entry(self_value)
2428
self._fileid_to_entry_cache[file_id] = entry
2429
new_path = self.id2path(file_id)
2433
delta.append((old_path, new_path, file_id, entry))
2436
def path2id(self, name):
2437
"""See CommonInventory.path2id()."""
2438
result = self._path_to_fileid_cache.get(name, None)
2440
result = CommonInventory.path2id(self, name)
2441
self._path_to_fileid_cache[name] = result
2445
"""Serialise the inventory to lines."""
2446
lines = ["chkinventory:\n"]
2447
if self._search_key_name != 'plain':
2448
# custom ordering grouping things that don't change together
2449
lines.append('search_key_name: %s\n' % (self._search_key_name,))
2450
lines.append("root_id: %s\n" % self.root_id)
2451
lines.append('parent_id_basename_to_file_id: %s\n' %
2452
self.parent_id_basename_to_file_id.key())
2453
lines.append("revision_id: %s\n" % self.revision_id)
2454
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
2456
lines.append("revision_id: %s\n" % self.revision_id)
2457
lines.append("root_id: %s\n" % self.root_id)
2458
if self.parent_id_basename_to_file_id is not None:
2459
lines.append('parent_id_basename_to_file_id: %s\n' %
2460
self.parent_id_basename_to_file_id.key())
2461
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
2466
"""Get the root entry."""
2467
return self[self.root_id]
2470
class CHKInventoryDirectory(InventoryDirectory):
2471
"""A directory in an inventory."""
2473
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
2474
'text_id', 'parent_id', '_children', 'executable',
2475
'revision', 'symlink_target', 'reference_revision',
2478
def __init__(self, file_id, name, parent_id, chk_inventory):
2479
# Don't call InventoryDirectory.__init__ - it isn't right for this
2481
InventoryEntry.__init__(self, file_id, name, parent_id)
2482
self._children = None
2483
self.kind = 'directory'
2484
self._chk_inventory = chk_inventory
2488
"""Access the list of children of this directory.
2490
With a parent_id_basename_to_file_id index, loads all the children,
2491
without loads the entire index. Without is bad. A more sophisticated
2492
proxy object might be nice, to allow partial loading of children as
2493
well when specific names are accessed. (So path traversal can be
2494
written in the obvious way but not examine siblings.).
2496
if self._children is not None:
2497
return self._children
2498
# No longer supported
2499
if self._chk_inventory.parent_id_basename_to_file_id is None:
2500
raise AssertionError("Inventories without"
2501
" parent_id_basename_to_file_id are no longer supported")
2503
# XXX: Todo - use proxy objects for the children rather than loading
2504
# all when the attribute is referenced.
2505
parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
2507
for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2508
key_filter=[(self.file_id,)]):
2509
child_keys.add((file_id,))
2511
for file_id_key in child_keys:
2512
entry = self._chk_inventory._fileid_to_entry_cache.get(
2513
file_id_key[0], None)
2514
if entry is not None:
2515
result[entry.name] = entry
2516
cached.add(file_id_key)
2517
child_keys.difference_update(cached)
2518
# populate; todo: do by name
2519
id_to_entry = self._chk_inventory.id_to_entry
2520
for file_id_key, bytes in id_to_entry.iteritems(child_keys):
2521
entry = self._chk_inventory._bytes_to_entry(bytes)
2522
result[entry.name] = entry
2523
self._chk_inventory._fileid_to_entry_cache[file_id_key[0]] = entry
2524
self._children = result
2187
2528
entry_factory = {
2188
2529
'directory': InventoryDirectory,