1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
94
92
>>> for ix, j in enumerate(i.iter_entries()):
95
93
... print (j[0] == shouldbe[ix], j[1])
97
(True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
95
(True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
98
96
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
99
97
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
98
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
99
Traceback (most recent call last):
101
BzrError: inventory already contains entry with id {2323}
100
102
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
101
103
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
102
104
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
291
293
self.text_sha1 = None
292
294
self.text_size = None
293
295
self.file_id = file_id
294
assert isinstance(file_id, (str, None.__class__)), \
295
'bad type %r for %r' % (type(file_id), file_id)
297
297
self.text_id = text_id
298
298
self.parent_id = parent_id
299
299
self.symlink_target = None
300
self.reference_revision = None
302
301
def kind_character(self):
303
302
"""Return a short kind indicator useful for appending to names."""
334
333
def versionable_kind(kind):
335
return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
334
return (kind in ('file', 'directory', 'symlink'))
337
336
def check(self, checker, rev_id, inv, tree):
338
337
"""Check this inventory entry is intact.
511
507
class RootEntry(InventoryEntry):
513
509
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
514
'text_id', 'parent_id', 'children', 'executable',
515
'revision', 'symlink_target', 'reference_revision']
510
'text_id', 'parent_id', 'children', 'executable',
511
'revision', 'symlink_target']
517
513
def _check(self, checker, rev_id, tree):
518
514
"""See InventoryEntry._check"""
540
536
"""A directory in an inventory."""
542
538
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
543
'text_id', 'parent_id', 'children', 'executable',
544
'revision', 'symlink_target', 'reference_revision']
539
'text_id', 'parent_id', 'children', 'executable',
540
'revision', 'symlink_target']
546
542
def _check(self, checker, rev_id, tree):
547
543
"""See InventoryEntry._check"""
587
583
"""A file in an inventory."""
589
585
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
590
'text_id', 'parent_id', 'children', 'executable',
591
'revision', 'symlink_target', 'reference_revision']
586
'text_id', 'parent_id', 'children', 'executable',
587
'revision', 'symlink_target']
593
589
def _check(self, checker, tree_revision_id, tree):
594
590
"""See InventoryEntry._check"""
735
731
"""A file in an inventory."""
737
733
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
738
'text_id', 'parent_id', 'children', 'executable',
739
'revision', 'symlink_target', 'reference_revision']
734
'text_id', 'parent_id', 'children', 'executable',
735
'revision', 'symlink_target']
741
737
def _check(self, checker, rev_id, tree):
742
738
"""See InventoryEntry._check"""
823
819
self.file_id, file_parents, self.symlink_target)
826
class TreeReference(InventoryEntry):
828
kind = 'tree-reference'
830
def __init__(self, file_id, name, parent_id, revision=None,
831
reference_revision=None):
832
InventoryEntry.__init__(self, file_id, name, parent_id)
833
self.revision = revision
834
self.reference_revision = reference_revision
837
return TreeReference(self.file_id, self.name, self.parent_id,
838
self.revision, self.reference_revision)
840
def _snapshot_text(self, file_parents, work_tree, commit_builder):
841
commit_builder.modified_reference(self.file_id, file_parents)
843
def _read_tree_state(self, path, work_tree):
844
"""Populate fields in the inventory entry from the given tree.
846
self.reference_revision = work_tree.get_reference_revision(
849
def _forget_tree_state(self):
850
self.reference_revision = None
853
822
class Inventory(object):
854
823
"""Inventory of versioned files in a tree.
887
856
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
888
857
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
889
Traceback (most recent call last):
890
BzrError: parent_id {TREE_ROOT} not in inventory
891
>>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
892
858
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
894
860
def __init__(self, root_id=ROOT_ID, revision_id=None):
901
867
The inventory is created with a default root directory, with
870
# We are letting Branch.create() create a unique inventory
871
# root id. Rather than generating a random one here.
873
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
904
874
if root_id is not None:
905
assert root_id.__class__ == str
906
self._set_root(InventoryDirectory(root_id, u'', None))
875
self._set_root(InventoryDirectory(root_id, '', None))
879
# FIXME: this isn't ever used, changing it to self.revision may break
880
# things. TODO make everything use self.revision_id
910
881
self.revision_id = revision_id
912
883
def _set_root(self, ie):
933
904
def iter_entries(self, from_dir=None):
934
905
"""Return (path, entry) pairs, in order by name."""
935
906
if from_dir is None:
936
if self.root is None:
938
908
from_dir = self.root
939
909
yield '', self.root
940
910
elif isinstance(from_dir, basestring):
974
944
# if we finished all children, pop it off the stack
977
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
947
def iter_entries_by_dir(self, from_dir=None):
978
948
"""Iterate over the entries in a directory first order.
980
950
This returns all entries for a directory before returning
985
955
:return: This yields (path, entry) pairs
987
if specific_file_ids:
988
safe = osutils.safe_file_id
989
specific_file_ids = set(safe(fid) for fid in specific_file_ids)
990
957
# TODO? Perhaps this should return the from_dir so that the root is
991
958
# yielded? or maybe an option?
992
959
if from_dir is None:
993
if self.root is None:
995
# Optimize a common case
996
if specific_file_ids is not None and len(specific_file_ids) == 1:
997
file_id = list(specific_file_ids)[0]
999
yield self.id2path(file_id), self[file_id]
1001
961
from_dir = self.root
1002
if (specific_file_ids is None or
1003
self.root.file_id in specific_file_ids):
1004
yield u'', self.root
1005
963
elif isinstance(from_dir, basestring):
1006
964
from_dir = self._byid[from_dir]
1008
if specific_file_ids is not None:
1009
# TODO: jam 20070302 This could really be done as a loop rather
1010
# than a bunch of recursive calls.
1013
def add_ancestors(file_id):
1014
if file_id not in byid:
1016
parent_id = byid[file_id].parent_id
1017
if parent_id is None:
1019
if parent_id not in parents:
1020
parents.add(parent_id)
1021
add_ancestors(parent_id)
1022
for file_id in specific_file_ids:
1023
add_ancestors(file_id)
1027
966
stack = [(u'', from_dir)]
1034
973
child_relpath = cur_relpath + child_name
1036
if (specific_file_ids is None or
1037
child_ie.file_id in specific_file_ids):
1038
yield child_relpath, child_ie
975
yield child_relpath, child_ie
1040
977
if child_ie.kind == 'directory':
1041
if parents is None or child_ie.file_id in parents:
1042
child_dirs.append((child_relpath+'/', child_ie))
978
child_dirs.append((child_relpath+'/', child_ie))
1043
979
stack.extend(reversed(child_dirs))
1045
981
def entries(self):
1099
1034
>>> inv['123123'].name
1102
file_id = osutils.safe_file_id(file_id)
1104
1038
return self._byid[file_id]
1105
1039
except KeyError:
1106
# really we're passing an inventory, not a tree...
1107
raise errors.NoSuchId(self, file_id)
1041
raise BzrError("can't look up file_id None")
1043
raise BzrError("file_id {%s} not in inventory" % file_id)
1109
1045
def get_file_kind(self, file_id):
1110
file_id = osutils.safe_file_id(file_id)
1111
1046
return self._byid[file_id].kind
1113
1048
def get_child(self, parent_id, filename):
1114
parent_id = osutils.safe_file_id(parent_id)
1115
1049
return self[parent_id].children.get(filename)
1117
def _add_child(self, entry):
1118
"""Add an entry to the inventory, without adding it to its parent"""
1119
if entry.file_id in self._byid:
1120
raise BzrError("inventory already contains entry with id {%s}" %
1122
self._byid[entry.file_id] = entry
1123
for child in getattr(entry, 'children', {}).itervalues():
1124
self._add_child(child)
1127
1051
def add(self, entry):
1128
1052
"""Add entry to inventory.
1133
1057
Returns the new entry object.
1135
1059
if entry.file_id in self._byid:
1136
raise errors.DuplicateFileId(entry.file_id,
1137
self._byid[entry.file_id])
1060
raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1139
1062
if entry.parent_id is None:
1140
1063
assert self.root is None and len(self._byid) == 0
1144
parent = self._byid[entry.parent_id]
1146
raise BzrError("parent_id {%s} not in inventory" %
1149
if entry.name in parent.children:
1150
raise BzrError("%s is already versioned" %
1151
osutils.pathjoin(self.id2path(parent.file_id),
1153
parent.children[entry.name] = entry
1154
return self._add_child(entry)
1064
self._set_root(entry)
1066
if entry.parent_id == ROOT_ID:
1067
assert self.root is not None, self
1068
entry.parent_id = self.root.file_id
1071
parent = self._byid[entry.parent_id]
1073
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
1075
if entry.name in parent.children:
1076
raise BzrError("%s is already versioned" %
1077
osutils.pathjoin(self.id2path(parent.file_id), entry.name))
1079
self._byid[entry.file_id] = entry
1080
parent.children[entry.name] = entry
1156
1083
def add_path(self, relpath, kind, file_id=None, parent_id=None):
1157
1084
"""Add entry from a path.
1165
1092
if len(parts) == 0:
1166
1093
if file_id is None:
1167
file_id = generate_ids.gen_root_id()
1169
file_id = osutils.safe_file_id(file_id)
1094
file_id = bzrlib.workingtree.gen_root_id()
1170
1095
self.root = InventoryDirectory(file_id, '', None)
1171
1096
self._byid = {self.root.file_id: self.root}
1174
1099
parent_path = parts[:-1]
1175
1100
parent_id = self.path2id(parent_path)
1230
1154
def _iter_file_id_parents(self, file_id):
1231
1155
"""Yield the parents of file_id up to the root."""
1232
file_id = osutils.safe_file_id(file_id)
1233
1156
while file_id is not None:
1235
1158
ie = self._byid[file_id]
1236
1159
except KeyError:
1237
raise errors.NoSuchId(tree=None, file_id=file_id)
1160
raise BzrError("file_id {%s} not found in inventory" % file_id)
1239
1162
file_id = ie.parent_id
1246
1169
is equal to the depth of the file in the tree, counting the
1247
1170
root directory as depth 1.
1249
file_id = osutils.safe_file_id(file_id)
1251
1173
for parent in self._iter_file_id_parents(file_id):
1252
1174
p.insert(0, parent.file_id)
1261
1183
>>> print i.id2path('foo-id')
1264
file_id = osutils.safe_file_id(file_id)
1265
1186
# get all names, skipping root
1266
1187
return '/'.join(reversed(
1267
1188
[parent.name for parent in
1305
1221
return bool(self.path2id(names))
1307
1223
def has_id(self, file_id):
1308
file_id = osutils.safe_file_id(file_id)
1309
1224
return (file_id in self._byid)
1311
1226
def remove_recursive_id(self, file_id):
1324
1238
for file_id in reversed(to_delete):
1325
1239
ie = self[file_id]
1326
1240
del self._byid[file_id]
1327
if ie.parent_id is not None:
1328
del self[ie.parent_id].children[ie.name]
1241
if ie.parent_id is not None:
1242
del self[ie.parent_id].children[ie.name]
1330
1244
def rename(self, file_id, new_parent_id, new_name):
1331
1245
"""Move a file within the inventory.
1333
1247
This can change either the name, or the parent, or both.
1335
This does not move the working file.
1337
file_id = osutils.safe_file_id(file_id)
1249
This does not move the working file."""
1338
1250
if not is_valid_name(new_name):
1339
1251
raise BzrError("not an acceptable filename: %r" % new_name)
1358
1270
file_ie.name = new_name
1359
1271
file_ie.parent_id = new_parent_id
1361
def is_root(self, file_id):
1362
file_id = osutils.safe_file_id(file_id)
1363
return self.root is not None and file_id == self.root.file_id
1367
'directory': InventoryDirectory,
1368
'file': InventoryFile,
1369
'symlink': InventoryLink,
1370
'tree-reference': TreeReference
1373
1274
def make_entry(kind, name, parent_id, file_id=None):
1374
1275
"""Create an inventory entry.
1379
1280
:param file_id: the file_id to use. if None, one will be created.
1381
1282
if file_id is None:
1382
file_id = generate_ids.gen_file_id(name)
1384
file_id = osutils.safe_file_id(file_id)
1283
file_id = bzrlib.workingtree.gen_file_id(name)
1386
#------- This has been copied to bzrlib.dirstate.DirState.add, please
1387
# keep them synchronised.
1388
# we dont import normalized_filename directly because we want to be
1389
# able to change the implementation at runtime for tests.
1390
1285
norm_name, can_access = osutils.normalized_filename(name)
1391
1286
if norm_name != name:
1396
1291
# if the error was raised with the full path
1397
1292
raise errors.InvalidNormalization(name)
1400
factory = entry_factory[kind]
1294
if kind == 'directory':
1295
return InventoryDirectory(file_id, name, parent_id)
1296
elif kind == 'file':
1297
return InventoryFile(file_id, name, parent_id)
1298
elif kind == 'symlink':
1299
return InventoryLink(file_id, name, parent_id)
1402
1301
raise BzrError("unknown kind %r" % kind)
1403
return factory(file_id, name, parent_id)
1406
1304
_NAME_RE = None