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
27
27
# created, but it's not for now.
28
28
ROOT_ID = "TREE_ROOT"
34
from bzrlib.lazy_import import lazy_import
35
lazy_import(globals(), """
49
from bzrlib.errors import (
39
from bzrlib import errors, osutils
40
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
41
pathjoin, sha_strings)
42
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
43
BzrError, BzrCheckError, BinaryFile)
53
44
from bzrlib.trace import mutter
90
81
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
91
82
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
92
83
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
93
>>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
84
>>> shouldbe = {0: '', 1: 'src', 2: pathjoin('src','hello.c')}
94
85
>>> for ix, j in enumerate(i.iter_entries()):
95
86
... print (j[0] == shouldbe[ix], j[1])
97
(True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
88
(True, RootEntry('TREE_ROOT', u'', parent_id=None, revision=None))
98
89
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
99
90
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
91
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
92
Traceback (most recent call last):
94
BzrError: inventory already contains entry with id {2323}
100
95
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
101
96
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
102
97
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
181
176
:param entry_vf: The entry versioned file, if its already available.
183
178
def get_ancestors(weave, entry):
184
return set(weave.get_ancestry(entry.revision, topo_sorted=False))
179
return set(weave.get_ancestry(entry.revision))
185
180
# revision:ie mapping for each ie found in previous_inventories.
187
182
# revision:ie mapping with one revision for each head.
193
188
if self.file_id in inv:
194
189
ie = inv[self.file_id]
195
190
assert ie.file_id == self.file_id
196
if ie.kind != self.kind:
197
# Can't be a candidate if the kind has changed.
199
191
if ie.revision in candidates:
200
192
# same revision value in two different inventories:
201
193
# correct possible inconsistencies:
254
246
def get_tar_item(self, root, dp, now, tree):
255
247
"""Get a tarfile item and a file stream for its content."""
256
item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
248
item = tarfile.TarInfo(pathjoin(root, dp))
257
249
# TODO: would be cool to actually set it to the timestamp of the
258
250
# revision it was last changed
289
281
assert isinstance(name, basestring), name
290
282
if '/' in name or '\\' in name:
291
raise errors.InvalidEntryName(name=name)
283
raise InvalidEntryName(name=name)
292
284
self.executable = False
293
285
self.revision = None
294
286
self.text_sha1 = None
295
287
self.text_size = None
296
288
self.file_id = file_id
297
assert isinstance(file_id, (str, None.__class__)), \
298
'bad type %r for %r' % (type(file_id), file_id)
300
290
self.text_id = text_id
301
291
self.parent_id = parent_id
302
292
self.symlink_target = None
303
self.reference_revision = None
305
294
def kind_character(self):
306
295
"""Return a short kind indicator useful for appending to names."""
307
296
raise BzrError('unknown kind %r' % self.kind)
309
known_kinds = ('file', 'directory', 'symlink')
298
known_kinds = ('file', 'directory', 'symlink', 'root_directory')
311
300
def _put_in_tar(self, item, tree):
312
301
"""populate item for stashing in a tar, and return the content stream.
322
311
This is a template method - implement _put_on_disk in subclasses.
324
fullpath = osutils.pathjoin(dest, dp)
313
fullpath = pathjoin(dest, dp)
325
314
self._put_on_disk(fullpath, tree)
326
315
# mutter(" export {%s} kind %s to %s", self.file_id,
327
316
# self.kind, fullpath)
337
326
def versionable_kind(kind):
338
return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
327
return kind in ('file', 'directory', 'symlink')
340
329
def check(self, checker, rev_id, inv, tree):
341
330
"""Check this inventory entry is intact.
516
500
class RootEntry(InventoryEntry):
518
502
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
519
'text_id', 'parent_id', 'children', 'executable',
520
'revision', 'symlink_target', 'reference_revision']
503
'text_id', 'parent_id', 'children', 'executable',
504
'revision', 'symlink_target']
522
506
def _check(self, checker, rev_id, tree):
523
507
"""See InventoryEntry._check"""
525
509
def __init__(self, file_id):
526
510
self.file_id = file_id
527
511
self.children = {}
528
self.kind = 'directory'
512
self.kind = 'root_directory'
529
513
self.parent_id = None
531
515
self.revision = None
532
symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
533
' Please use InventoryDirectory instead.',
534
DeprecationWarning, stacklevel=2)
536
517
def __eq__(self, other):
537
518
if not isinstance(other, RootEntry):
545
526
"""A directory in an inventory."""
547
528
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
548
'text_id', 'parent_id', 'children', 'executable',
549
'revision', 'symlink_target', 'reference_revision']
529
'text_id', 'parent_id', 'children', 'executable',
530
'revision', 'symlink_target']
551
532
def _check(self, checker, rev_id, tree):
552
533
"""See InventoryEntry._check"""
592
573
"""A file in an inventory."""
594
575
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
595
'text_id', 'parent_id', 'children', 'executable',
596
'revision', 'symlink_target', 'reference_revision']
576
'text_id', 'parent_id', 'children', 'executable',
577
'revision', 'symlink_target']
598
579
def _check(self, checker, tree_revision_id, tree):
599
580
"""See InventoryEntry._check"""
610
591
if self.file_id not in checker.checked_weaves:
611
592
mutter('check weave {%s}', self.file_id)
612
w = tree._get_weave(self.file_id)
593
w = tree.get_weave(self.file_id)
613
594
# Not passing a progress bar, because it creates a new
614
595
# progress, which overwrites the current progress,
615
596
# and doesn't look nice
617
598
checker.checked_weaves[self.file_id] = True
619
w = tree._get_weave(self.file_id)
600
w = tree.get_weave(self.file_id)
621
602
mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
622
603
checker.checked_text_cnt += 1
661
642
text_diff(to_label, to_text,
662
643
from_label, from_text, output_to)
663
except errors.BinaryFile:
665
646
label_pair = (to_label, from_label)
693
674
def _put_on_disk(self, fullpath, tree):
694
675
"""See InventoryEntry._put_on_disk."""
695
osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
676
pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
696
677
if tree.is_executable(self.file_id):
697
678
os.chmod(fullpath, 0755)
740
721
"""A file in an inventory."""
742
723
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
743
'text_id', 'parent_id', 'children', 'executable',
744
'revision', 'symlink_target', 'reference_revision']
724
'text_id', 'parent_id', 'children', 'executable',
725
'revision', 'symlink_target']
746
727
def _check(self, checker, rev_id, tree):
747
728
"""See InventoryEntry._check"""
828
809
self.file_id, file_parents, self.symlink_target)
831
class TreeReference(InventoryEntry):
833
kind = 'tree-reference'
835
def __init__(self, file_id, name, parent_id, revision=None,
836
reference_revision=None):
837
InventoryEntry.__init__(self, file_id, name, parent_id)
838
self.revision = revision
839
self.reference_revision = reference_revision
842
return TreeReference(self.file_id, self.name, self.parent_id,
843
self.revision, self.reference_revision)
845
def _snapshot_text(self, file_parents, work_tree, commit_builder):
846
commit_builder.modified_reference(self.file_id, file_parents)
848
def _read_tree_state(self, path, work_tree):
849
"""Populate fields in the inventory entry from the given tree.
851
self.reference_revision = work_tree.get_reference_revision(
854
def _forget_tree_state(self):
855
self.reference_revision = None
858
812
class Inventory(object):
859
813
"""Inventory of versioned files in a tree.
892
846
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
893
847
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
894
Traceback (most recent call last):
895
BzrError: parent_id {TREE_ROOT} not in inventory
896
>>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
897
848
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
899
850
def __init__(self, root_id=ROOT_ID, revision_id=None):
906
857
The inventory is created with a default root directory, with
909
if root_id is not None:
910
assert root_id.__class__ == str
911
self._set_root(InventoryDirectory(root_id, u'', None))
860
# We are letting Branch.create() create a unique inventory
861
# root id. Rather than generating a random one here.
863
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
864
self.root = RootEntry(root_id)
865
# FIXME: this isn't ever used, changing it to self.revision may break
866
# things. TODO make everything use self.revision_id
915
867
self.revision_id = revision_id
917
def _set_root(self, ie):
919
868
self._byid = {self.root.file_id: self.root}
938
887
def iter_entries(self, from_dir=None):
939
888
"""Return (path, entry) pairs, in order by name."""
940
889
if from_dir is None:
941
if self.root is None:
943
891
from_dir = self.root
944
892
yield '', self.root
945
893
elif isinstance(from_dir, basestring):
979
927
# if we finished all children, pop it off the stack
982
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
930
def iter_entries_by_dir(self, from_dir=None):
983
931
"""Iterate over the entries in a directory first order.
985
933
This returns all entries for a directory before returning
990
938
:return: This yields (path, entry) pairs
992
if specific_file_ids:
993
safe = osutils.safe_file_id
994
specific_file_ids = set(safe(fid) for fid in specific_file_ids)
995
940
# TODO? Perhaps this should return the from_dir so that the root is
996
941
# yielded? or maybe an option?
997
942
if from_dir is None:
998
if self.root is None:
1000
# Optimize a common case
1001
if specific_file_ids is not None and len(specific_file_ids) == 1:
1002
file_id = list(specific_file_ids)[0]
1004
yield self.id2path(file_id), self[file_id]
1006
944
from_dir = self.root
1007
if (specific_file_ids is None or
1008
self.root.file_id in specific_file_ids):
1009
yield u'', self.root
1010
946
elif isinstance(from_dir, basestring):
1011
947
from_dir = self._byid[from_dir]
1013
if specific_file_ids is not None:
1014
# TODO: jam 20070302 This could really be done as a loop rather
1015
# than a bunch of recursive calls.
1018
def add_ancestors(file_id):
1019
if file_id not in byid:
1021
parent_id = byid[file_id].parent_id
1022
if parent_id is None:
1024
if parent_id not in parents:
1025
parents.add(parent_id)
1026
add_ancestors(parent_id)
1027
for file_id in specific_file_ids:
1028
add_ancestors(file_id)
1032
949
stack = [(u'', from_dir)]
1039
956
child_relpath = cur_relpath + child_name
1041
if (specific_file_ids is None or
1042
child_ie.file_id in specific_file_ids):
1043
yield child_relpath, child_ie
958
yield child_relpath, child_ie
1045
960
if child_ie.kind == 'directory':
1046
if parents is None or child_ie.file_id in parents:
1047
child_dirs.append((child_relpath+'/', child_ie))
961
child_dirs.append((child_relpath+'/', child_ie))
1048
962
stack.extend(reversed(child_dirs))
1050
def make_entry(self, kind, name, parent_id, file_id=None):
1051
"""Simple thunk to bzrlib.inventory.make_entry."""
1052
return make_entry(kind, name, parent_id, file_id)
1054
964
def entries(self):
1055
965
"""Return list of (path, ie) for all entries except the root.
1061
971
kids = dir_ie.children.items()
1063
973
for name, ie in kids:
1064
child_path = osutils.pathjoin(dir_path, name)
974
child_path = pathjoin(dir_path, name)
1065
975
accum.append((child_path, ie))
1066
976
if ie.kind == 'directory':
1067
977
descend(ie, child_path)
1082
992
for name, child_ie in kids:
1083
child_path = osutils.pathjoin(parent_path, name)
993
child_path = pathjoin(parent_path, name)
1084
994
descend(child_ie, child_path)
1085
995
descend(self.root, u'')
1096
1006
>>> '456' in inv
1099
file_id = osutils.safe_file_id(file_id)
1100
return (file_id in self._byid)
1009
return file_id in self._byid
1102
1011
def __getitem__(self, file_id):
1103
1012
"""Return the entry for given file_id.
1108
1017
>>> inv['123123'].name
1111
file_id = osutils.safe_file_id(file_id)
1113
1021
return self._byid[file_id]
1114
1022
except KeyError:
1115
# really we're passing an inventory, not a tree...
1116
raise errors.NoSuchId(self, file_id)
1024
raise BzrError("can't look up file_id None")
1026
raise BzrError("file_id {%s} not in inventory" % file_id)
1118
1028
def get_file_kind(self, file_id):
1119
file_id = osutils.safe_file_id(file_id)
1120
1029
return self._byid[file_id].kind
1122
1031
def get_child(self, parent_id, filename):
1123
parent_id = osutils.safe_file_id(parent_id)
1124
1032
return self[parent_id].children.get(filename)
1126
def _add_child(self, entry):
1127
"""Add an entry to the inventory, without adding it to its parent"""
1128
if entry.file_id in self._byid:
1129
raise BzrError("inventory already contains entry with id {%s}" %
1131
self._byid[entry.file_id] = entry
1132
for child in getattr(entry, 'children', {}).itervalues():
1133
self._add_child(child)
1136
1034
def add(self, entry):
1137
1035
"""Add entry to inventory.
1142
1040
Returns the new entry object.
1144
1042
if entry.file_id in self._byid:
1145
raise errors.DuplicateFileId(entry.file_id,
1146
self._byid[entry.file_id])
1148
if entry.parent_id is None:
1149
assert self.root is None and len(self._byid) == 0
1153
parent = self._byid[entry.parent_id]
1155
raise BzrError("parent_id {%s} not in inventory" %
1158
if entry.name in parent.children:
1159
raise BzrError("%s is already versioned" %
1160
osutils.pathjoin(self.id2path(parent.file_id),
1161
entry.name).encode('utf-8'))
1162
parent.children[entry.name] = entry
1163
return self._add_child(entry)
1043
raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1045
if entry.parent_id == ROOT_ID or entry.parent_id is None:
1046
entry.parent_id = self.root.file_id
1049
parent = self._byid[entry.parent_id]
1051
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
1053
if entry.name in parent.children:
1054
raise BzrError("%s is already versioned" %
1055
pathjoin(self.id2path(parent.file_id), entry.name))
1057
self._byid[entry.file_id] = entry
1058
parent.children[entry.name] = entry
1165
1061
def add_path(self, relpath, kind, file_id=None, parent_id=None):
1166
1062
"""Add entry from a path.
1174
1070
if len(parts) == 0:
1175
1071
if file_id is None:
1176
file_id = generate_ids.gen_root_id()
1178
file_id = osutils.safe_file_id(file_id)
1179
self.root = InventoryDirectory(file_id, '', None)
1072
file_id = bzrlib.workingtree.gen_root_id()
1073
self.root = RootEntry(file_id)
1180
1074
self._byid = {self.root.file_id: self.root}
1183
1077
parent_path = parts[:-1]
1184
1078
parent_id = self.path2id(parent_path)
1185
1079
if parent_id is None:
1186
raise errors.NotVersionedError(path=parent_path)
1080
raise NotVersionedError(path=parent_path)
1187
1081
ie = make_entry(kind, parts[-1], parent_id, file_id)
1188
1082
return self.add(ie)
1239
1132
def _iter_file_id_parents(self, file_id):
1240
1133
"""Yield the parents of file_id up to the root."""
1241
file_id = osutils.safe_file_id(file_id)
1242
1134
while file_id is not None:
1244
1136
ie = self._byid[file_id]
1245
1137
except KeyError:
1246
raise errors.NoSuchId(tree=None, file_id=file_id)
1138
raise BzrError("file_id {%s} not found in inventory" % file_id)
1248
1140
file_id = ie.parent_id
1255
1147
is equal to the depth of the file in the tree, counting the
1256
1148
root directory as depth 1.
1258
file_id = osutils.safe_file_id(file_id)
1260
1151
for parent in self._iter_file_id_parents(file_id):
1261
1152
p.insert(0, parent.file_id)
1270
1161
>>> print i.id2path('foo-id')
1273
file_id = osutils.safe_file_id(file_id)
1274
1164
# get all names, skipping root
1275
1165
return '/'.join(reversed(
1276
1166
[parent.name for parent in
1288
1178
Returns None IFF the path is not found.
1290
if isinstance(name, basestring):
1291
name = osutils.splitpath(name)
1180
if isinstance(name, types.StringTypes):
1181
name = splitpath(name)
1293
1183
# mutter("lookup path %r" % name)
1295
1185
parent = self.root
1300
children = getattr(parent, 'children', None)
1301
if children is None:
1188
cie = parent.children[f]
1304
1189
assert cie.name == f
1305
1190
assert cie.parent_id == parent.file_id
1314
1199
return bool(self.path2id(names))
1316
1201
def has_id(self, file_id):
1317
file_id = osutils.safe_file_id(file_id)
1318
return (file_id in self._byid)
1320
def remove_recursive_id(self, file_id):
1321
"""Remove file_id, and children, from the inventory.
1323
:param file_id: A file_id to remove.
1325
file_id = osutils.safe_file_id(file_id)
1326
to_find_delete = [self._byid[file_id]]
1328
while to_find_delete:
1329
ie = to_find_delete.pop()
1330
to_delete.append(ie.file_id)
1331
if ie.kind == 'directory':
1332
to_find_delete.extend(ie.children.values())
1333
for file_id in reversed(to_delete):
1335
del self._byid[file_id]
1336
if ie.parent_id is not None:
1337
del self[ie.parent_id].children[ie.name]
1202
return self._byid.has_key(file_id)
1341
1204
def rename(self, file_id, new_parent_id, new_name):
1342
1205
"""Move a file within the inventory.
1344
1207
This can change either the name, or the parent, or both.
1346
This does not move the working file.
1348
file_id = osutils.safe_file_id(file_id)
1209
This does not move the working file."""
1349
1210
if not is_valid_name(new_name):
1350
1211
raise BzrError("not an acceptable filename: %r" % new_name)
1369
1230
file_ie.name = new_name
1370
1231
file_ie.parent_id = new_parent_id
1372
def is_root(self, file_id):
1373
file_id = osutils.safe_file_id(file_id)
1374
return self.root is not None and file_id == self.root.file_id
1378
'directory': InventoryDirectory,
1379
'file': InventoryFile,
1380
'symlink': InventoryLink,
1381
'tree-reference': TreeReference
1384
1234
def make_entry(kind, name, parent_id, file_id=None):
1385
1235
"""Create an inventory entry.
1390
1240
:param file_id: the file_id to use. if None, one will be created.
1392
1242
if file_id is None:
1393
file_id = generate_ids.gen_file_id(name)
1395
file_id = osutils.safe_file_id(file_id)
1243
file_id = bzrlib.workingtree.gen_file_id(name)
1397
#------- This has been copied to bzrlib.dirstate.DirState.add, please
1398
# keep them synchronised.
1399
# we dont import normalized_filename directly because we want to be
1400
# able to change the implementation at runtime for tests.
1401
1245
norm_name, can_access = osutils.normalized_filename(name)
1402
1246
if norm_name != name:
1407
1251
# if the error was raised with the full path
1408
1252
raise errors.InvalidNormalization(name)
1411
factory = entry_factory[kind]
1254
if kind == 'directory':
1255
return InventoryDirectory(file_id, name, parent_id)
1256
elif kind == 'file':
1257
return InventoryFile(file_id, name, parent_id)
1258
elif kind == 'symlink':
1259
return InventoryLink(file_id, name, parent_id)
1413
1261
raise BzrError("unknown kind %r" % kind)
1414
return factory(file_id, name, parent_id)
1417
1264
_NAME_RE = None