714
714
class CommonInventory(object):
715
"""Basic inventory logic, defined in terms of primitives like has_id."""
715
"""Basic inventory logic, defined in terms of primitives like has_id.
717
An inventory is the metadata about the contents of a tree.
719
This is broadly a map from file_id to entries such as directories, files,
720
symlinks and tree references. Each entry maintains its own metadata like
721
SHA1 and length for files, or children for a directory.
723
Entries can be looked up either by path or by file_id.
725
InventoryEntry objects must not be modified after they are
726
inserted, other than through the Inventory API.
717
729
def __contains__(self, file_id):
718
730
"""True if this entry contains a file with given id.
1021
1033
class Inventory(CommonInventory):
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
1034
"""Mutable dict based in-memory inventory.
1036
We never store the full path to a file, because renaming a directory
1037
implicitly moves all of its contents. This class internally maintains a
1032
1038
lookup tree that allows the children under a directory to be
1033
1039
returned quickly.
1035
InventoryEntry objects must not be modified after they are
1036
inserted, other than through the Inventory API.
1038
1041
>>> inv = Inventory()
1039
1042
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
1040
1043
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None, revision=None)
1041
1044
>>> inv['123-123'].name
1044
May be treated as an iterator or set to look up file ids:
1047
Id's may be looked up from paths:
1046
>>> bool(inv.path2id('hello.c'))
1049
>>> inv.path2id('hello.c')
1048
1051
>>> '123-123' in inv
1051
May also look up by name:
1054
There are iterators over the contents:
1053
>>> [x[0] for x in inv.iter_entries()]
1056
>>> [entry[0] for entry in inv.iter_entries()]
1054
1057
['', 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)
1062
1060
def __init__(self, root_id=ROOT_ID, revision_id=None):
1063
1061
"""Create or read an inventory.
1128
1126
# Check that the delta is legal. It would be nice if this could be
1129
1127
# done within the loops below but it's safer to validate the delta
1130
# before starting to mutate the inventory.
1131
unique_file_ids = set([f for _, _, f, _ in delta])
1132
if len(unique_file_ids) != len(delta):
1133
raise AssertionError("a file-id appears multiple times in %r"
1128
# before starting to mutate the inventory, as there isn't a rollback
1130
list(_check_delta_unique_ids(_check_delta_unique_new_paths(
1131
_check_delta_unique_old_paths(_check_delta_ids_match_entry(
1138
1135
# Remove all affected items which were in the original inventory,
1167
1164
replacement.revision = new_entry.revision
1168
1165
replacement.children = children.pop(replacement.file_id, {})
1169
1166
new_entry = replacement
1169
except AttributeError:
1170
raise errors.InconsistentDelta(new_path, new_entry.file_id,
1171
"Parent is not a directory.")
1171
1172
if len(children):
1172
1173
# Get the parent id that was deleted
1173
1174
parent_id, children = children.popitem()
1266
1267
parent = self._byid[entry.parent_id]
1267
1268
except KeyError:
1268
raise BzrError("parent_id {%s} not in inventory" %
1269
raise errors.InconsistentDelta("<unknown>", entry.parent_id,
1270
"Parent not in inventory.")
1271
1271
if entry.name in parent.children:
1272
raise BzrError("%s is already versioned" %
1273
osutils.pathjoin(self.id2path(parent.file_id),
1274
entry.name).encode('utf-8'))
1272
raise errors.InconsistentDelta(
1273
self.id2path(parent.children[entry.name].file_id),
1275
"Path already versioned")
1275
1276
parent.children[entry.name] = entry
1276
1277
return self._add_child(entry)
1619
1620
result.parent_id_basename_to_file_id = None
1620
1621
result.root_id = self.root_id
1621
1622
id_to_entry_delta = []
1623
# inventory_delta is only traversed once, so we just update the
1625
# Check for repeated file ids
1626
inventory_delta = _check_delta_unique_ids(inventory_delta)
1627
# Repeated old paths
1628
inventory_delta = _check_delta_unique_old_paths(inventory_delta)
1629
# Check for repeated new paths
1630
inventory_delta = _check_delta_unique_new_paths(inventory_delta)
1631
# Check for entries that don't match the fileid
1632
inventory_delta = _check_delta_ids_match_entry(inventory_delta)
1633
# All changed entries need to have their parents be directories.
1622
1635
for old_path, new_path, file_id, entry in inventory_delta:
1623
1636
# file id changes
1624
1637
if new_path == '':
1664
1678
result.id_to_entry.apply_delta(id_to_entry_delta)
1665
1679
if parent_id_basename_delta:
1666
1680
result.parent_id_basename_to_file_id.apply_delta(parent_id_basename_delta)
1681
parents.discard(None)
1682
for parent in parents:
1684
if result[parent].kind != 'directory':
1685
raise errors.InconsistentDelta(result.id2path(parent), parent,
1686
'Not a directory, but given children')
1687
except errors.NoSuchId:
1688
raise errors.InconsistentDelta("<unknown>", parent,
1689
"Parent is not present in resulting inventory.")
2072
2095
_NAME_RE = re.compile(r'^[^/\\]+$')
2074
2097
return bool(_NAME_RE.match(name))
2100
def _check_delta_unique_ids(delta):
2101
"""Decorate a delta and check that the file ids in it are unique.
2103
:return: A generator over delta.
2107
length = len(ids) + 1
2109
if len(ids) != length:
2110
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2115
def _check_delta_unique_new_paths(delta):
2116
"""Decorate a delta and check that the new paths in it are unique.
2118
:return: A generator over delta.
2122
length = len(paths) + 1
2124
if path is not None:
2126
if len(paths) != length:
2127
raise errors.InconsistentDelta(path, item[2], "repeated path")
2131
def _check_delta_unique_old_paths(delta):
2132
"""Decorate a delta and check that the old paths in it are unique.
2134
:return: A generator over delta.
2138
length = len(paths) + 1
2140
if path is not None:
2142
if len(paths) != length:
2143
raise errors.InconsistentDelta(path, item[2], "repeated path")
2147
def _check_delta_ids_match_entry(delta):
2148
"""Decorate a delta and check that the ids in it match the entry.file_id.
2150
:return: A generator over delta.
2154
if entry is not None:
2155
if entry.file_id != item[2]:
2156
raise errors.InconsistentDelta(item[0] or item[1], item[2],
2157
"mismatched id with %r" % entry)