681
815
compatible = False
682
816
return compatible
685
class TreeReference(InventoryEntry):
687
kind = 'tree-reference'
689
def __init__(self, file_id, name, parent_id, revision=None,
690
reference_revision=None):
691
InventoryEntry.__init__(self, file_id, name, parent_id)
692
self.revision = revision
693
self.reference_revision = reference_revision
818
def _snapshot_text(self, file_parents, work_tree, commit_builder):
819
"""See InventoryEntry._snapshot_text."""
820
commit_builder.modified_link(
821
self.file_id, file_parents, self.symlink_target)
824
class Inventory(object):
825
"""Inventory of versioned files in a tree.
827
This describes which file_id is present at each point in the tree,
828
and possibly the SHA-1 or other information about the file.
829
Entries can be looked up either by path or by file_id.
831
The inventory represents a typical unix file tree, with
832
directories containing files and subdirectories. We never store
833
the full path to a file, because renaming a directory implicitly
834
moves all of its contents. This class internally maintains a
835
lookup tree that allows the children under a directory to be
838
InventoryEntry objects must not be modified after they are
839
inserted, other than through the Inventory API.
841
>>> inv = Inventory()
842
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
843
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
844
>>> inv['123-123'].name
847
May be treated as an iterator or set to look up file ids:
849
>>> bool(inv.path2id('hello.c'))
854
May also look up by name:
856
>>> [x[0] for x in inv.iter_entries()]
858
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
859
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
860
Traceback (most recent call last):
861
BzrError: parent_id {TREE_ROOT} not in inventory
862
>>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
863
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
865
def __init__(self, root_id=ROOT_ID, revision_id=None):
866
"""Create or read an inventory.
868
If a working directory is specified, the inventory is read
869
from there. If the file is specified, read from that. If not,
870
the inventory is created empty.
872
The inventory is created with a default root directory, with
875
if root_id is not None:
876
self._set_root(InventoryDirectory(root_id, '', None))
880
self.revision_id = revision_id
882
def _set_root(self, ie):
884
self._byid = {self.root.file_id: self.root}
696
return TreeReference(self.file_id, self.name, self.parent_id,
697
self.revision, self.reference_revision)
699
def _read_tree_state(self, path, work_tree):
700
"""Populate fields in the inventory entry from the given tree.
702
self.reference_revision = work_tree.get_reference_revision(
705
def _forget_tree_state(self):
706
self.reference_revision = None
708
def _unchanged(self, previous_ie):
709
"""See InventoryEntry._unchanged."""
710
compatible = super(TreeReference, self)._unchanged(previous_ie)
711
if self.reference_revision != previous_ie.reference_revision:
716
class CommonInventory(object):
717
"""Basic inventory logic, defined in terms of primitives like has_id."""
719
def __contains__(self, file_id):
720
"""True if this entry contains a file with given id.
722
>>> inv = Inventory()
723
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
724
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
730
Note that this method along with __iter__ are not encouraged for use as
731
they are less clear than specific query methods - they may be rmeoved
734
return self.has_id(file_id)
736
def id2path(self, file_id):
737
"""Return as a string the path to file_id.
740
>>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
741
>>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
742
>>> print i.id2path('foo-id')
745
# get all names, skipping root
746
return '/'.join(reversed(
747
[parent.name for parent in
748
self._iter_file_id_parents(file_id)][:-1]))
887
# TODO: jam 20051218 Should copy also copy the revision_id?
888
entries = self.iter_entries()
889
other = Inventory(entries.next()[1].file_id)
890
# copy recursively so we know directories will be added before
891
# their children. There are more efficient ways than this...
892
for path, entry in entries():
893
other.add(entry.copy())
897
return iter(self._byid)
900
"""Returns number of entries."""
901
return len(self._byid)
750
903
def iter_entries(self, from_dir=None):
751
904
"""Return (path, entry) pairs, in order by name."""
927
1012
descend(child_ie, child_path)
928
1013
descend(self.root, u'')
931
def path2id(self, name):
932
"""Walk down through directories to return entry of last component.
934
names may be either a list of path components, or a single
935
string, in which case it is automatically split.
937
This returns the entry of the last component in the path,
938
which may be either a file or a directory.
940
Returns None IFF the path is not found.
942
if isinstance(name, basestring):
943
name = osutils.splitpath(name)
945
# mutter("lookup path %r" % name)
949
except errors.NoSuchId:
950
# root doesn't exist yet so nothing else can
956
children = getattr(parent, 'children', None)
965
return parent.file_id
967
def filter(self, specific_fileids):
968
"""Get an inventory view filtered against a set of file-ids.
970
Children of directories and parents are included.
972
The result may or may not reference the underlying inventory
973
so it should be treated as immutable.
975
interesting_parents = set()
976
for fileid in specific_fileids:
978
interesting_parents.update(self.get_idpath(fileid))
979
except errors.NoSuchId:
980
# This fileid is not in the inventory - that's ok
982
entries = self.iter_entries()
983
if self.root is None:
984
return Inventory(root_id=None)
985
other = Inventory(entries.next()[1].file_id)
986
other.root.revision = self.root.revision
987
other.revision_id = self.revision_id
988
directories_to_expand = set()
989
for path, entry in entries:
990
file_id = entry.file_id
991
if (file_id in specific_fileids
992
or entry.parent_id in directories_to_expand):
993
if entry.kind == 'directory':
994
directories_to_expand.add(file_id)
995
elif file_id not in interesting_parents:
997
other.add(entry.copy())
1000
def get_idpath(self, file_id):
1001
"""Return a list of file_ids for the path to an entry.
1003
The list contains one element for each directory followed by
1004
the id of the file itself. So the length of the returned list
1005
is equal to the depth of the file in the tree, counting the
1006
root directory as depth 1.
1009
for parent in self._iter_file_id_parents(file_id):
1010
p.insert(0, parent.file_id)
1014
class Inventory(CommonInventory):
1015
"""Inventory of versioned files in a tree.
1017
This describes which file_id is present at each point in the tree,
1018
and possibly the SHA-1 or other information about the file.
1019
Entries can be looked up either by path or by file_id.
1021
The inventory represents a typical unix file tree, with
1022
directories containing files and subdirectories. We never store
1023
the full path to a file, because renaming a directory implicitly
1024
moves all of its contents. This class internally maintains a
1025
lookup tree that allows the children under a directory to be
1028
InventoryEntry objects must not be modified after they are
1029
inserted, other than through the Inventory API.
1031
>>> inv = Inventory()
1032
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
1033
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1034
>>> inv['123-123'].name
1037
May be treated as an iterator or set to look up file ids:
1039
>>> bool(inv.path2id('hello.c'))
1041
>>> '123-123' in inv
1044
May also look up by name:
1046
>>> [x[0] for x in inv.iter_entries()]
1048
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
1049
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
1050
Traceback (most recent call last):
1051
BzrError: parent_id {TREE_ROOT} not in inventory
1052
>>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
1053
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None, revision=None)
1055
def __init__(self, root_id=ROOT_ID, revision_id=None):
1056
"""Create or read an inventory.
1058
If a working directory is specified, the inventory is read
1059
from there. If the file is specified, read from that. If not,
1060
the inventory is created empty.
1062
The inventory is created with a default root directory, with
1065
if root_id is not None:
1066
self._set_root(InventoryDirectory(root_id, u'', None))
1070
self.revision_id = revision_id
1073
# More than one page of ouput is not useful anymore to debug
1076
contents = repr(self._byid)
1077
if len(contents) > max_len:
1078
contents = contents[:(max_len-len(closing))] + closing
1079
return "<Inventory object at %x, contents=%r>" % (id(self), contents)
1081
def apply_delta(self, delta):
1082
"""Apply a delta to this inventory.
1084
:param delta: A list of changes to apply. After all the changes are
1085
applied the final inventory must be internally consistent, but it
1086
is ok to supply changes which, if only half-applied would have an
1087
invalid result - such as supplying two changes which rename two
1088
files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
1089
('B', 'A', 'B-id', b_entry)].
1091
Each change is a tuple, of the form (old_path, new_path, file_id,
1094
When new_path is None, the change indicates the removal of an entry
1095
from the inventory and new_entry will be ignored (using None is
1096
appropriate). If new_path is not None, then new_entry must be an
1097
InventoryEntry instance, which will be incorporated into the
1098
inventory (and replace any existing entry with the same file id).
1100
When old_path is None, the change indicates the addition of
1101
a new entry to the inventory.
1103
When neither new_path nor old_path are None, the change is a
1104
modification to an entry, such as a rename, reparent, kind change
1107
The children attribute of new_entry is ignored. This is because
1108
this method preserves children automatically across alterations to
1109
the parent of the children, and cases where the parent id of a
1110
child is changing require the child to be passed in as a separate
1111
change regardless. E.g. in the recursive deletion of a directory -
1112
the directory's children must be included in the delta, or the
1113
final inventory will be invalid.
1115
Note that a file_id must only appear once within a given delta.
1116
An AssertionError is raised otherwise.
1118
# Check that the delta is legal. It would be nice if this could be
1119
# done within the loops below but it's safer to validate the delta
1120
# before starting to mutate the inventory.
1121
unique_file_ids = set([f for _, _, f, _ in delta])
1122
if len(unique_file_ids) != len(delta):
1123
raise AssertionError("a file-id appears multiple times in %r"
1128
# Remove all affected items which were in the original inventory,
1129
# starting with the longest paths, thus ensuring parents are examined
1130
# after their children, which means that everything we examine has no
1131
# modified children remaining by the time we examine it.
1132
for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
1133
if op is not None), reverse=True):
1134
if file_id not in self:
1137
# Preserve unaltered children of file_id for later reinsertion.
1138
file_id_children = getattr(self[file_id], 'children', {})
1139
if len(file_id_children):
1140
children[file_id] = file_id_children
1141
# Remove file_id and the unaltered children. If file_id is not
1142
# being deleted it will be reinserted back later.
1143
self.remove_recursive_id(file_id)
1144
# Insert all affected which should be in the new inventory, reattaching
1145
# their children if they had any. This is done from shortest path to
1146
# longest, ensuring that items which were modified and whose parents in
1147
# the resulting inventory were also modified, are inserted after their
1149
for new_path, new_entry in sorted((np, e) for op, np, f, e in
1150
delta if np is not None):
1151
if new_entry.kind == 'directory':
1152
# Pop the child which to allow detection of children whose
1153
# parents were deleted and which were not reattached to a new
1155
replacement = InventoryDirectory(new_entry.file_id,
1156
new_entry.name, new_entry.parent_id)
1157
replacement.revision = new_entry.revision
1158
replacement.children = children.pop(replacement.file_id, {})
1159
new_entry = replacement
1162
# Get the parent id that was deleted
1163
parent_id, children = children.popitem()
1164
raise errors.InconsistentDelta("<deleted>", parent_id,
1165
"The file id was deleted but its children were not deleted.")
1167
def _set_root(self, ie):
1169
self._byid = {self.root.file_id: self.root}
1172
# TODO: jam 20051218 Should copy also copy the revision_id?
1173
entries = self.iter_entries()
1174
if self.root is None:
1175
return Inventory(root_id=None)
1176
other = Inventory(entries.next()[1].file_id)
1177
other.root.revision = self.root.revision
1178
# copy recursively so we know directories will be added before
1179
# their children. There are more efficient ways than this...
1180
for path, entry in entries:
1181
other.add(entry.copy())
1184
def _get_mutable_inventory(self):
1185
"""Returns a mutable copy of the object.
1187
Some inventories are immutable, yet working trees, for example, needs
1188
to mutate exisiting inventories instead of creating a new one.
1190
return deepcopy(self)
1192
def _get_mutable_inventory(self):
1193
"""See CommonInventory._get_mutable_inventory."""
1194
return deepcopy(self)
1197
"""Iterate over all file-ids."""
1198
return iter(self._byid)
1200
def iter_just_entries(self):
1201
"""Iterate over all entries.
1203
Unlike iter_entries(), just the entries are returned (not (path, ie))
1204
and the order of entries is undefined.
1016
def __contains__(self, file_id):
1017
"""True if this entry contains a file with given id.
1206
XXX: We may not want to merge this into bzr.dev.
1019
>>> inv = Inventory()
1020
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1021
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
1208
if self.root is None:
1210
for _, ie in self._byid.iteritems():
1214
"""Returns number of entries."""
1215
return len(self._byid)
1027
return (file_id in self._byid)
1217
1029
def __getitem__(self, file_id):
1218
1030
"""Return the entry for given file_id.
1220
1032
>>> inv = Inventory()
1221
1033
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
1222
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1034
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
1223
1035
>>> inv['123123'].name
1442
1274
return self.root is not None and file_id == self.root.file_id
1445
class CHKInventory(CommonInventory):
1446
"""An inventory persisted in a CHK store.
1448
By design, a CHKInventory is immutable so many of the methods
1449
supported by Inventory - add, rename, apply_delta, etc - are *not*
1450
supported. To create a new CHKInventory, use create_by_apply_delta()
1451
or from_inventory(), say.
1453
Internally, a CHKInventory has one or two CHKMaps:
1455
* id_to_entry - a map from (file_id,) => InventoryEntry as bytes
1456
* parent_id_basename_to_file_id - a map from (parent_id, basename_utf8)
1459
The second map is optional and not present in early CHkRepository's.
1461
No caching is performed: every method call or item access will perform
1462
requests to the storage layer. As such, keep references to objects you
1466
def __init__(self, search_key_name):
1467
CommonInventory.__init__(self)
1468
self._fileid_to_entry_cache = {}
1469
self._path_to_fileid_cache = {}
1470
self._search_key_name = search_key_name
1472
def _entry_to_bytes(self, entry):
1473
"""Serialise entry as a single bytestring.
1475
:param Entry: An inventory entry.
1476
:return: A bytestring for the entry.
1479
ENTRY ::= FILE | DIR | SYMLINK | TREE
1480
FILE ::= "file: " COMMON SEP SHA SEP SIZE SEP EXECUTABLE
1481
DIR ::= "dir: " COMMON
1482
SYMLINK ::= "symlink: " COMMON SEP TARGET_UTF8
1483
TREE ::= "tree: " COMMON REFERENCE_REVISION
1484
COMMON ::= FILE_ID SEP PARENT_ID SEP NAME_UTF8 SEP REVISION
1487
if entry.parent_id is not None:
1488
parent_str = entry.parent_id
1491
name_str = entry.name.encode("utf8")
1492
if entry.kind == 'file':
1493
if entry.executable:
1497
return "file: %s\n%s\n%s\n%s\n%s\n%d\n%s" % (
1498
entry.file_id, parent_str, name_str, entry.revision,
1499
entry.text_sha1, entry.text_size, exec_str)
1500
elif entry.kind == 'directory':
1501
return "dir: %s\n%s\n%s\n%s" % (
1502
entry.file_id, parent_str, name_str, entry.revision)
1503
elif entry.kind == 'symlink':
1504
return "symlink: %s\n%s\n%s\n%s\n%s" % (
1505
entry.file_id, parent_str, name_str, entry.revision,
1506
entry.symlink_target.encode("utf8"))
1507
elif entry.kind == 'tree-reference':
1508
return "tree: %s\n%s\n%s\n%s\n%s" % (
1509
entry.file_id, parent_str, name_str, entry.revision,
1510
entry.reference_revision)
1512
raise ValueError("unknown kind %r" % entry.kind)
1515
def _bytes_to_utf8name_key(bytes):
1516
"""Get the file_id, revision_id key out of bytes."""
1517
# We don't normally care about name, except for times when we want
1518
# to filter out empty names because of non rich-root...
1519
sections = bytes.split('\n')
1520
kind, file_id = sections[0].split(': ')
1521
return (sections[2], file_id, sections[3])
1523
def _bytes_to_entry(self, bytes):
1524
"""Deserialise a serialised entry."""
1525
sections = bytes.split('\n')
1526
if sections[0].startswith("file: "):
1527
result = InventoryFile(sections[0][6:],
1528
sections[2].decode('utf8'),
1530
result.text_sha1 = sections[4]
1531
result.text_size = int(sections[5])
1532
result.executable = sections[6] == "Y"
1533
elif sections[0].startswith("dir: "):
1534
result = CHKInventoryDirectory(sections[0][5:],
1535
sections[2].decode('utf8'),
1537
elif sections[0].startswith("symlink: "):
1538
result = InventoryLink(sections[0][9:],
1539
sections[2].decode('utf8'),
1541
result.symlink_target = sections[4].decode('utf8')
1542
elif sections[0].startswith("tree: "):
1543
result = TreeReference(sections[0][6:],
1544
sections[2].decode('utf8'),
1546
result.reference_revision = sections[4]
1548
raise ValueError("Not a serialised entry %r" % bytes)
1549
result.revision = sections[3]
1550
if result.parent_id == '':
1551
result.parent_id = None
1552
self._fileid_to_entry_cache[result.file_id] = result
1555
def _get_mutable_inventory(self):
1556
"""See CommonInventory._get_mutable_inventory."""
1557
entries = self.iter_entries()
1558
if self.root_id is not None:
1560
inv = Inventory(self.root_id, self.revision_id)
1561
for path, inv_entry in entries:
1565
def create_by_apply_delta(self, inventory_delta, new_revision_id,
1566
propagate_caches=False):
1567
"""Create a new CHKInventory by applying inventory_delta to this one.
1569
:param inventory_delta: The inventory delta to apply. See
1570
Inventory.apply_delta for details.
1571
:param new_revision_id: The revision id of the resulting CHKInventory.
1572
:param propagate_caches: If True, the caches for this inventory are
1573
copied to and updated for the result.
1574
:return: The new CHKInventory.
1576
result = CHKInventory(self._search_key_name)
1577
if propagate_caches:
1578
# Just propagate the path-to-fileid cache for now
1579
result._path_to_fileid_cache = dict(self._path_to_fileid_cache.iteritems())
1580
search_key_func = chk_map.search_key_registry.get(self._search_key_name)
1581
self.id_to_entry._ensure_root()
1582
maximum_size = self.id_to_entry._root_node.maximum_size
1583
result.revision_id = new_revision_id
1584
result.id_to_entry = chk_map.CHKMap(
1585
self.id_to_entry._store,
1586
self.id_to_entry.key(),
1587
search_key_func=search_key_func)
1588
result.id_to_entry._ensure_root()
1589
result.id_to_entry._root_node.set_maximum_size(maximum_size)
1590
parent_id_basename_delta = []
1591
if self.parent_id_basename_to_file_id is not None:
1592
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1593
self.parent_id_basename_to_file_id._store,
1594
self.parent_id_basename_to_file_id.key(),
1595
search_key_func=search_key_func)
1596
result.parent_id_basename_to_file_id._ensure_root()
1597
self.parent_id_basename_to_file_id._ensure_root()
1598
result_p_id_root = result.parent_id_basename_to_file_id._root_node
1599
p_id_root = self.parent_id_basename_to_file_id._root_node
1600
result_p_id_root.set_maximum_size(p_id_root.maximum_size)
1601
result_p_id_root._key_width = p_id_root._key_width
1603
result.parent_id_basename_to_file_id = None
1604
result.root_id = self.root_id
1605
id_to_entry_delta = []
1606
for old_path, new_path, file_id, entry in inventory_delta:
1609
result.root_id = file_id
1610
if new_path is None:
1615
if propagate_caches:
1617
del result._path_to_fileid_cache[old_path]
1621
new_key = (file_id,)
1622
new_value = result._entry_to_bytes(entry)
1623
# Update caches. It's worth doing this whether
1624
# we're propagating the old caches or not.
1625
result._path_to_fileid_cache[new_path] = file_id
1626
if old_path is None:
1629
old_key = (file_id,)
1630
id_to_entry_delta.append((old_key, new_key, new_value))
1631
if result.parent_id_basename_to_file_id is not None:
1632
# parent_id, basename changes
1633
if old_path is None:
1636
old_entry = self[file_id]
1637
old_key = self._parent_id_basename_key(old_entry)
1638
if new_path is None:
1642
new_key = self._parent_id_basename_key(entry)
1644
if old_key != new_key:
1645
# If the two keys are the same, the value will be unchanged
1646
# as its always the file id.
1647
parent_id_basename_delta.append((old_key, new_key, new_value))
1648
result.id_to_entry.apply_delta(id_to_entry_delta)
1649
if parent_id_basename_delta:
1650
result.parent_id_basename_to_file_id.apply_delta(parent_id_basename_delta)
1654
def deserialise(klass, chk_store, bytes, expected_revision_id):
1655
"""Deserialise a CHKInventory.
1657
:param chk_store: A CHK capable VersionedFiles instance.
1658
:param bytes: The serialised bytes.
1659
:param expected_revision_id: The revision ID we think this inventory is
1661
:return: A CHKInventory
1663
lines = bytes.split('\n')
1665
raise AssertionError('bytes to deserialize must end with an eol')
1667
if lines[0] != 'chkinventory:':
1668
raise ValueError("not a serialised CHKInventory: %r" % bytes)
1670
allowed_keys = frozenset(['root_id', 'revision_id', 'search_key_name',
1671
'parent_id_basename_to_file_id',
1673
for line in lines[1:]:
1674
key, value = line.split(': ', 1)
1675
if key not in allowed_keys:
1676
raise errors.BzrError('Unknown key in inventory: %r\n%r'
1679
raise errors.BzrError('Duplicate key in inventory: %r\n%r'
1682
revision_id = info['revision_id']
1683
root_id = info['root_id']
1684
search_key_name = info.get('search_key_name', 'plain')
1685
parent_id_basename_to_file_id = info.get(
1686
'parent_id_basename_to_file_id', None)
1687
id_to_entry = info['id_to_entry']
1689
result = CHKInventory(search_key_name)
1690
result.revision_id = revision_id
1691
result.root_id = root_id
1692
search_key_func = chk_map.search_key_registry.get(
1693
result._search_key_name)
1694
if parent_id_basename_to_file_id is not None:
1695
result.parent_id_basename_to_file_id = chk_map.CHKMap(
1696
chk_store, (parent_id_basename_to_file_id,),
1697
search_key_func=search_key_func)
1699
result.parent_id_basename_to_file_id = None
1701
result.id_to_entry = chk_map.CHKMap(chk_store, (id_to_entry,),
1702
search_key_func=search_key_func)
1703
if (result.revision_id,) != expected_revision_id:
1704
raise ValueError("Mismatched revision id and expected: %r, %r" %
1705
(result.revision_id, expected_revision_id))
1709
def from_inventory(klass, chk_store, inventory, maximum_size=0, search_key_name='plain'):
1710
"""Create a CHKInventory from an existing inventory.
1712
The content of inventory is copied into the chk_store, and a
1713
CHKInventory referencing that is returned.
1715
:param chk_store: A CHK capable VersionedFiles instance.
1716
:param inventory: The inventory to copy.
1717
:param maximum_size: The CHKMap node size limit.
1718
:param search_key_name: The identifier for the search key function
1720
result = CHKInventory(search_key_name)
1721
result.revision_id = inventory.revision_id
1722
result.root_id = inventory.root.file_id
1723
search_key_func = chk_map.search_key_registry.get(search_key_name)
1724
result.id_to_entry = chk_map.CHKMap(chk_store, None, search_key_func)
1725
result.id_to_entry._root_node.set_maximum_size(maximum_size)
1727
result.parent_id_basename_to_file_id = chk_map.CHKMap(chk_store,
1728
None, search_key_func)
1729
result.parent_id_basename_to_file_id._root_node.set_maximum_size(
1731
result.parent_id_basename_to_file_id._root_node._key_width = 2
1732
parent_id_delta = []
1733
for path, entry in inventory.iter_entries():
1734
file_id_delta.append((None, (entry.file_id,),
1735
result._entry_to_bytes(entry)))
1736
parent_id_delta.append(
1737
(None, result._parent_id_basename_key(entry),
1739
result.id_to_entry.apply_delta(file_id_delta)
1740
result.parent_id_basename_to_file_id.apply_delta(parent_id_delta)
1743
def _parent_id_basename_key(self, entry):
1744
"""Create a key for a entry in a parent_id_basename_to_file_id index."""
1745
if entry.parent_id is not None:
1746
parent_id = entry.parent_id
1749
return parent_id, entry.name.encode('utf8')
1751
def __getitem__(self, file_id):
1752
"""map a single file_id -> InventoryEntry."""
1754
raise errors.NoSuchId(self, file_id)
1755
result = self._fileid_to_entry_cache.get(file_id, None)
1756
if result is not None:
1759
return self._bytes_to_entry(
1760
self.id_to_entry.iteritems([(file_id,)]).next()[1])
1761
except StopIteration:
1762
# really we're passing an inventory, not a tree...
1763
raise errors.NoSuchId(self, file_id)
1765
def has_id(self, file_id):
1766
# Perhaps have an explicit 'contains' method on CHKMap ?
1767
if self._fileid_to_entry_cache.get(file_id, None) is not None:
1769
return len(list(self.id_to_entry.iteritems([(file_id,)]))) == 1
1771
def is_root(self, file_id):
1772
return file_id == self.root_id
1774
def _iter_file_id_parents(self, file_id):
1775
"""Yield the parents of file_id up to the root."""
1776
while file_id is not None:
1780
raise errors.NoSuchId(tree=self, file_id=file_id)
1782
file_id = ie.parent_id
1785
"""Iterate over all file-ids."""
1786
for key, _ in self.id_to_entry.iteritems():
1789
def iter_just_entries(self):
1790
"""Iterate over all entries.
1792
Unlike iter_entries(), just the entries are returned (not (path, ie))
1793
and the order of entries is undefined.
1795
XXX: We may not want to merge this into bzr.dev.
1797
for key, entry in self.id_to_entry.iteritems():
1799
ie = self._fileid_to_entry_cache.get(file_id, None)
1801
ie = self._bytes_to_entry(entry)
1802
self._fileid_to_entry_cache[file_id] = ie
1805
def iter_changes(self, basis):
1806
"""Generate a Tree.iter_changes change list between this and basis.
1808
:param basis: Another CHKInventory.
1809
:return: An iterator over the changes between self and basis, as per
1810
tree.iter_changes().
1812
# We want: (file_id, (path_in_source, path_in_target),
1813
# changed_content, versioned, parent, name, kind,
1815
for key, basis_value, self_value in \
1816
self.id_to_entry.iter_changes(basis.id_to_entry):
1818
if basis_value is not None:
1819
basis_entry = basis._bytes_to_entry(basis_value)
1820
path_in_source = basis.id2path(file_id)
1821
basis_parent = basis_entry.parent_id
1822
basis_name = basis_entry.name
1823
basis_executable = basis_entry.executable
1825
path_in_source = None
1828
basis_executable = None
1829
if self_value is not None:
1830
self_entry = self._bytes_to_entry(self_value)
1831
path_in_target = self.id2path(file_id)
1832
self_parent = self_entry.parent_id
1833
self_name = self_entry.name
1834
self_executable = self_entry.executable
1836
path_in_target = None
1839
self_executable = None
1840
if basis_value is None:
1842
kind = (None, self_entry.kind)
1843
versioned = (False, True)
1844
elif self_value is None:
1846
kind = (basis_entry.kind, None)
1847
versioned = (True, False)
1849
kind = (basis_entry.kind, self_entry.kind)
1850
versioned = (True, True)
1851
changed_content = False
1852
if kind[0] != kind[1]:
1853
changed_content = True
1854
elif kind[0] == 'file':
1855
if (self_entry.text_size != basis_entry.text_size or
1856
self_entry.text_sha1 != basis_entry.text_sha1):
1857
changed_content = True
1858
elif kind[0] == 'symlink':
1859
if self_entry.symlink_target != basis_entry.symlink_target:
1860
changed_content = True
1861
elif kind[0] == 'tree-reference':
1862
if (self_entry.reference_revision !=
1863
basis_entry.reference_revision):
1864
changed_content = True
1865
parent = (basis_parent, self_parent)
1866
name = (basis_name, self_name)
1867
executable = (basis_executable, self_executable)
1868
if (not changed_content
1869
and parent[0] == parent[1]
1870
and name[0] == name[1]
1871
and executable[0] == executable[1]):
1872
# Could happen when only the revision changed for a directory
1875
yield (file_id, (path_in_source, path_in_target), changed_content,
1876
versioned, parent, name, kind, executable)
1879
"""Return the number of entries in the inventory."""
1880
return len(self.id_to_entry)
1882
def _make_delta(self, old):
1883
"""Make an inventory delta from two inventories."""
1884
if type(old) != CHKInventory:
1885
return CommonInventory._make_delta(self, old)
1887
for key, old_value, self_value in \
1888
self.id_to_entry.iter_changes(old.id_to_entry):
1890
if old_value is not None:
1891
old_path = old.id2path(file_id)
1894
if self_value is not None:
1895
entry = self._bytes_to_entry(self_value)
1896
self._fileid_to_entry_cache[file_id] = entry
1897
new_path = self.id2path(file_id)
1901
delta.append((old_path, new_path, file_id, entry))
1904
def path2id(self, name):
1905
"""See CommonInventory.path2id()."""
1906
result = self._path_to_fileid_cache.get(name, None)
1908
result = CommonInventory.path2id(self, name)
1909
self._path_to_fileid_cache[name] = result
1913
"""Serialise the inventory to lines."""
1914
lines = ["chkinventory:\n"]
1915
if self._search_key_name != 'plain':
1916
# custom ordering grouping things that don't change together
1917
lines.append('search_key_name: %s\n' % (self._search_key_name,))
1918
lines.append("root_id: %s\n" % self.root_id)
1919
lines.append('parent_id_basename_to_file_id: %s\n' %
1920
self.parent_id_basename_to_file_id.key())
1921
lines.append("revision_id: %s\n" % self.revision_id)
1922
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
1924
lines.append("revision_id: %s\n" % self.revision_id)
1925
lines.append("root_id: %s\n" % self.root_id)
1926
if self.parent_id_basename_to_file_id is not None:
1927
lines.append('parent_id_basename_to_file_id: %s\n' %
1928
self.parent_id_basename_to_file_id.key())
1929
lines.append("id_to_entry: %s\n" % self.id_to_entry.key())
1934
"""Get the root entry."""
1935
return self[self.root_id]
1938
class CHKInventoryDirectory(InventoryDirectory):
1939
"""A directory in an inventory."""
1941
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
1942
'text_id', 'parent_id', '_children', 'executable',
1943
'revision', 'symlink_target', 'reference_revision',
1946
def __init__(self, file_id, name, parent_id, chk_inventory):
1947
# Don't call InventoryDirectory.__init__ - it isn't right for this
1949
InventoryEntry.__init__(self, file_id, name, parent_id)
1950
self._children = None
1951
self.kind = 'directory'
1952
self._chk_inventory = chk_inventory
1956
"""Access the list of children of this directory.
1958
With a parent_id_basename_to_file_id index, loads all the children,
1959
without loads the entire index. Without is bad. A more sophisticated
1960
proxy object might be nice, to allow partial loading of children as
1961
well when specific names are accessed. (So path traversal can be
1962
written in the obvious way but not examine siblings.).
1964
if self._children is not None:
1965
return self._children
1966
# No longer supported
1967
if self._chk_inventory.parent_id_basename_to_file_id is None:
1968
raise AssertionError("Inventories without"
1969
" parent_id_basename_to_file_id are no longer supported")
1971
# XXX: Todo - use proxy objects for the children rather than loading
1972
# all when the attribute is referenced.
1973
parent_id_index = self._chk_inventory.parent_id_basename_to_file_id
1975
for (parent_id, name_utf8), file_id in parent_id_index.iteritems(
1976
key_filter=[(self.file_id,)]):
1977
child_keys.add((file_id,))
1979
for file_id_key in child_keys:
1980
entry = self._chk_inventory._fileid_to_entry_cache.get(
1981
file_id_key[0], None)
1982
if entry is not None:
1983
result[entry.name] = entry
1984
cached.add(file_id_key)
1985
child_keys.difference_update(cached)
1986
# populate; todo: do by name
1987
id_to_entry = self._chk_inventory.id_to_entry
1988
for file_id_key, bytes in id_to_entry.iteritems(child_keys):
1989
entry = self._chk_inventory._bytes_to_entry(bytes)
1990
result[entry.name] = entry
1991
self._chk_inventory._fileid_to_entry_cache[file_id_key[0]] = entry
1992
self._children = result
1996
'directory': InventoryDirectory,
1997
'file': InventoryFile,
1998
'symlink': InventoryLink,
1999
'tree-reference': TreeReference
2002
1277
def make_entry(kind, name, parent_id, file_id=None):
2003
1278
"""Create an inventory entry.