713
712
return compatible
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]))
750
def iter_entries(self, from_dir=None):
751
"""Return (path, entry) pairs, in order by name."""
753
if self.root is None:
757
elif isinstance(from_dir, basestring):
758
from_dir = self[from_dir]
760
# unrolling the recursive called changed the time from
761
# 440ms/663ms (inline/total) to 116ms/116ms
762
children = from_dir.children.items()
764
children = collections.deque(children)
765
stack = [(u'', children)]
767
from_dir_relpath, children = stack[-1]
770
name, ie = children.popleft()
772
# we know that from_dir_relpath never ends in a slash
773
# and 'f' doesn't begin with one, we can do a string op, rather
774
# than the checks of pathjoin(), though this means that all paths
776
path = from_dir_relpath + '/' + name
780
if ie.kind != 'directory':
783
# But do this child first
784
new_children = ie.children.items()
786
new_children = collections.deque(new_children)
787
stack.append((path, new_children))
788
# Break out of inner loop, so that we start outer loop with child
791
# if we finished all children, pop it off the stack
794
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
795
yield_parents=False):
796
"""Iterate over the entries in a directory first order.
798
This returns all entries for a directory before returning
799
the entries for children of a directory. This is not
800
lexicographically sorted order, and is a hybrid between
801
depth-first and breadth-first.
803
:param yield_parents: If True, yield the parents from the root leading
804
down to specific_file_ids that have been requested. This has no
805
impact if specific_file_ids is None.
806
:return: This yields (path, entry) pairs
808
if specific_file_ids and not isinstance(specific_file_ids, set):
809
specific_file_ids = set(specific_file_ids)
810
# TODO? Perhaps this should return the from_dir so that the root is
811
# yielded? or maybe an option?
813
if self.root is None:
815
# Optimize a common case
816
if (not yield_parents and specific_file_ids is not None and
817
len(specific_file_ids) == 1):
818
file_id = list(specific_file_ids)[0]
820
yield self.id2path(file_id), self[file_id]
823
if (specific_file_ids is None or yield_parents or
824
self.root.file_id in specific_file_ids):
826
elif isinstance(from_dir, basestring):
827
from_dir = self[from_dir]
829
if specific_file_ids is not None:
830
# TODO: jam 20070302 This could really be done as a loop rather
831
# than a bunch of recursive calls.
834
def add_ancestors(file_id):
835
if file_id not in byid:
837
parent_id = byid[file_id].parent_id
838
if parent_id is None:
840
if parent_id not in parents:
841
parents.add(parent_id)
842
add_ancestors(parent_id)
843
for file_id in specific_file_ids:
844
add_ancestors(file_id)
848
stack = [(u'', from_dir)]
850
cur_relpath, cur_dir = stack.pop()
853
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
855
child_relpath = cur_relpath + child_name
857
if (specific_file_ids is None or
858
child_ie.file_id in specific_file_ids or
859
(yield_parents and child_ie.file_id in parents)):
860
yield child_relpath, child_ie
862
if child_ie.kind == 'directory':
863
if parents is None or child_ie.file_id in parents:
864
child_dirs.append((child_relpath+'/', child_ie))
865
stack.extend(reversed(child_dirs))
867
def _make_delta(self, old):
868
"""Make an inventory delta from two inventories."""
871
adds = new_ids - old_ids
872
deletes = old_ids - new_ids
873
common = old_ids.intersection(new_ids)
875
for file_id in deletes:
876
delta.append((old.id2path(file_id), None, file_id, None))
878
delta.append((None, self.id2path(file_id), file_id, self[file_id]))
879
for file_id in common:
880
if old[file_id] != self[file_id]:
881
delta.append((old.id2path(file_id), self.id2path(file_id),
882
file_id, self[file_id]))
885
def _get_mutable_inventory(self):
886
"""Returns a mutable copy of the object.
888
Some inventories are immutable, yet working trees, for example, needs
889
to mutate exisiting inventories instead of creating a new one.
891
raise NotImplementedError(self._get_mutable_inventory)
893
def make_entry(self, kind, name, parent_id, file_id=None):
894
"""Simple thunk to bzrlib.inventory.make_entry."""
895
return make_entry(kind, name, parent_id, file_id)
898
"""Return list of (path, ie) for all entries except the root.
900
This may be faster than iter_entries.
903
def descend(dir_ie, dir_path):
904
kids = dir_ie.children.items()
906
for name, ie in kids:
907
child_path = osutils.pathjoin(dir_path, name)
908
accum.append((child_path, ie))
909
if ie.kind == 'directory':
910
descend(ie, child_path)
912
descend(self.root, u'')
915
def directories(self):
916
"""Return (path, entry) pairs for all directories, including the root.
919
def descend(parent_ie, parent_path):
920
accum.append((parent_path, parent_ie))
922
kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
925
for name, child_ie in kids:
926
child_path = osutils.pathjoin(parent_path, name)
927
descend(child_ie, child_path)
928
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):
715
class Inventory(object):
1015
716
"""Inventory of versioned files in a tree.
1017
718
This describes which file_id is present at each point in the tree,
1181
860
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)
1196
863
def __iter__(self):
1197
"""Iterate over all file-ids."""
1198
864
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.
1206
XXX: We may not want to merge this into bzr.dev.
1208
if self.root is None:
1210
for _, ie in self._byid.iteritems():
1213
866
def __len__(self):
1214
867
"""Returns number of entries."""
1215
868
return len(self._byid)
870
def iter_entries(self, from_dir=None):
871
"""Return (path, entry) pairs, in order by name."""
873
if self.root is None:
877
elif isinstance(from_dir, basestring):
878
from_dir = self._byid[from_dir]
880
# unrolling the recursive called changed the time from
881
# 440ms/663ms (inline/total) to 116ms/116ms
882
children = from_dir.children.items()
884
children = collections.deque(children)
885
stack = [(u'', children)]
887
from_dir_relpath, children = stack[-1]
890
name, ie = children.popleft()
892
# we know that from_dir_relpath never ends in a slash
893
# and 'f' doesn't begin with one, we can do a string op, rather
894
# than the checks of pathjoin(), though this means that all paths
896
path = from_dir_relpath + '/' + name
900
if ie.kind != 'directory':
903
# But do this child first
904
new_children = ie.children.items()
906
new_children = collections.deque(new_children)
907
stack.append((path, new_children))
908
# Break out of inner loop, so that we start outer loop with child
911
# if we finished all children, pop it off the stack
914
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
915
yield_parents=False):
916
"""Iterate over the entries in a directory first order.
918
This returns all entries for a directory before returning
919
the entries for children of a directory. This is not
920
lexicographically sorted order, and is a hybrid between
921
depth-first and breadth-first.
923
:param yield_parents: If True, yield the parents from the root leading
924
down to specific_file_ids that have been requested. This has no
925
impact if specific_file_ids is None.
926
:return: This yields (path, entry) pairs
928
if specific_file_ids and not isinstance(specific_file_ids, set):
929
specific_file_ids = set(specific_file_ids)
930
# TODO? Perhaps this should return the from_dir so that the root is
931
# yielded? or maybe an option?
933
if self.root is None:
935
# Optimize a common case
936
if (not yield_parents and specific_file_ids is not None and
937
len(specific_file_ids) == 1):
938
file_id = list(specific_file_ids)[0]
940
yield self.id2path(file_id), self[file_id]
943
if (specific_file_ids is None or yield_parents or
944
self.root.file_id in specific_file_ids):
946
elif isinstance(from_dir, basestring):
947
from_dir = self._byid[from_dir]
949
if specific_file_ids is not None:
950
# TODO: jam 20070302 This could really be done as a loop rather
951
# than a bunch of recursive calls.
954
def add_ancestors(file_id):
955
if file_id not in byid:
957
parent_id = byid[file_id].parent_id
958
if parent_id is None:
960
if parent_id not in parents:
961
parents.add(parent_id)
962
add_ancestors(parent_id)
963
for file_id in specific_file_ids:
964
add_ancestors(file_id)
968
stack = [(u'', from_dir)]
970
cur_relpath, cur_dir = stack.pop()
973
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
975
child_relpath = cur_relpath + child_name
977
if (specific_file_ids is None or
978
child_ie.file_id in specific_file_ids or
979
(yield_parents and child_ie.file_id in parents)):
980
yield child_relpath, child_ie
982
if child_ie.kind == 'directory':
983
if parents is None or child_ie.file_id in parents:
984
child_dirs.append((child_relpath+'/', child_ie))
985
stack.extend(reversed(child_dirs))
987
def make_entry(self, kind, name, parent_id, file_id=None):
988
"""Simple thunk to bzrlib.inventory.make_entry."""
989
return make_entry(kind, name, parent_id, file_id)
992
"""Return list of (path, ie) for all entries except the root.
994
This may be faster than iter_entries.
997
def descend(dir_ie, dir_path):
998
kids = dir_ie.children.items()
1000
for name, ie in kids:
1001
child_path = osutils.pathjoin(dir_path, name)
1002
accum.append((child_path, ie))
1003
if ie.kind == 'directory':
1004
descend(ie, child_path)
1006
descend(self.root, u'')
1009
def directories(self):
1010
"""Return (path, entry) pairs for all directories, including the root.
1013
def descend(parent_ie, parent_path):
1014
accum.append((parent_path, parent_ie))
1016
kids = [(ie.name, ie) for ie in parent_ie.children.itervalues() if ie.kind == 'directory']
1019
for name, child_ie in kids:
1020
child_path = osutils.pathjoin(parent_path, name)
1021
descend(child_ie, child_path)
1022
descend(self.root, u'')
1025
def __contains__(self, file_id):
1026
"""True if this entry contains a file with given id.
1028
>>> inv = Inventory()
1029
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1030
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
1036
return (file_id in self._byid)
1217
1038
def __getitem__(self, file_id):
1218
1039
"""Return the entry for given file_id.
1220
1041
>>> inv = Inventory()
1221
1042
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
1222
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1043
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
1223
1044
>>> inv['123123'].name
1442
1322
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
1995
1325
entry_factory = {
1996
1326
'directory': InventoryDirectory,
1997
1327
'file': InventoryFile,