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(), """
37
from warnings import warn
49
from bzrlib.errors import (
40
from bzrlib import errors, osutils
41
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
42
pathjoin, sha_strings)
43
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
44
BzrError, BzrCheckError, BinaryFile)
53
45
from bzrlib.trace import mutter
90
82
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
91
83
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
92
84
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
93
>>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
85
>>> shouldbe = {0: '', 1: 'src', 2: pathjoin('src','hello.c')}
94
86
>>> for ix, j in enumerate(i.iter_entries()):
95
87
... print (j[0] == shouldbe[ix], j[1])
97
(True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
89
(True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
98
90
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
99
91
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
92
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
93
Traceback (most recent call last):
95
BzrError: inventory already contains entry with id {2323}
100
96
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
101
97
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
102
98
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
193
189
if self.file_id in inv:
194
190
ie = inv[self.file_id]
195
191
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
192
if ie.revision in candidates:
200
193
# same revision value in two different inventories:
201
194
# correct possible inconsistencies:
254
247
def get_tar_item(self, root, dp, now, tree):
255
248
"""Get a tarfile item and a file stream for its content."""
256
item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
249
item = tarfile.TarInfo(pathjoin(root, dp))
257
250
# TODO: would be cool to actually set it to the timestamp of the
258
251
# revision it was last changed
289
282
assert isinstance(name, basestring), name
290
283
if '/' in name or '\\' in name:
291
raise errors.InvalidEntryName(name=name)
284
raise InvalidEntryName(name=name)
292
285
self.executable = False
293
286
self.revision = None
294
287
self.text_sha1 = None
295
288
self.text_size = None
296
289
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
291
self.text_id = text_id
301
292
self.parent_id = parent_id
302
293
self.symlink_target = None
303
self.reference_revision = None
305
295
def kind_character(self):
306
296
"""Return a short kind indicator useful for appending to names."""
322
312
This is a template method - implement _put_on_disk in subclasses.
324
fullpath = osutils.pathjoin(dest, dp)
314
fullpath = pathjoin(dest, dp)
325
315
self._put_on_disk(fullpath, tree)
326
316
# mutter(" export {%s} kind %s to %s", self.file_id,
327
317
# self.kind, fullpath)
337
327
def versionable_kind(kind):
338
return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
328
return (kind in ('file', 'directory', 'symlink'))
340
330
def check(self, checker, rev_id, inv, tree):
341
331
"""Check this inventory entry is intact.
516
501
class RootEntry(InventoryEntry):
518
503
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
519
'text_id', 'parent_id', 'children', 'executable',
520
'revision', 'symlink_target', 'reference_revision']
504
'text_id', 'parent_id', 'children', 'executable',
505
'revision', 'symlink_target']
522
507
def _check(self, checker, rev_id, tree):
523
508
"""See InventoryEntry._check"""
529
514
self.parent_id = None
531
516
self.revision = None
532
symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
533
' Please use InventoryDirectory instead.',
534
DeprecationWarning, stacklevel=2)
517
warn('RootEntry is deprecated as of bzr 0.10. Please use '
518
'InventoryDirectory instead.',
519
DeprecationWarning, stacklevel=2)
536
521
def __eq__(self, other):
537
522
if not isinstance(other, RootEntry):
545
530
"""A directory in an inventory."""
547
532
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
548
'text_id', 'parent_id', 'children', 'executable',
549
'revision', 'symlink_target', 'reference_revision']
533
'text_id', 'parent_id', 'children', 'executable',
534
'revision', 'symlink_target']
551
536
def _check(self, checker, rev_id, tree):
552
537
"""See InventoryEntry._check"""
592
577
"""A file in an inventory."""
594
579
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
595
'text_id', 'parent_id', 'children', 'executable',
596
'revision', 'symlink_target', 'reference_revision']
580
'text_id', 'parent_id', 'children', 'executable',
581
'revision', 'symlink_target']
598
583
def _check(self, checker, tree_revision_id, tree):
599
584
"""See InventoryEntry._check"""
661
646
text_diff(to_label, to_text,
662
647
from_label, from_text, output_to)
663
except errors.BinaryFile:
665
650
label_pair = (to_label, from_label)
693
678
def _put_on_disk(self, fullpath, tree):
694
679
"""See InventoryEntry._put_on_disk."""
695
osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
680
pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
696
681
if tree.is_executable(self.file_id):
697
682
os.chmod(fullpath, 0755)
740
725
"""A file in an inventory."""
742
727
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
743
'text_id', 'parent_id', 'children', 'executable',
744
'revision', 'symlink_target', 'reference_revision']
728
'text_id', 'parent_id', 'children', 'executable',
729
'revision', 'symlink_target']
746
731
def _check(self, checker, rev_id, tree):
747
732
"""See InventoryEntry._check"""
828
813
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
816
class Inventory(object):
859
817
"""Inventory of versioned files in a tree.
892
850
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
893
851
>>> 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
852
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
899
854
def __init__(self, root_id=ROOT_ID, revision_id=None):
906
861
The inventory is created with a default root directory, with
864
# We are letting Branch.create() create a unique inventory
865
# root id. Rather than generating a random one here.
867
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
909
868
if root_id is not None:
910
assert root_id.__class__ == str
911
self._set_root(InventoryDirectory(root_id, u'', None))
869
self._set_root(InventoryDirectory(root_id, '', None))
873
# FIXME: this isn't ever used, changing it to self.revision may break
874
# things. TODO make everything use self.revision_id
915
875
self.revision_id = revision_id
917
877
def _set_root(self, ie):
938
898
def iter_entries(self, from_dir=None):
939
899
"""Return (path, entry) pairs, in order by name."""
940
900
if from_dir is None:
941
if self.root is None:
943
902
from_dir = self.root
944
903
yield '', self.root
945
904
elif isinstance(from_dir, basestring):
979
938
# if we finished all children, pop it off the stack
982
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
941
def iter_entries_by_dir(self, from_dir=None):
983
942
"""Iterate over the entries in a directory first order.
985
944
This returns all entries for a directory before returning
990
949
: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
951
# TODO? Perhaps this should return the from_dir so that the root is
996
952
# yielded? or maybe an option?
997
953
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
955
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
957
elif isinstance(from_dir, basestring):
1011
958
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
960
stack = [(u'', from_dir)]
1039
967
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
969
yield child_relpath, child_ie
1045
971
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))
972
child_dirs.append((child_relpath+'/', child_ie))
1048
973
stack.extend(reversed(child_dirs))
1050
975
def entries(self):
1057
982
kids = dir_ie.children.items()
1059
984
for name, ie in kids:
1060
child_path = osutils.pathjoin(dir_path, name)
985
child_path = pathjoin(dir_path, name)
1061
986
accum.append((child_path, ie))
1062
987
if ie.kind == 'directory':
1063
988
descend(ie, child_path)
1078
1003
for name, child_ie in kids:
1079
child_path = osutils.pathjoin(parent_path, name)
1004
child_path = pathjoin(parent_path, name)
1080
1005
descend(child_ie, child_path)
1081
1006
descend(self.root, u'')
1104
1028
>>> inv['123123'].name
1107
file_id = osutils.safe_file_id(file_id)
1109
1032
return self._byid[file_id]
1110
1033
except KeyError:
1111
# really we're passing an inventory, not a tree...
1112
raise errors.NoSuchId(self, file_id)
1035
raise BzrError("can't look up file_id None")
1037
raise BzrError("file_id {%s} not in inventory" % file_id)
1114
1039
def get_file_kind(self, file_id):
1115
file_id = osutils.safe_file_id(file_id)
1116
1040
return self._byid[file_id].kind
1118
1042
def get_child(self, parent_id, filename):
1119
parent_id = osutils.safe_file_id(parent_id)
1120
1043
return self[parent_id].children.get(filename)
1122
def _add_child(self, entry):
1123
"""Add an entry to the inventory, without adding it to its parent"""
1124
if entry.file_id in self._byid:
1125
raise BzrError("inventory already contains entry with id {%s}" %
1127
self._byid[entry.file_id] = entry
1128
for child in getattr(entry, 'children', {}).itervalues():
1129
self._add_child(child)
1132
1045
def add(self, entry):
1133
1046
"""Add entry to inventory.
1138
1051
Returns the new entry object.
1140
1053
if entry.file_id in self._byid:
1141
raise errors.DuplicateFileId(entry.file_id,
1142
self._byid[entry.file_id])
1054
raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1144
1056
if entry.parent_id is None:
1145
1057
assert self.root is None and len(self._byid) == 0
1149
parent = self._byid[entry.parent_id]
1151
raise BzrError("parent_id {%s} not in inventory" %
1154
if entry.name in parent.children:
1155
raise BzrError("%s is already versioned" %
1156
osutils.pathjoin(self.id2path(parent.file_id),
1157
entry.name).encode('utf-8'))
1158
parent.children[entry.name] = entry
1159
return self._add_child(entry)
1058
self._set_root(entry)
1060
if entry.parent_id == ROOT_ID:
1061
assert self.root is not None, self
1062
entry.parent_id = self.root.file_id
1065
parent = self._byid[entry.parent_id]
1067
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
1069
if entry.name in parent.children:
1070
raise BzrError("%s is already versioned" %
1071
pathjoin(self.id2path(parent.file_id), entry.name))
1073
self._byid[entry.file_id] = entry
1074
parent.children[entry.name] = entry
1161
1077
def add_path(self, relpath, kind, file_id=None, parent_id=None):
1162
1078
"""Add entry from a path.
1170
1086
if len(parts) == 0:
1171
1087
if file_id is None:
1172
file_id = generate_ids.gen_root_id()
1174
file_id = osutils.safe_file_id(file_id)
1088
file_id = bzrlib.workingtree.gen_root_id()
1175
1089
self.root = InventoryDirectory(file_id, '', None)
1176
1090
self._byid = {self.root.file_id: self.root}
1179
1093
parent_path = parts[:-1]
1180
1094
parent_id = self.path2id(parent_path)
1181
1095
if parent_id is None:
1182
raise errors.NotVersionedError(path=parent_path)
1096
raise NotVersionedError(path=parent_path)
1183
1097
ie = make_entry(kind, parts[-1], parent_id, file_id)
1184
1098
return self.add(ie)
1235
1148
def _iter_file_id_parents(self, file_id):
1236
1149
"""Yield the parents of file_id up to the root."""
1237
file_id = osutils.safe_file_id(file_id)
1238
1150
while file_id is not None:
1240
1152
ie = self._byid[file_id]
1241
1153
except KeyError:
1242
raise errors.NoSuchId(tree=None, file_id=file_id)
1154
raise BzrError("file_id {%s} not found in inventory" % file_id)
1244
1156
file_id = ie.parent_id
1251
1163
is equal to the depth of the file in the tree, counting the
1252
1164
root directory as depth 1.
1254
file_id = osutils.safe_file_id(file_id)
1256
1167
for parent in self._iter_file_id_parents(file_id):
1257
1168
p.insert(0, parent.file_id)
1266
1177
>>> print i.id2path('foo-id')
1269
file_id = osutils.safe_file_id(file_id)
1270
1180
# get all names, skipping root
1271
1181
return '/'.join(reversed(
1272
1182
[parent.name for parent in
1284
1194
Returns None IFF the path is not found.
1286
if isinstance(name, basestring):
1287
name = osutils.splitpath(name)
1196
if isinstance(name, types.StringTypes):
1197
name = splitpath(name)
1289
1199
# mutter("lookup path %r" % name)
1291
1201
parent = self.root
1296
children = getattr(parent, 'children', None)
1297
if children is None:
1204
cie = parent.children[f]
1300
1205
assert cie.name == f
1301
1206
assert cie.parent_id == parent.file_id
1310
1215
return bool(self.path2id(names))
1312
1217
def has_id(self, file_id):
1313
file_id = osutils.safe_file_id(file_id)
1314
1218
return (file_id in self._byid)
1316
1220
def remove_recursive_id(self, file_id):
1329
1232
for file_id in reversed(to_delete):
1330
1233
ie = self[file_id]
1331
1234
del self._byid[file_id]
1332
if ie.parent_id is not None:
1333
del self[ie.parent_id].children[ie.name]
1235
if ie.parent_id is not None:
1236
del self[ie.parent_id].children[ie.name]
1337
1238
def rename(self, file_id, new_parent_id, new_name):
1338
1239
"""Move a file within the inventory.
1340
1241
This can change either the name, or the parent, or both.
1342
This does not move the working file.
1344
file_id = osutils.safe_file_id(file_id)
1243
This does not move the working file."""
1345
1244
if not is_valid_name(new_name):
1346
1245
raise BzrError("not an acceptable filename: %r" % new_name)
1365
1264
file_ie.name = new_name
1366
1265
file_ie.parent_id = new_parent_id
1368
def is_root(self, file_id):
1369
file_id = osutils.safe_file_id(file_id)
1370
return self.root is not None and file_id == self.root.file_id
1374
'directory': InventoryDirectory,
1375
'file': InventoryFile,
1376
'symlink': InventoryLink,
1377
'tree-reference': TreeReference
1380
1268
def make_entry(kind, name, parent_id, file_id=None):
1381
1269
"""Create an inventory entry.
1386
1274
:param file_id: the file_id to use. if None, one will be created.
1388
1276
if file_id is None:
1389
file_id = generate_ids.gen_file_id(name)
1391
file_id = osutils.safe_file_id(file_id)
1277
file_id = bzrlib.workingtree.gen_file_id(name)
1393
#------- This has been copied to bzrlib.dirstate.DirState.add, please
1394
# keep them synchronised.
1395
# we dont import normalized_filename directly because we want to be
1396
# able to change the implementation at runtime for tests.
1397
1279
norm_name, can_access = osutils.normalized_filename(name)
1398
1280
if norm_name != name:
1403
1285
# if the error was raised with the full path
1404
1286
raise errors.InvalidNormalization(name)
1407
factory = entry_factory[kind]
1288
if kind == 'directory':
1289
return InventoryDirectory(file_id, name, parent_id)
1290
elif kind == 'file':
1291
return InventoryFile(file_id, name, parent_id)
1292
elif kind == 'symlink':
1293
return InventoryLink(file_id, name, parent_id)
1409
1295
raise BzrError("unknown kind %r" % kind)
1410
return factory(file_id, name, parent_id)
1413
1298
_NAME_RE = None