1089
1089
See the inventory developers documentation for the theory behind
1090
1090
inventory deltas.
1092
If delta application fails the inventory is left in an indeterminate
1093
state and must not be used.
1092
1095
:param delta: A list of changes to apply. After all the changes are
1093
1096
applied the final inventory must be internally consistent, but it
1094
1097
is ok to supply changes which, if only half-applied would have an
1138
1143
# modified children remaining by the time we examine it.
1139
1144
for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
1140
1145
if op is not None), reverse=True):
1141
if file_id not in self:
1144
1146
# Preserve unaltered children of file_id for later reinsertion.
1145
1147
file_id_children = getattr(self[file_id], 'children', {})
1146
1148
if len(file_id_children):
1147
1149
children[file_id] = file_id_children
1150
if self.id2path(file_id) != old_path:
1151
raise errors.InconsistentDelta(old_path, file_id,
1152
"Entry was at wrong other path %r." % self.id2path(file_id))
1148
1153
# Remove file_id and the unaltered children. If file_id is not
1149
1154
# being deleted it will be reinserted back later.
1150
1155
self.remove_recursive_id(file_id)
1153
1158
# longest, ensuring that items which were modified and whose parents in
1154
1159
# the resulting inventory were also modified, are inserted after their
1156
for new_path, new_entry in sorted((np, e) for op, np, f, e in
1161
for new_path, f, new_entry in sorted((np, f, e) for op, np, f, e in
1157
1162
delta if np is not None):
1158
1163
if new_entry.kind == 'directory':
1159
1164
# Pop the child which to allow detection of children whose
1166
1171
new_entry = replacement
1168
1173
self.add(new_entry)
1174
except errors.DuplicateFileId:
1175
raise errors.InconsistentDelta(new_path, new_entry.file_id,
1176
"New id is already present in target.")
1169
1177
except AttributeError:
1170
1178
raise errors.InconsistentDelta(new_path, new_entry.file_id,
1171
1179
"Parent is not a directory.")
1180
if self.id2path(new_entry.file_id) != new_path:
1181
raise errors.InconsistentDelta(new_path, new_entry.file_id,
1182
"New path is not consistent with parent path.")
1172
1183
if len(children):
1173
1184
# Get the parent id that was deleted
1174
1185
parent_id, children = children.popitem()
1254
1265
To add a file to a branch ready to be committed, use Branch.add,
1255
1266
which calls this.
1257
Returns the new entry object.
1259
1270
if entry.file_id in self._byid:
1260
1271
raise errors.DuplicateFileId(entry.file_id,
1261
1272
self._byid[entry.file_id])
1263
1273
if entry.parent_id is None:
1264
1274
self.root = entry
1604
1616
search_key_func=search_key_func)
1605
1617
result.id_to_entry._ensure_root()
1606
1618
result.id_to_entry._root_node.set_maximum_size(maximum_size)
1607
parent_id_basename_delta = []
1619
# Change to apply to the parent_id_basename delta. The dict maps
1620
# (parent_id, basename) -> (old_key, new_value). We use a dict because
1621
# when a path has its id replaced (e.g. the root is changed, or someone
1622
# does bzr mv a b, bzr mv c a, we should output a single change to this
1623
# map rather than two.
1624
parent_id_basename_delta = {}
1608
1625
if self.parent_id_basename_to_file_id is not None:
1609
1626
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1610
1627
self.parent_id_basename_to_file_id._store,
1630
1647
inventory_delta = _check_delta_unique_new_paths(inventory_delta)
1631
1648
# Check for entries that don't match the fileid
1632
1649
inventory_delta = _check_delta_ids_match_entry(inventory_delta)
1633
# All changed entries need to have their parents be directories.
1650
# Check for nonsense fileids
1651
inventory_delta = _check_delta_ids_are_valid(inventory_delta)
1652
# Check for new_path <-> entry consistency
1653
inventory_delta = _check_delta_new_path_entry_both_or_None(
1655
# All changed entries need to have their parents be directories and be
1656
# at the right path. This set contains (path, id) tuples.
1634
1657
parents = set()
1658
# When we delete an item, all the children of it must be either deleted
1659
# or altered in their own right. As we batch process the change via
1660
# CHKMap.apply_delta, we build a set of things to use to validate the
1635
1664
for old_path, new_path, file_id, entry in inventory_delta:
1636
1665
# file id changes
1637
1666
if new_path == '':
1646
1675
del result._path_to_fileid_cache[old_path]
1647
1676
except KeyError:
1678
deletes.add(file_id)
1650
1680
new_key = (file_id,)
1651
1681
new_value = result._entry_to_bytes(entry)
1652
1682
# Update caches. It's worth doing this whether
1653
1683
# we're propagating the old caches or not.
1654
1684
result._path_to_fileid_cache[new_path] = file_id
1655
parents.add(entry.parent_id)
1685
parents.add((split(new_path)[0], entry.parent_id))
1656
1686
if old_path is None:
1659
1689
old_key = (file_id,)
1690
if self.id2path(file_id) != old_path:
1691
raise errors.InconsistentDelta(old_path, file_id,
1692
"Entry was at wrong other path %r." %
1693
self.id2path(file_id))
1694
altered.add(file_id)
1660
1695
id_to_entry_delta.append((old_key, new_key, new_value))
1661
1696
if result.parent_id_basename_to_file_id is not None:
1662
1697
# parent_id, basename changes
1672
1707
new_key = self._parent_id_basename_key(entry)
1673
1708
new_value = file_id
1709
# If the two keys are the same, the value will be unchanged
1710
# as its always the file id for this entry.
1674
1711
if old_key != new_key:
1675
# If the two keys are the same, the value will be unchanged
1676
# as its always the file id.
1677
parent_id_basename_delta.append((old_key, new_key, new_value))
1712
# Transform a change into explicit delete/add preserving
1713
# a possible match on the key from a different file id.
1714
if old_key is not None:
1715
parent_id_basename_delta.setdefault(
1716
old_key, [None, None])[0] = old_key
1717
if new_key is not None:
1718
parent_id_basename_delta.setdefault(
1719
new_key, [None, None])[1] = new_value
1720
# validate that deletes are complete.
1721
for file_id in deletes:
1722
entry = self[file_id]
1723
if entry.kind != 'directory':
1725
# This loop could potentially be better by using the id_basename
1726
# map to just get the child file ids.
1727
for child in entry.children.values():
1728
if child.file_id not in altered:
1729
raise errors.InconsistentDelta(self.id2path(child.file_id),
1730
child.file_id, "Child not deleted or reparented when "
1678
1732
result.id_to_entry.apply_delta(id_to_entry_delta)
1679
1733
if parent_id_basename_delta:
1680
result.parent_id_basename_to_file_id.apply_delta(parent_id_basename_delta)
1681
parents.discard(None)
1682
for parent in parents:
1734
# Transform the parent_id_basename delta data into a linear delta
1735
# with only one record for a given key. Optimally this would allow
1736
# re-keying, but its simpler to just output that as a delete+add
1737
# to spend less time calculating the delta.
1739
for key, (old_key, value) in parent_id_basename_delta.iteritems():
1740
if value is not None:
1741
delta_list.append((old_key, key, value))
1743
delta_list.append((old_key, None, None))
1744
result.parent_id_basename_to_file_id.apply_delta(delta_list)
1745
parents.discard(('', None))
1746
for parent_path, parent in parents:
1684
1748
if result[parent].kind != 'directory':
1685
1749
raise errors.InconsistentDelta(result.id2path(parent), parent,
1687
1751
except errors.NoSuchId:
1688
1752
raise errors.InconsistentDelta("<unknown>", parent,
1689
1753
"Parent is not present in resulting inventory.")
1754
if result.path2id(parent_path) != parent:
1755
raise errors.InconsistentDelta(parent_path, parent,
1756
"Parent has wrong path %r." % result.path2id(parent_path))
1952
2019
def path2id(self, name):
1953
2020
"""See CommonInventory.path2id()."""
2021
# TODO: perhaps support negative hits?
1954
2022
result = self._path_to_fileid_cache.get(name, None)
1956
result = CommonInventory.path2id(self, name)
1957
self._path_to_fileid_cache[name] = result
2023
if result is not None:
2025
if isinstance(name, basestring):
2026
names = osutils.splitpath(name)
2029
current_id = self.root_id
2030
if current_id is None:
2032
parent_id_index = self.parent_id_basename_to_file_id
2033
for basename in names:
2034
# TODO: Cache each path we figure out in this function.
2035
basename_utf8 = basename.encode('utf8')
2036
key_filter = [(current_id, basename_utf8)]
2038
for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
2039
key_filter=key_filter):
2040
if parent_id != current_id or name_utf8 != basename_utf8:
2041
raise errors.BzrError("corrupt inventory lookup! "
2042
"%r %r %r %r" % (parent_id, current_id, name_utf8,
2046
current_id = file_id
2047
self._path_to_fileid_cache[name] = current_id
1960
2050
def to_lines(self):
1961
2051
"""Serialise the inventory to lines."""
2237
def _check_delta_ids_are_valid(delta):
2238
"""Decorate a delta and check that the ids in it are valid.
2240
:return: A generator over delta.
2245
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2246
"entry with file_id None %r" % entry)
2247
if type(item[2]) != str:
2248
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2249
"entry with non bytes file_id %r" % entry)
2147
2253
def _check_delta_ids_match_entry(delta):
2148
2254
"""Decorate a delta and check that the ids in it match the entry.file_id.
2156
2262
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2157
2263
"mismatched id with %r" % entry)
2267
def _check_delta_new_path_entry_both_or_None(delta):
2268
"""Decorate a delta and check that the new_path and entry are paired.
2270
:return: A generator over delta.
2275
if new_path is None and entry is not None:
2276
raise errors.InconsistentDelta(item[0], item[1],
2277
"Entry with no new_path")
2278
if new_path is not None and entry is None:
2279
raise errors.InconsistentDelta(new_path, item[1],
2280
"new_path with no entry")