90
91
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
91
92
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
92
93
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
93
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
94
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None, revision=None)
94
95
>>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
95
96
>>> for ix, j in enumerate(i.iter_entries()):
96
97
... print (j[0] == shouldbe[ix], j[1])
98
99
(True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
99
100
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
100
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
101
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None, revision=None))
101
102
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
102
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
103
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None, revision=None)
103
104
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
104
105
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
105
106
>>> i.path2id('src/wibble')
709
711
return compatible
712
class Inventory(object):
714
class CommonInventory(object):
715
"""Basic inventory logic, defined in terms of primitives like has_id."""
717
def iter_entries(self, from_dir=None):
718
"""Return (path, entry) pairs, in order by name."""
720
if self.root is None:
724
elif isinstance(from_dir, basestring):
725
from_dir = self[from_dir]
727
# unrolling the recursive called changed the time from
728
# 440ms/663ms (inline/total) to 116ms/116ms
729
children = from_dir.children.items()
731
children = collections.deque(children)
732
stack = [(u'', children)]
734
from_dir_relpath, children = stack[-1]
737
name, ie = children.popleft()
739
# we know that from_dir_relpath never ends in a slash
740
# and 'f' doesn't begin with one, we can do a string op, rather
741
# than the checks of pathjoin(), though this means that all paths
743
path = from_dir_relpath + '/' + name
747
if ie.kind != 'directory':
750
# But do this child first
751
new_children = ie.children.items()
753
new_children = collections.deque(new_children)
754
stack.append((path, new_children))
755
# Break out of inner loop, so that we start outer loop with child
758
# if we finished all children, pop it off the stack
761
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
762
yield_parents=False):
763
"""Iterate over the entries in a directory first order.
765
This returns all entries for a directory before returning
766
the entries for children of a directory. This is not
767
lexicographically sorted order, and is a hybrid between
768
depth-first and breadth-first.
770
:param yield_parents: If True, yield the parents from the root leading
771
down to specific_file_ids that have been requested. This has no
772
impact if specific_file_ids is None.
773
:return: This yields (path, entry) pairs
775
if specific_file_ids and not isinstance(specific_file_ids, set):
776
specific_file_ids = set(specific_file_ids)
777
# TODO? Perhaps this should return the from_dir so that the root is
778
# yielded? or maybe an option?
780
if self.root is None:
782
# Optimize a common case
783
if (not yield_parents and specific_file_ids is not None and
784
len(specific_file_ids) == 1):
785
file_id = list(specific_file_ids)[0]
787
yield self.id2path(file_id), self[file_id]
790
if (specific_file_ids is None or yield_parents or
791
self.root.file_id in specific_file_ids):
793
elif isinstance(from_dir, basestring):
794
from_dir = self[from_dir]
796
if specific_file_ids is not None:
797
# TODO: jam 20070302 This could really be done as a loop rather
798
# than a bunch of recursive calls.
801
def add_ancestors(file_id):
802
if file_id not in byid:
804
parent_id = byid[file_id].parent_id
805
if parent_id is None:
807
if parent_id not in parents:
808
parents.add(parent_id)
809
add_ancestors(parent_id)
810
for file_id in specific_file_ids:
811
add_ancestors(file_id)
815
stack = [(u'', from_dir)]
817
cur_relpath, cur_dir = stack.pop()
820
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
822
child_relpath = cur_relpath + child_name
824
if (specific_file_ids is None or
825
child_ie.file_id in specific_file_ids or
826
(yield_parents and child_ie.file_id in parents)):
827
yield child_relpath, child_ie
829
if child_ie.kind == 'directory':
830
if parents is None or child_ie.file_id in parents:
831
child_dirs.append((child_relpath+'/', child_ie))
832
stack.extend(reversed(child_dirs))
835
class Inventory(CommonInventory):
713
836
"""Inventory of versioned files in a tree.
715
838
This describes which file_id is present at each point in the tree,
864
987
"""Returns number of entries."""
865
988
return len(self._byid)
867
def iter_entries(self, from_dir=None):
868
"""Return (path, entry) pairs, in order by name."""
870
if self.root is None:
874
elif isinstance(from_dir, basestring):
875
from_dir = self._byid[from_dir]
877
# unrolling the recursive called changed the time from
878
# 440ms/663ms (inline/total) to 116ms/116ms
879
children = from_dir.children.items()
881
children = collections.deque(children)
882
stack = [(u'', children)]
884
from_dir_relpath, children = stack[-1]
887
name, ie = children.popleft()
889
# we know that from_dir_relpath never ends in a slash
890
# and 'f' doesn't begin with one, we can do a string op, rather
891
# than the checks of pathjoin(), though this means that all paths
893
path = from_dir_relpath + '/' + name
897
if ie.kind != 'directory':
900
# But do this child first
901
new_children = ie.children.items()
903
new_children = collections.deque(new_children)
904
stack.append((path, new_children))
905
# Break out of inner loop, so that we start outer loop with child
908
# if we finished all children, pop it off the stack
911
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
912
yield_parents=False):
913
"""Iterate over the entries in a directory first order.
915
This returns all entries for a directory before returning
916
the entries for children of a directory. This is not
917
lexicographically sorted order, and is a hybrid between
918
depth-first and breadth-first.
920
:param yield_parents: If True, yield the parents from the root leading
921
down to specific_file_ids that have been requested. This has no
922
impact if specific_file_ids is None.
923
:return: This yields (path, entry) pairs
925
if specific_file_ids and not isinstance(specific_file_ids, set):
926
specific_file_ids = set(specific_file_ids)
927
# TODO? Perhaps this should return the from_dir so that the root is
928
# yielded? or maybe an option?
930
if self.root is None:
932
# Optimize a common case
933
if (not yield_parents and specific_file_ids is not None and
934
len(specific_file_ids) == 1):
935
file_id = list(specific_file_ids)[0]
937
yield self.id2path(file_id), self[file_id]
940
if (specific_file_ids is None or yield_parents or
941
self.root.file_id in specific_file_ids):
943
elif isinstance(from_dir, basestring):
944
from_dir = self._byid[from_dir]
946
if specific_file_ids is not None:
947
# TODO: jam 20070302 This could really be done as a loop rather
948
# than a bunch of recursive calls.
951
def add_ancestors(file_id):
952
if file_id not in byid:
954
parent_id = byid[file_id].parent_id
955
if parent_id is None:
957
if parent_id not in parents:
958
parents.add(parent_id)
959
add_ancestors(parent_id)
960
for file_id in specific_file_ids:
961
add_ancestors(file_id)
965
stack = [(u'', from_dir)]
967
cur_relpath, cur_dir = stack.pop()
970
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
972
child_relpath = cur_relpath + child_name
974
if (specific_file_ids is None or
975
child_ie.file_id in specific_file_ids or
976
(yield_parents and child_ie.file_id in parents)):
977
yield child_relpath, child_ie
979
if child_ie.kind == 'directory':
980
if parents is None or child_ie.file_id in parents:
981
child_dirs.append((child_relpath+'/', child_ie))
982
stack.extend(reversed(child_dirs))
984
990
def make_entry(self, kind, name, parent_id, file_id=None):
985
991
"""Simple thunk to bzrlib.inventory.make_entry."""
986
992
return make_entry(kind, name, parent_id, file_id)
1288
1294
return self.root is not None and file_id == self.root.file_id
1297
class CHKInventory(CommonInventory):
1298
"""A inventory persisted in a CHK store.
1300
No caching is performed: every method call or item access will perform
1301
requests to the storage layer. As such, keep references to objects you want
1305
def _entry_to_bytes(self, entry):
1306
"""Serialise entry as a single bytestring.
1308
:param Entry: An inventory entry.
1309
:return: A bytestring for the entry.
1312
ENTRY ::= FILE | DIR | SYMLINK | TREE
1313
FILE ::= "file: " COMMON NULL SHA NULL SIZE NULL EXECUTABLE
1314
DIR ::= "dir: " COMMON
1315
SYMLINK ::= "symlink: " COMMON NULL TARGET_UTF8
1316
TREE ::= "tree: " COMMON REFERENCE_REVISION
1317
COMMON ::= FILE_ID NULL PARENT_ID NULL NAME_UTF8 NULL REVISION
1319
if entry.parent_id is not None:
1320
parent_str = entry.parent_id
1323
name_str = entry.name.encode("utf8")
1324
if entry.kind == 'file':
1325
if entry.executable:
1329
return "file: %s\x00%s\x00%s\x00%s\x00%s\x00%d\x00%s" % (
1330
entry.file_id, parent_str, name_str, entry.revision,
1331
entry.text_sha1, entry.text_size, exec_str)
1332
elif entry.kind == 'directory':
1333
return "dir: %s\x00%s\x00%s\x00%s" % (
1334
entry.file_id, parent_str, name_str, entry.revision)
1335
elif entry.kind == 'symlink':
1336
return "symlink: %s\x00%s\x00%s\x00%s\x00%s" % (
1337
entry.file_id, parent_str, name_str, entry.revision,
1338
entry.symlink_target.encode("utf8"))
1339
elif entry.kind == 'tree-reference':
1340
return "tree: %s\x00%s\x00%s\x00%s\x00%s" % (
1341
entry.file_id, parent_str, name_str, entry.revision,
1342
entry.reference_revision)
1344
raise ValueError("unknown kind %r" % entry.kind)
1346
def _bytes_to_entry(self, bytes):
1347
"""Deserialise a serialised entry."""
1348
sections = bytes.split('\x00')
1349
if sections[0].startswith("file: "):
1350
result = InventoryFile(sections[0][6:],
1351
sections[2].decode('utf8'),
1353
result.text_sha1 = sections[4]
1354
result.text_size = int(sections[5])
1355
result.executable = sections[6] == "Y"
1356
elif sections[0].startswith("dir: "):
1357
result = CHKInventoryDirectory(sections[0][5:],
1358
sections[2].decode('utf8'),
1360
elif sections[0].startswith("symlink: "):
1361
result = InventoryLink(sections[0][9:],
1362
sections[2].decode('utf8'),
1364
result.symlink_target = sections[4]
1365
elif sections[0].startswith("tree: "):
1366
result = TreeReference(sections[0][6:],
1367
sections[2].decode('utf8'),
1369
result.reference_revision = sections[4]
1371
raise ValueError("Not a serialised entry %r" % bytes)
1372
result.revision = sections[3]
1373
if result.parent_id == '':
1374
result.parent_id = None
1378
def deserialise(klass, chk_store, bytes, expected_revision_id):
1379
"""Deserialise a CHKInventory.
1381
:param chk_store: A CHK capable VersionedFiles instance.
1382
:param bytes: The serialised bytes.
1383
:param expected_revision_id: The revision ID we think this inventory is
1385
:return: A CHKInventory
1387
result = CHKInventory()
1388
lines = bytes.splitlines()
1389
if lines[0] != 'chkinventory:':
1390
raise ValueError("not a serialised CHKInventory: %r" % bytes)
1391
result.revision_id = lines[1][13:]
1392
result.root_id = lines[2][9:]
1393
result.id_to_entry = chk_map.CHKMap(chk_store, (lines[3][13:],))
1394
if (result.revision_id,) != expected_revision_id:
1395
raise ValueError("Mismatched revision id and expected: %r, %r" %
1396
(result.revision_id, expected_revision_id))
1400
def from_inventory(klass, chk_store, inventory):
1401
"""Create a CHKInventory from an existing inventory.
1403
The content of inventory is copied into the chk_store, and a
1404
CHKInventory referencing that is returned.
1406
:param chk_store: A CHK capable VersionedFiles instance.
1407
:param inventory: The inventory to copy.
1409
result = CHKInventory()
1410
result.revision_id = inventory.revision_id
1411
result.root_id = inventory.root.file_id
1412
result.id_to_entry = chk_map.CHKMap(chk_store, None)
1413
for path, entry in inventory.iter_entries():
1414
result.id_to_entry._map(entry.file_id,
1415
result._entry_to_bytes(entry))
1416
result.id_to_entry._save()
1419
def __getitem__(self, file_id):
1420
"""map a single file_id -> InventoryEntry."""
1422
return self._bytes_to_entry(
1423
self.id_to_entry.iteritems([file_id]).next()[1])
1424
except StopIteration:
1425
raise KeyError(file_id)
1427
def has_id(self, file_id):
1428
# Perhaps have an explicit 'contains' method on CHKMap ?
1429
return len(list(self.id_to_entry.iteritems([file_id]))) == 1
1432
"""Iterate over the entire inventory contents; size-of-tree - beware!."""
1433
for file_id, _ in self.id_to_entry.iteritems():
1437
"""Return the number of entries in the inventory."""
1438
# Might want to cache the length in the meta node.
1439
return len([item for item in self])
1442
"""Serialise the inventory to lines."""
1443
lines = ["chkinventory:\n"]
1444
lines.append("revision_id: %s\n" % self.revision_id)
1445
lines.append("root_id: %s\n" % self.root_id)
1446
lines.append("id_to_entry: %s\n" % self.id_to_entry._root_node._key)
1451
"""Get the root entry."""
1452
return self[self.root_id]
1455
class CHKInventoryDirectory(InventoryDirectory):
1456
"""A directory in an inventory."""
1458
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
1459
'text_id', 'parent_id', '_children', 'executable',
1460
'revision', 'symlink_target', 'reference_revision',
1463
def __init__(self, file_id, name, parent_id, chk_inventory):
1464
# Don't call InventoryDirectory.__init__ - it isn't right for this
1466
InventoryEntry.__init__(self, file_id, name, parent_id)
1467
self._children = None
1468
self.kind = 'directory'
1469
self._chk_inventory = chk_inventory
1473
"""Access the list of children of this inventory.
1475
Currently causes a full-load of all the children; a more sophisticated
1476
proxy object is planned.
1478
if self._children is not None:
1479
return self._children
1481
# populate; todo: do by name
1482
for file_id, bytes in self._chk_inventory.id_to_entry.iteritems():
1483
entry = self._chk_inventory._bytes_to_entry(bytes)
1484
if entry.parent_id == self.file_id:
1485
result[entry.name] = entry
1486
self._children = result
1291
1490
entry_factory = {
1292
1491
'directory': InventoryDirectory,
1293
1492
'file': InventoryFile,