1538
1701
result.reference_revision = sections[4]
1540
1703
raise ValueError("Not a serialised entry %r" % bytes)
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
1704
result.file_id = intern(result.file_id)
1705
result.revision = intern(sections[3])
1706
if result.parent_id == '':
1707
result.parent_id = None
1708
self._fileid_to_entry_cache[result.file_id] = result
1711
def _get_mutable_inventory(self):
1712
"""See CommonInventory._get_mutable_inventory."""
1713
entries = self.iter_entries()
1714
inv = Inventory(None, self.revision_id)
1715
for path, inv_entry in entries:
1716
inv.add(inv_entry.copy())
1719
def create_by_apply_delta(self, inventory_delta, new_revision_id,
1720
propagate_caches=False):
1721
"""Create a new CHKInventory by applying inventory_delta to this one.
1723
See the inventory developers documentation for the theory behind
1726
:param inventory_delta: The inventory delta to apply. See
1727
Inventory.apply_delta for details.
1728
:param new_revision_id: The revision id of the resulting CHKInventory.
1729
:param propagate_caches: If True, the caches for this inventory are
1730
copied to and updated for the result.
1731
:return: The new CHKInventory.
1733
split = osutils.split
1734
result = CHKInventory(self._search_key_name)
1735
if propagate_caches:
1736
# Just propagate the path-to-fileid cache for now
1737
result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
1738
search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1739
self.id_to_entry._ensure_root()
1740
maximum_size = self.id_to_entry._root_node.maximum_size
1741
result.revision_id = new_revision_id
1742
result.id_to_entry = chk_map.CHKMap(
1743
self.id_to_entry._store,
1744
self.id_to_entry.key(),
1745
search_key_func=search_key_func)
1746
result.id_to_entry._ensure_root()
1747
result.id_to_entry._root_node.set_maximum_size(maximum_size)
1748
# Change to apply to the parent_id_basename delta. The dict maps
1749
# (parent_id, basename) -> (old_key, new_value). We use a dict because
1750
# when a path has its id replaced (e.g. the root is changed, or someone
1751
# does bzr mv a b, bzr mv c a, we should output a single change to this
1752
# map rather than two.
1753
parent_id_basename_delta = {}
1754
if self.parent_id_basename_to_file_id is not None:
1755
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1756
self.parent_id_basename_to_file_id._store,
1757
self.parent_id_basename_to_file_id.key(),
1758
search_key_func=search_key_func)
1759
result.parent_id_basename_to_file_id._ensure_root()
1760
self.parent_id_basename_to_file_id._ensure_root()
1761
result_p_id_root = result.parent_id_basename_to_file_id._root_node
1762
p_id_root = self.parent_id_basename_to_file_id._root_node
1763
result_p_id_root.set_maximum_size(p_id_root.maximum_size)
1764
result_p_id_root._key_width = p_id_root._key_width
1766
result.parent_id_basename_to_file_id = None
1767
result.root_id = self.root_id
1768
id_to_entry_delta = []
1769
# inventory_delta is only traversed once, so we just update the
1771
# Check for repeated file ids
1772
inventory_delta = _check_delta_unique_ids(inventory_delta)
1773
# Repeated old paths
1774
inventory_delta = _check_delta_unique_old_paths(inventory_delta)
1775
# Check for repeated new paths
1776
inventory_delta = _check_delta_unique_new_paths(inventory_delta)
1777
# Check for entries that don't match the fileid
1778
inventory_delta = _check_delta_ids_match_entry(inventory_delta)
1779
# Check for nonsense fileids
1780
inventory_delta = _check_delta_ids_are_valid(inventory_delta)
1781
# Check for new_path <-> entry consistency
1782
inventory_delta = _check_delta_new_path_entry_both_or_None(
1784
# All changed entries need to have their parents be directories and be
1785
# at the right path. This set contains (path, id) tuples.
1787
# When we delete an item, all the children of it must be either deleted
1788
# or altered in their own right. As we batch process the change via
1789
# CHKMap.apply_delta, we build a set of things to use to validate the
1793
for old_path, new_path, file_id, entry in inventory_delta:
1796
result.root_id = file_id
1797
if new_path is None:
1802
if propagate_caches:
1804
del result._path_to_fileid_cache[old_path]
1807
deletes.add(file_id)
1809
new_key = StaticTuple(file_id,)
1810
new_value = result._entry_to_bytes(entry)
1811
# Update caches. It's worth doing this whether
1812
# we're propagating the old caches or not.
1813
result._path_to_fileid_cache[new_path] = file_id
1814
parents.add((split(new_path)[0], entry.parent_id))
1815
if old_path is None:
1818
old_key = StaticTuple(file_id,)
1819
if self.id2path(file_id) != old_path:
1820
raise errors.InconsistentDelta(old_path, file_id,
1821
"Entry was at wrong other path %r." %
1822
self.id2path(file_id))
1823
altered.add(file_id)
1824
id_to_entry_delta.append(StaticTuple(old_key, new_key, new_value))
1825
if result.parent_id_basename_to_file_id is not None:
1826
# parent_id, basename changes
1827
if old_path is None:
1830
old_entry = self[file_id]
1831
old_key = self._parent_id_basename_key(old_entry)
1832
if new_path is None:
1836
new_key = self._parent_id_basename_key(entry)
1838
# If the two keys are the same, the value will be unchanged
1839
# as its always the file id for this entry.
1840
if old_key != new_key:
1841
# Transform a change into explicit delete/add preserving
1842
# a possible match on the key from a different file id.
1843
if old_key is not None:
1844
parent_id_basename_delta.setdefault(
1845
old_key, [None, None])[0] = old_key
1846
if new_key is not None:
1847
parent_id_basename_delta.setdefault(
1848
new_key, [None, None])[1] = new_value
1849
# validate that deletes are complete.
1850
for file_id in deletes:
1851
entry = self[file_id]
1852
if entry.kind != 'directory':
1854
# This loop could potentially be better by using the id_basename
1855
# map to just get the child file ids.
1856
for child in entry.children.values():
1857
if child.file_id not in altered:
1858
raise errors.InconsistentDelta(self.id2path(child.file_id),
1859
child.file_id, "Child not deleted or reparented when "
1861
result.id_to_entry.apply_delta(id_to_entry_delta)
1862
if parent_id_basename_delta:
1863
# Transform the parent_id_basename delta data into a linear delta
1864
# with only one record for a given key. Optimally this would allow
1865
# re-keying, but its simpler to just output that as a delete+add
1866
# to spend less time calculating the delta.
1868
for key, (old_key, value) in parent_id_basename_delta.iteritems():
1869
if value is not None:
1870
delta_list.append((old_key, key, value))
1872
delta_list.append((old_key, None, None))
1873
result.parent_id_basename_to_file_id.apply_delta(delta_list)
1874
parents.discard(('', None))
1875
for parent_path, parent in parents:
1877
if result[parent].kind != 'directory':
1878
raise errors.InconsistentDelta(result.id2path(parent), parent,
1879
'Not a directory, but given children')
1880
except errors.NoSuchId:
1881
raise errors.InconsistentDelta("<unknown>", parent,
1882
"Parent is not present in resulting inventory.")
1883
if result.path2id(parent_path) != parent:
1884
raise errors.InconsistentDelta(parent_path, parent,
1885
"Parent has wrong path %r." % result.path2id(parent_path))
1889
def deserialise(klass, chk_store, bytes, expected_revision_id):
1890
"""Deserialise a CHKInventory.
1892
:param chk_store: A CHK capable VersionedFiles instance.
1893
:param bytes: The serialised bytes.
1894
:param expected_revision_id: The revision ID we think this inventory is
1896
:return: A CHKInventory
1898
lines = bytes.split('\n')
1900
raise AssertionError('bytes to deserialize must end with an eol')
1902
if lines[0] != 'chkinventory:':
1903
raise ValueError("not a serialised CHKInventory: %r" % bytes)
1905
allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
1906
'parent_id_basename_to_file_id',
1908
for line in lines[1:]:
1909
key, value = line.split(': ', 1)
1910
if key not in allowed_keys:
1911
raise errors.BzrError('Unknown key in inventory: %r\n%r'
1914
raise errors.BzrError('Duplicate key in inventory: %r\n%r'
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,))
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,))
1930
result = CHKInventory(search_key_name)
1931
result.revision_id = revision_id
1932
result.root_id = root_id
1933
search_key_func = chk_map.search_key_registry.get(
1934
result._search_key_name)
1935
if parent_id_basename_to_file_id is not None:
1936
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1937
chk_store, StaticTuple(parent_id_basename_to_file_id,),
1938
search_key_func=search_key_func)
1940
result.parent_id_basename_to_file_id = None
1942
result.id_to_entry = chk_map.CHKMap(chk_store,
1943
StaticTuple(id_to_entry,),
1944
search_key_func=search_key_func)
1945
if (result.revision_id,) != expected_revision_id:
1946
raise ValueError("Mismatched revision id and expected: %r, %r" %
1947
(result.revision_id, expected_revision_id))
1951
def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name='plain'):
1952
"""Create a CHKInventory from an existing inventory.
1954
The content of inventory is copied into the chk_store, and a
1955
CHKInventory referencing that is returned.
1957
:param chk_store: A CHK capable VersionedFiles instance.
1958
:param inventory: The inventory to copy.
1959
:param maximum_size: The CHKMap node size limit.
1960
:param search_key_name: The identifier for the search key function
1962
result = klass(search_key_name)
1963
result.revision_id = inventory.revision_id
1964
result.root_id = inventory.root.file_id
1966
entry_to_bytes = result._entry_to_bytes
1967
parent_id_basename_key = result._parent_id_basename_key
1968
id_to_entry_dict = {}
1969
parent_id_basename_dict = {}
1970
for path, entry in inventory.iter_entries():
1971
key = StaticTuple(entry.file_id,).intern()
1972
id_to_entry_dict[key] = entry_to_bytes(entry)
1973
p_id_key = parent_id_basename_key(entry)
1974
parent_id_basename_dict[p_id_key] = entry.file_id
1976
result._populate_from_dicts(chk_store, id_to_entry_dict,
1977
parent_id_basename_dict, maximum_size=maximum_size)
1980
def _populate_from_dicts(self, chk_store, id_to_entry_dict,
1981
parent_id_basename_dict, maximum_size):
1982
search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1983
root_key = chk_map.CHKMap.from_dict(chk_store, id_to_entry_dict,
1984
maximum_size=maximum_size, key_width=1,
1985
search_key_func=search_key_func)
1986
self.id_to_entry = chk_map.CHKMap(chk_store, root_key,
1988
root_key = chk_map.CHKMap.from_dict(chk_store,
1989
parent_id_basename_dict,
1990
maximum_size=maximum_size, key_width=2,
1991
search_key_func=search_key_func)
1992
self.parent_id_basename_to_file_id = chk_map.CHKMap(chk_store,
1993
root_key, search_key_func)
1995
def _parent_id_basename_key(self, entry):
1996
"""Create a key for a entry in a parent_id_basename_to_file_id index."""
1997
if entry.parent_id is not None:
1998
parent_id = entry.parent_id
2001
return StaticTuple(parent_id, entry.name.encode('utf8')).intern()
2003
def __getitem__(self, file_id):
2004
"""map a single file_id -> InventoryEntry."""
2006
raise errors.NoSuchId(self, file_id)
2007
result = self._fileid_to_entry_cache.get(file_id, None)
2008
if result is not None:
2011
return self._bytes_to_entry(
2012
self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
2013
except StopIteration:
2014
# really we're passing an inventory, not a tree...
2015
raise errors.NoSuchId(self, file_id)
2017
def _getitems(self, file_ids):
2018
"""Similar to __getitem__, but lets you query for multiple.
2020
The returned order is undefined. And currently if an item doesn't
2021
exist, it isn't included in the output.
2025
for file_id in file_ids:
2026
entry = self._fileid_to_entry_cache.get(file_id, None)
2028
remaining.append(file_id)
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
2038
def has_id(self, file_id):
2039
# Perhaps have an explicit 'contains' method on CHKMap ?
2040
if self._fileid_to_entry_cache.get(file_id, None) is not None:
2043
self.id_to_entry.iteritems([StaticTuple(file_id,)]))) == 1
2045
def is_root(self, file_id):
2046
return file_id == self.root_id
2048
def _iter_file_id_parents(self, file_id):
2049
"""Yield the parents of file_id up to the root."""
2050
while file_id is not None:
2054
raise errors.NoSuchId(tree=self, file_id=file_id)
2056
file_id = ie.parent_id
2059
"""Iterate over all file-ids."""
2060
for key, _ in self.id_to_entry.iteritems():
2063
def iter_just_entries(self):
2064
"""Iterate over all entries.
2066
Unlike iter_entries(), just the entries are returned (not (path, ie))
2067
and the order of entries is undefined.
2069
XXX: We may not want to merge this into bzr.dev.
2071
for key, entry in self.id_to_entry.iteritems():
2073
ie = self._fileid_to_entry_cache.get(file_id, None)
2075
ie = self._bytes_to_entry(entry)
2076
self._fileid_to_entry_cache[file_id] = ie
2079
def iter_changes(self, basis):
2080
"""Generate a Tree.iter_changes change list between this and basis.
2082
:param basis: Another CHKInventory.
2083
:return: An iterator over the changes between self and basis, as per
2084
tree.iter_changes().
2086
# We want: (file_id, (path_in_source, path_in_target),
2087
# changed_content, versioned, parent, name, kind,
2089
for key, basis_value, self_value in \
2090
self.id_to_entry.iter_changes(basis.id_to_entry):
2092
if basis_value is not None:
2093
basis_entry = basis._bytes_to_entry(basis_value)
2094
path_in_source = basis.id2path(file_id)
2095
basis_parent = basis_entry.parent_id
2096
basis_name = basis_entry.name
2097
basis_executable = basis_entry.executable
2099
path_in_source = None
2102
basis_executable = None
2103
if self_value is not None:
2104
self_entry = self._bytes_to_entry(self_value)
2105
path_in_target = self.id2path(file_id)
2106
self_parent = self_entry.parent_id
2107
self_name = self_entry.name
2108
self_executable = self_entry.executable
2110
path_in_target = None
2113
self_executable = None
2114
if basis_value is None:
2116
kind = (None, self_entry.kind)
2117
versioned = (False, True)
2118
elif self_value is None:
2120
kind = (basis_entry.kind, None)
2121
versioned = (True, False)
2123
kind = (basis_entry.kind, self_entry.kind)
2124
versioned = (True, True)
2125
changed_content = False
2126
if kind[0] != kind[1]:
2127
changed_content = True
2128
elif kind[0] == 'file':
2129
if (self_entry.text_size != basis_entry.text_size or
2130
self_entry.text_sha1 != basis_entry.text_sha1):
2131
changed_content = True
2132
elif kind[0] == 'symlink':
2133
if self_entry.symlink_target != basis_entry.symlink_target:
2134
changed_content = True
2135
elif kind[0] == 'tree-reference':
2136
if (self_entry.reference_revision !=
2137
basis_entry.reference_revision):
2138
changed_content = True
2139
parent = (basis_parent, self_parent)
2140
name = (basis_name, self_name)
2141
executable = (basis_executable, self_executable)
2142
if (not changed_content
2143
and parent[0] == parent[1]
2144
and name[0] == name[1]
2145
and executable[0] == executable[1]):
2146
# Could happen when only the revision changed for a directory
2149
yield (file_id, (path_in_source, path_in_target), changed_content,
2150
versioned, parent, name, kind, executable)
2153
"""Return the number of entries in the inventory."""
2154
return len(self.id_to_entry)
2156
def _make_delta(self, old):
2157
"""Make an inventory delta from two inventories."""
2158
if type(old) != CHKInventory:
2159
return CommonInventory._make_delta(self, old)
2161
for key, old_value, self_value in \
2162
self.id_to_entry.iter_changes(old.id_to_entry):
2164
if old_value is not None:
2165
old_path = old.id2path(file_id)
2168
if self_value is not None:
2169
entry = self._bytes_to_entry(self_value)
2170
self._fileid_to_entry_cache[file_id] = entry
2171
new_path = self.id2path(file_id)
2175
delta.append((old_path, new_path, file_id, entry))
2178
def path2id(self, relpath):
2179
"""See CommonInventory.path2id()."""
2180
# TODO: perhaps support negative hits?
2181
result = self._path_to_fileid_cache.get(relpath, None)
2182
if result is not None:
2184
if isinstance(relpath, basestring):
2185
names = osutils.splitpath(relpath)
2188
current_id = self.root_id
2189
if current_id is None:
2191
parent_id_index = self.parent_id_basename_to_file_id
2193
for basename in names:
2194
if cur_path is None:
2197
cur_path = cur_path + '/' + basename
2198
basename_utf8 = basename.encode('utf8')
2199
file_id = self._path_to_fileid_cache.get(cur_path, 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,
2211
self._path_to_fileid_cache[cur_path] = file_id
2212
current_id = file_id
2216
"""Serialise the inventory to lines."""
2217
lines = ["chkinventory:\n"]
2218
if self._search_key_name != 'plain':
2219
# custom ordering grouping things that don't change together
2220
lines.append('search_key_name: %s\n' % (self._search_key_name,))
2221
lines.append("root_id: %s\n" % self.root_id)
2222
lines.append('parent_id_basename_to_file_id: %s\n' %
2223
(self.parent_id_basename_to_file_id.key()[0],))
2224
lines.append("revision_id: %s\n" % self.revision_id)
2225
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2227
lines.append("revision_id: %s\n" % self.revision_id)
2228
lines.append("root_id: %s\n" % self.root_id)
2229
if self.parent_id_basename_to_file_id is not None:
2230
lines.append('parent_id_basename_to_file_id: %s\n' %
2231
(self.parent_id_basename_to_file_id.key()[0],))
2232
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
2237
"""Get the root entry."""
2238
return self[self.root_id]
2241
class CHKInventoryDirectory(InventoryDirectory):
2242
"""A directory in an inventory."""
2244
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
2245
'text_id', 'parent_id', '_children', 'executable',
2246
'revision', 'symlink_target', 'reference_revision',
2249
def __init__(self, file_id, name, parent_id, chk_inventory):
2250
# Don't call InventoryDirectory.__init__ - it isn't right for this
2252
InventoryEntry.__init__(self, file_id, name, parent_id)
2253
self._children = None
2254
self.kind = 'directory'
2255
self._chk_inventory = chk_inventory
2259
"""Access the list of children of this directory.
2261
With a parent_id_basename_to_file_id index, loads all the children,
2262
without loads the entire index. Without is bad. A more sophisticated
2263
proxy object might be nice, to allow partial loading of children as
2264
well when specific names are accessed. (So path traversal can be
2265
written in the obvious way but not examine siblings.).
2267
if self._children is not None:
2268
return self._children
2269
# No longer supported
2270
if self._chk_inventory.parent_id_basename_to_file_id is None:
2271
raise AssertionError("Inventories without"
2272
" parent_id_basename_to_file_id are no longer supported")
2274
# XXX: Todo - use proxy objects for the children rather than loading
2275
# all when the attribute is referenced.
2276
parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
2278
for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2279
key_filter=[StaticTuple(self.file_id,)]):
2280
child_keys.add(StaticTuple(file_id,))
2282
for file_id_key in child_keys:
2283
entry = self._chk_inventory._fileid_to_entry_cache.get(
2284
file_id_key[0], None)
2285
if entry is not None:
2286
result[entry.name] = entry
2287
cached.add(file_id_key)
2288
child_keys.difference_update(cached)
2289
# populate; todo: do by name
2290
id_to_entry = self._chk_inventory.id_to_entry
2291
for file_id_key, bytes in id_to_entry.iteritems(child_keys):
2292
entry = self._chk_inventory._bytes_to_entry(bytes)
2293
result[entry.name] = entry
2294
self._chk_inventory._fileid_to_entry_cache[file_id_key[0]] = entry
2295
self._children = result
2528
2298
entry_factory = {
2529
2299
'directory': InventoryDirectory,