273
272
:param rev_id: Revision id from which this InventoryEntry was loaded.
274
273
Not necessarily the last-changed revision for this file.
275
274
:param inv: Inventory from which the entry was loaded.
275
:param tree: RevisionTree for this entry.
277
277
if self.parent_id is not None:
278
278
if not inv.has_id(self.parent_id):
279
279
raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
280
280
% (self.parent_id, rev_id))
281
checker._add_entry_to_text_key_references(inv, self)
282
self._check(checker, rev_id)
281
self._check(checker, rev_id, tree)
284
def _check(self, checker, rev_id):
283
def _check(self, checker, rev_id, tree):
285
284
"""Check this inventory entry for kind specific errors."""
286
checker._report_items.append(
287
'unknown entry kind %r in revision {%s}' % (self.kind, rev_id))
285
raise BzrCheckError('unknown entry kind %r in revision {%s}' %
290
289
"""Clone this inventory entry."""
432
431
'text_id', 'parent_id', 'children', 'executable',
433
432
'revision', 'symlink_target', 'reference_revision']
435
def _check(self, checker, rev_id):
434
def _check(self, checker, rev_id, tree):
436
435
"""See InventoryEntry._check"""
437
if (self.text_sha1 is not None or self.text_size is not None or
438
self.text_id is not None):
439
checker._report_items.append('directory {%s} has text in revision {%s}'
436
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
437
raise BzrCheckError('directory {%s} has text in revision {%s}'
440
438
% (self.file_id, rev_id))
441
# In non rich root repositories we do not expect a file graph for the
443
if self.name == '' and not checker.rich_roots:
445
# Directories are stored as an empty file, but the file should exist
446
# to provide a per-fileid log. The hash of every directory content is
447
# "da..." below (the sha1sum of '').
448
checker.add_pending_item(rev_id,
449
('texts', self.file_id, self.revision), 'text',
450
'da39a3ee5e6b4b0d3255bfef95601890afd80709')
453
441
other = InventoryDirectory(self.file_id, self.name, self.parent_id)
486
474
'text_id', 'parent_id', 'children', 'executable',
487
475
'revision', 'symlink_target', 'reference_revision']
489
def _check(self, checker, tree_revision_id):
477
def _check(self, checker, tree_revision_id, tree):
490
478
"""See InventoryEntry._check"""
491
# TODO: check size too.
492
checker.add_pending_item(tree_revision_id,
493
('texts', self.file_id, self.revision), 'text',
495
if self.text_size is None:
496
checker._report_items.append(
497
'fileid {%s} in {%s} has None for text_size' % (self.file_id,
479
key = (self.file_id, self.revision)
480
if key in checker.checked_texts:
481
prev_sha = checker.checked_texts[key]
482
if prev_sha != self.text_sha1:
484
'mismatched sha1 on {%s} in {%s} (%s != %s) %r' %
485
(self.file_id, tree_revision_id, prev_sha, self.text_sha1,
488
checker.repeated_text_cnt += 1
491
checker.checked_text_cnt += 1
492
# We can't check the length, because Weave doesn't store that
493
# information, and the whole point of looking at the weave's
494
# sha1sum is that we don't have to extract the text.
495
if (self.text_sha1 != tree._repository.texts.get_sha1s([key])[key]):
496
raise BzrCheckError('text {%s} version {%s} wrong sha1' % key)
497
checker.checked_texts[key] = self.text_sha1
501
500
other = InventoryFile(self.file_id, self.name, self.parent_id)
599
598
'text_id', 'parent_id', 'children', 'executable',
600
599
'revision', 'symlink_target', 'reference_revision']
602
def _check(self, checker, tree_revision_id):
601
def _check(self, checker, rev_id, tree):
603
602
"""See InventoryEntry._check"""
604
603
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
605
checker._report_items.append(
606
'symlink {%s} has text in revision {%s}'
607
% (self.file_id, tree_revision_id))
604
raise BzrCheckError('symlink {%s} has text in revision {%s}'
605
% (self.file_id, rev_id))
608
606
if self.symlink_target is None:
609
checker._report_items.append(
610
'symlink {%s} has no target in revision {%s}'
611
% (self.file_id, tree_revision_id))
612
# Symlinks are stored as ''
613
checker.add_pending_item(tree_revision_id,
614
('texts', self.file_id, self.revision), 'text',
615
'da39a3ee5e6b4b0d3255bfef95601890afd80709')
607
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
608
% (self.file_id, rev_id))
618
611
other = InventoryLink(self.file_id, self.name, self.parent_id)
721
714
class CommonInventory(object):
722
"""Basic inventory logic, defined in terms of primitives like has_id.
724
An inventory is the metadata about the contents of a tree.
726
This is broadly a map from file_id to entries such as directories, files,
727
symlinks and tree references. Each entry maintains its own metadata like
728
SHA1 and length for files, or children for a directory.
730
Entries can be looked up either by path or by file_id.
732
InventoryEntry objects must not be modified after they are
733
inserted, other than through the Inventory API.
715
"""Basic inventory logic, defined in terms of primitives like has_id."""
736
717
def __contains__(self, file_id):
737
718
"""True if this entry contains a file with given id.
959
935
descend(self.root, u'')
962
def path2id(self, relpath):
938
def path2id(self, name):
963
939
"""Walk down through directories to return entry of last component.
965
:param relpath: may be either a list of path components, or a single
966
string, in which case it is automatically split.
941
names may be either a list of path components, or a single
942
string, in which case it is automatically split.
968
944
This returns the entry of the last component in the path,
969
945
which may be either a file or a directory.
971
947
Returns None IFF the path is not found.
973
if isinstance(relpath, basestring):
974
names = osutils.splitpath(relpath)
949
if isinstance(name, basestring):
950
name = osutils.splitpath(name)
952
# mutter("lookup path %r" % name)
979
955
parent = self.root
1045
1021
class Inventory(CommonInventory):
1046
"""Mutable dict based in-memory inventory.
1048
We never store the full path to a file, because renaming a directory
1049
implicitly moves all of its contents. This class internally maintains a
1022
"""Inventory of versioned files in a tree.
1024
This describes which file_id is present at each point in the tree,
1025
and possibly the SHA-1 or other information about the file.
1026
Entries can be looked up either by path or by file_id.
1028
The inventory represents a typical unix file tree, with
1029
directories containing files and subdirectories. We never store
1030
the full path to a file, because renaming a directory implicitly
1031
moves all of its contents. This class internally maintains a
1050
1032
lookup tree that allows the children under a directory to be
1051
1033
returned quickly.
1035
InventoryEntry objects must not be modified after they are
1036
inserted, other than through the Inventory API.
1053
1038
>>> inv = Inventory()
1054
1039
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
1055
1040
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1056
1041
>>> inv['123-123'].name
1059
Id's may be looked up from paths:
1044
May be treated as an iterator or set to look up file ids:
1061
>>> inv.path2id('hello.c')
1046
>>> bool(inv.path2id('hello.c'))
1063
1048
>>> '123-123' in inv
1066
There are iterators over the contents:
1051
May also look up by name:
1068
>>> [entry[0] for entry in inv.iter_entries()]
1053
>>> [x[0] for x in inv.iter_entries()]
1069
1054
['', u'hello.c']
1055
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
1056
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
1057
Traceback (most recent call last):
1058
BzrError: parent_id {TREE_ROOT} not in inventory
1059
>>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
1060
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None, revision=None)
1072
1062
def __init__(self, root_id=ROOT_ID, revision_id=None):
1073
1063
"""Create or read an inventory.
1155
1141
# modified children remaining by the time we examine it.
1156
1142
for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
1157
1143
if op is not None), reverse=True):
1144
if file_id not in self:
1158
1147
# Preserve unaltered children of file_id for later reinsertion.
1159
1148
file_id_children = getattr(self[file_id], 'children', {})
1160
1149
if len(file_id_children):
1161
1150
children[file_id] = file_id_children
1162
if self.id2path(file_id) != old_path:
1163
raise errors.InconsistentDelta(old_path, file_id,
1164
"Entry was at wrong other path %r." % self.id2path(file_id))
1165
1151
# Remove file_id and the unaltered children. If file_id is not
1166
1152
# being deleted it will be reinserted back later.
1167
1153
self.remove_recursive_id(file_id)
1181
1167
replacement.revision = new_entry.revision
1182
1168
replacement.children = children.pop(replacement.file_id, {})
1183
1169
new_entry = replacement
1186
except errors.DuplicateFileId:
1187
raise errors.InconsistentDelta(new_path, new_entry.file_id,
1188
"New id is already present in target.")
1189
except AttributeError:
1190
raise errors.InconsistentDelta(new_path, new_entry.file_id,
1191
"Parent is not a directory.")
1192
if self.id2path(new_entry.file_id) != new_path:
1193
raise errors.InconsistentDelta(new_path, new_entry.file_id,
1194
"New path is not consistent with parent path.")
1195
1171
if len(children):
1196
1172
# Get the parent id that was deleted
1197
1173
parent_id, children = children.popitem()
1198
1174
raise errors.InconsistentDelta("<deleted>", parent_id,
1199
1175
"The file id was deleted but its children were not deleted.")
1201
def create_by_apply_delta(self, inventory_delta, new_revision_id,
1202
propagate_caches=False):
1203
"""See CHKInventory.create_by_apply_delta()"""
1204
new_inv = self.copy()
1205
new_inv.apply_delta(inventory_delta)
1206
new_inv.revision_id = new_revision_id
1209
1177
def _set_root(self, ie):
1211
1179
self._byid = {self.root.file_id: self.root}
1285
1253
To add a file to a branch ready to be committed, use Branch.add,
1286
1254
which calls this.
1256
Returns the new entry object.
1290
1258
if entry.file_id in self._byid:
1291
1259
raise errors.DuplicateFileId(entry.file_id,
1292
1260
self._byid[entry.file_id])
1293
1262
if entry.parent_id is None:
1294
1263
self.root = entry
1297
1266
parent = self._byid[entry.parent_id]
1298
1267
except KeyError:
1299
raise errors.InconsistentDelta("<unknown>", entry.parent_id,
1300
"Parent not in inventory.")
1268
raise BzrError("parent_id {%s} not in inventory" %
1301
1271
if entry.name in parent.children:
1302
raise errors.InconsistentDelta(
1303
self.id2path(parent.children[entry.name].file_id),
1305
"Path already versioned")
1272
raise BzrError("%s is already versioned" %
1273
osutils.pathjoin(self.id2path(parent.file_id),
1274
entry.name).encode('utf-8'))
1306
1275
parent.children[entry.name] = entry
1307
1276
return self._add_child(entry)
1556
1527
raise ValueError("unknown kind %r" % entry.kind)
1558
def _expand_fileids_to_parents_and_children(self, file_ids):
1559
"""Give a more wholistic view starting with the given file_ids.
1561
For any file_id which maps to a directory, we will include all children
1562
of that directory. We will also include all directories which are
1563
parents of the given file_ids, but we will not include their children.
1570
fringle # fringle-id
1574
if given [foo-id] we will include
1575
TREE_ROOT as interesting parents
1577
foo-id, baz-id, frob-id, fringle-id
1581
# TODO: Pre-pass over the list of fileids to see if anything is already
1582
# deserialized in self._fileid_to_entry_cache
1584
directories_to_expand = set()
1585
children_of_parent_id = {}
1586
# It is okay if some of the fileids are missing
1587
for entry in self._getitems(file_ids):
1588
if entry.kind == 'directory':
1589
directories_to_expand.add(entry.file_id)
1590
interesting.add(entry.parent_id)
1591
children_of_parent_id.setdefault(entry.parent_id, []
1592
).append(entry.file_id)
1594
# Now, interesting has all of the direct parents, but not the
1595
# parents of those parents. It also may have some duplicates with
1597
remaining_parents = interesting.difference(file_ids)
1598
# When we hit the TREE_ROOT, we'll get an interesting parent of None,
1599
# but we don't actually want to recurse into that
1600
interesting.add(None) # this will auto-filter it in the loop
1601
remaining_parents.discard(None)
1602
while remaining_parents:
1603
next_parents = set()
1604
for entry in self._getitems(remaining_parents):
1605
next_parents.add(entry.parent_id)
1606
children_of_parent_id.setdefault(entry.parent_id, []
1607
).append(entry.file_id)
1608
# Remove any search tips we've already processed
1609
remaining_parents = next_parents.difference(interesting)
1610
interesting.update(remaining_parents)
1611
# We should probably also .difference(directories_to_expand)
1612
interesting.update(file_ids)
1613
interesting.discard(None)
1614
while directories_to_expand:
1615
# Expand directories by looking in the
1616
# parent_id_basename_to_file_id map
1617
keys = [StaticTuple(f,).intern() for f in directories_to_expand]
1618
directories_to_expand = set()
1619
items = self.parent_id_basename_to_file_id.iteritems(keys)
1620
next_file_ids = set([item[1] for item in items])
1621
next_file_ids = next_file_ids.difference(interesting)
1622
interesting.update(next_file_ids)
1623
for entry in self._getitems(next_file_ids):
1624
if entry.kind == 'directory':
1625
directories_to_expand.add(entry.file_id)
1626
children_of_parent_id.setdefault(entry.parent_id, []
1627
).append(entry.file_id)
1628
return interesting, children_of_parent_id
1630
def filter(self, specific_fileids):
1631
"""Get an inventory view filtered against a set of file-ids.
1633
Children of directories and parents are included.
1635
The result may or may not reference the underlying inventory
1636
so it should be treated as immutable.
1639
parent_to_children) = self._expand_fileids_to_parents_and_children(
1641
# There is some overlap here, but we assume that all interesting items
1642
# are in the _fileid_to_entry_cache because we had to read them to
1643
# determine if they were a dir we wanted to recurse, or just a file
1644
# This should give us all the entries we'll want to add, so start
1646
other = Inventory(self.root_id)
1647
other.root.revision = self.root.revision
1648
other.revision_id = self.revision_id
1649
if not interesting or not parent_to_children:
1650
# empty filter, or filtering entrys that don't exist
1651
# (if even 1 existed, then we would have populated
1652
# parent_to_children with at least the tree root.)
1654
cache = self._fileid_to_entry_cache
1655
remaining_children = collections.deque(parent_to_children[self.root_id])
1656
while remaining_children:
1657
file_id = remaining_children.popleft()
1659
if ie.kind == 'directory':
1660
ie = ie.copy() # We create a copy to depopulate the .children attribute
1661
# TODO: depending on the uses of 'other' we should probably alwyas
1662
# '.copy()' to prevent someone from mutating other and
1663
# invaliding our internal cache
1665
if file_id in parent_to_children:
1666
remaining_children.extend(parent_to_children[file_id])
1670
1530
def _bytes_to_utf8name_key(bytes):
1671
1531
"""Get the file_id, revision_id key out of bytes."""
1745
1603
search_key_func=search_key_func)
1746
1604
result.id_to_entry._ensure_root()
1747
1605
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 = {}
1606
parent_id_basename_delta = []
1754
1607
if self.parent_id_basename_to_file_id is not None:
1755
1608
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1756
1609
self.parent_id_basename_to_file_id._store,
1766
1619
result.parent_id_basename_to_file_id = None
1767
1620
result.root_id = self.root_id
1768
1621
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
1622
for old_path, new_path, file_id, entry in inventory_delta:
1794
1623
# file id changes
1795
1624
if new_path == '':
1804
1633
del result._path_to_fileid_cache[old_path]
1805
1634
except KeyError:
1807
deletes.add(file_id)
1809
new_key = StaticTuple(file_id,)
1637
new_key = (file_id,)
1810
1638
new_value = result._entry_to_bytes(entry)
1811
1639
# Update caches. It's worth doing this whether
1812
1640
# we're propagating the old caches or not.
1813
1641
result._path_to_fileid_cache[new_path] = file_id
1814
parents.add((split(new_path)[0], entry.parent_id))
1815
1642
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))
1645
old_key = (file_id,)
1646
id_to_entry_delta.append((old_key, new_key, new_value))
1825
1647
if result.parent_id_basename_to_file_id is not None:
1826
1648
# parent_id, basename changes
1827
1649
if old_path is None:
1836
1658
new_key = self._parent_id_basename_key(entry)
1837
1659
new_value = file_id
1838
# If the two keys are the same, the value will be unchanged
1839
# as its always the file id for this entry.
1840
1660
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 "
1661
# If the two keys are the same, the value will be unchanged
1662
# as its always the file id.
1663
parent_id_basename_delta.append((old_key, new_key, new_value))
1861
1664
result.id_to_entry.apply_delta(id_to_entry_delta)
1862
1665
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))
1666
result.parent_id_basename_to_file_id.apply_delta(parent_id_basename_delta)
1914
1695
raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1915
1696
% (key, bytes))
1916
1697
info[key] = value
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,))
1698
revision_id = info['revision_id']
1699
root_id = info['root_id']
1700
search_key_name = info.get('search_key_name', 'plain')
1701
parent_id_basename_to_file_id = info.get(
1702
'parent_id_basename_to_file_id', None)
1925
1703
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
1705
result = CHKInventory(search_key_name)
1931
1706
result.revision_id = revision_id
1934
1709
result._search_key_name)
1935
1710
if parent_id_basename_to_file_id is not None:
1936
1711
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1937
chk_store, StaticTuple(parent_id_basename_to_file_id,),
1712
chk_store, (parent_id_basename_to_file_id,),
1938
1713
search_key_func=search_key_func)
1940
1715
result.parent_id_basename_to_file_id = None
1942
result.id_to_entry = chk_map.CHKMap(chk_store,
1943
StaticTuple(id_to_entry,),
1717
result.id_to_entry = chk_map.CHKMap(chk_store, (id_to_entry,),
1944
1718
search_key_func=search_key_func)
1945
1719
if (result.revision_id,) != expected_revision_id:
1946
1720
raise ValueError("Mismatched revision id and expected: %r, %r" %
2011
1784
return self._bytes_to_entry(
2012
self.id_to_entry.iteritems([StaticTuple(file_id,)]).next()[1])
1785
self.id_to_entry.iteritems([(file_id,)]).next()[1])
2013
1786
except StopIteration:
2014
1787
# really we're passing an inventory, not a tree...
2015
1788
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
1790
def has_id(self, file_id):
2039
1791
# Perhaps have an explicit 'contains' method on CHKMap ?
2040
1792
if self._fileid_to_entry_cache.get(file_id, None) is not None:
2043
self.id_to_entry.iteritems([StaticTuple(file_id,)]))) == 1
1794
return len(list(self.id_to_entry.iteritems([(file_id,)]))) == 1
2045
1796
def is_root(self, file_id):
2046
1797
return file_id == self.root_id
2175
1926
delta.append((old_path, new_path, file_id, entry))
2178
def path2id(self, relpath):
1929
def path2id(self, name):
2179
1930
"""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
1931
result = self._path_to_fileid_cache.get(name, None)
1933
result = CommonInventory.path2id(self, name)
1934
self._path_to_fileid_cache[name] = result
2215
1937
def to_lines(self):
2216
1938
"""Serialise the inventory to lines."""
2220
1942
lines.append('search_key_name: %s\n' % (self._search_key_name,))
2221
1943
lines.append("root_id: %s\n" % self.root_id)
2222
1944
lines.append('parent_id_basename_to_file_id: %s\n' %
2223
(self.parent_id_basename_to_file_id.key()[0],))
1945
self.parent_id_basename_to_file_id.key())
2224
1946
lines.append("revision_id: %s\n" % self.revision_id)
2225
lines.append("id_to_entry: %s\n" % (self.id_to_entry.key()[0],))
1947
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
2227
1949
lines.append("revision_id: %s\n" % self.revision_id)
2228
1950
lines.append("root_id: %s\n" % self.root_id)
2229
1951
if self.parent_id_basename_to_file_id is not None:
2230
1952
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],))
1953
self.parent_id_basename_to_file_id.key())
1954
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
2350
2072
_NAME_RE = re.compile(r'^[^/\\]+$')
2352
2074
return bool(_NAME_RE.match(name))
2355
def _check_delta_unique_ids(delta):
2356
"""Decorate a delta and check that the file ids in it are unique.
2358
:return: A generator over delta.
2362
length = len(ids) + 1
2364
if len(ids) != length:
2365
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2370
def _check_delta_unique_new_paths(delta):
2371
"""Decorate a delta and check that the new paths in it are unique.
2373
:return: A generator over delta.
2377
length = len(paths) + 1
2379
if path is not None:
2381
if len(paths) != length:
2382
raise errors.InconsistentDelta(path, item[2], "repeated path")
2386
def _check_delta_unique_old_paths(delta):
2387
"""Decorate a delta and check that the old paths in it are unique.
2389
:return: A generator over delta.
2393
length = len(paths) + 1
2395
if path is not None:
2397
if len(paths) != length:
2398
raise errors.InconsistentDelta(path, item[2], "repeated path")
2402
def _check_delta_ids_are_valid(delta):
2403
"""Decorate a delta and check that the ids in it are valid.
2405
:return: A generator over delta.
2410
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2411
"entry with file_id None %r" % entry)
2412
if type(item[2]) != str:
2413
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2414
"entry with non bytes file_id %r" % entry)
2418
def _check_delta_ids_match_entry(delta):
2419
"""Decorate a delta and check that the ids in it match the entry.file_id.
2421
:return: A generator over delta.
2425
if entry is not None:
2426
if entry.file_id != item[2]:
2427
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2428
"mismatched id with %r" % entry)
2432
def _check_delta_new_path_entry_both_or_None(delta):
2433
"""Decorate a delta and check that the new_path and entry are paired.
2435
:return: A generator over delta.
2440
if new_path is None and entry is not None:
2441
raise errors.InconsistentDelta(item[0], item[1],
2442
"Entry with no new_path")
2443
if new_path is not None and entry is None:
2444
raise errors.InconsistentDelta(new_path, item[1],
2445
"new_path with no entry")