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(), """
39
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
40
pathjoin, sha_strings)
41
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
42
BzrError, BzrCheckError, BinaryFile)
49
from bzrlib.errors import (
43
53
from bzrlib.trace import mutter
79
89
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
80
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
90
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
81
91
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
82
InventoryFile('2323', 'hello.c', parent_id='123')
83
>>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
92
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
93
>>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
84
94
>>> for ix, j in enumerate(i.iter_entries()):
85
95
... print (j[0] == shouldbe[ix], j[1])
87
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
88
(True, InventoryFile('2323', 'hello.c', parent_id='123'))
89
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
90
Traceback (most recent call last):
92
BzrError: inventory already contains entry with id {2323}
97
(True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
98
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
99
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
93
100
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
94
InventoryFile('2324', 'bye.c', parent_id='123')
101
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
95
102
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
96
InventoryDirectory('2325', 'wibble', parent_id='123')
103
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
97
104
>>> i.path2id('src/wibble')
101
108
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
102
InventoryFile('2326', 'wibble.c', parent_id='2325')
109
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
104
InventoryFile('2326', 'wibble.c', parent_id='2325')
111
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
105
112
>>> for path, entry in i.iter_entries():
107
114
... assert i.path2id(path)
123
131
RENAMED = 'renamed'
124
132
MODIFIED_AND_RENAMED = 'modified and renamed'
126
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
127
'text_id', 'parent_id', 'children', 'executable',
130
def _add_text_to_weave(self, new_lines, parents, weave_store, transaction):
131
versionedfile = weave_store.get_weave_or_empty(self.file_id,
133
versionedfile.add_lines(self.revision, parents, new_lines)
134
versionedfile.clear_cache()
136
136
def detect_changes(self, old_entry):
137
137
"""Return a (text_modified, meta_modified) from this to old_entry.
286
286
assert isinstance(name, basestring), name
287
287
if '/' in name or '\\' in name:
288
raise InvalidEntryName(name=name)
288
raise errors.InvalidEntryName(name=name)
289
289
self.executable = False
290
290
self.revision = None
291
291
self.text_sha1 = None
292
292
self.text_size = None
293
293
self.file_id = file_id
294
assert isinstance(file_id, (str, None.__class__)), \
295
'bad type %r for %r' % (type(file_id), file_id)
295
297
self.text_id = text_id
296
298
self.parent_id = parent_id
297
299
self.symlink_target = None
300
self.reference_revision = None
299
302
def kind_character(self):
300
303
"""Return a short kind indicator useful for appending to names."""
301
304
raise BzrError('unknown kind %r' % self.kind)
303
known_kinds = ('file', 'directory', 'symlink', 'root_directory')
306
known_kinds = ('file', 'directory', 'symlink')
305
308
def _put_in_tar(self, item, tree):
306
309
"""populate item for stashing in a tar, and return the content stream.
316
319
This is a template method - implement _put_on_disk in subclasses.
318
fullpath = pathjoin(dest, dp)
321
fullpath = osutils.pathjoin(dest, dp)
319
322
self._put_on_disk(fullpath, tree)
320
mutter(" export {%s} kind %s to %s", self.file_id,
323
# mutter(" export {%s} kind %s to %s", self.file_id,
324
# self.kind, fullpath)
323
326
def _put_on_disk(self, fullpath, tree):
324
327
"""Put this entry onto disk at fullpath, from tree tree."""
325
328
raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
327
330
def sorted_children(self):
328
l = self.children.items()
331
return sorted(self.children.items())
333
334
def versionable_kind(kind):
334
return kind in ('file', 'directory', 'symlink')
335
return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
336
337
def check(self, checker, rev_id, inv, tree):
337
338
"""Check this inventory entry is intact.
403
406
return 'unchanged'
405
408
def __repr__(self):
406
return ("%s(%r, %r, parent_id=%r)"
409
return ("%s(%r, %r, parent_id=%r, revision=%r)"
407
410
% (self.__class__.__name__,
412
416
def snapshot(self, revision, path, previous_entries,
413
work_tree, weave_store, transaction):
417
work_tree, commit_builder):
414
418
"""Make a snapshot of this entry which may or may not have changed.
416
420
This means that all its fields are populated, that it has its
417
421
text stored in the text store or weave.
419
mutter('new parents of %s are %r', path, previous_entries)
423
# mutter('new parents of %s are %r', path, previous_entries)
420
424
self._read_tree_state(path, work_tree)
425
# TODO: Where should we determine whether to reuse a
426
# previous revision id or create a new revision? 20060606
421
427
if len(previous_entries) == 1:
422
428
# cannot be unchanged unless there is only one parent file rev.
423
429
parent_ie = previous_entries.values()[0]
424
430
if self._unchanged(parent_ie):
425
mutter("found unchanged entry")
431
# mutter("found unchanged entry")
426
432
self.revision = parent_ie.revision
427
433
return "unchanged"
428
434
return self._snapshot_into_revision(revision, previous_entries,
429
work_tree, weave_store, transaction)
435
work_tree, commit_builder)
431
437
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
432
weave_store, transaction):
433
439
"""Record this revision unconditionally into a store.
435
441
The entry's last-changed revision property (`revision`) is updated to
440
446
:returns: String description of the commit (e.g. "merged", "modified"), etc.
442
mutter('new revision {%s} for {%s}', revision, self.file_id)
448
# mutter('new revision {%s} for {%s}', revision, self.file_id)
443
449
self.revision = revision
444
self._snapshot_text(previous_entries, work_tree, weave_store,
450
self._snapshot_text(previous_entries, work_tree, commit_builder)
447
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
452
def _snapshot_text(self, file_parents, work_tree, commit_builder):
448
453
"""Record the 'text' of this entry, whatever form that takes.
450
455
This default implementation simply adds an empty text.
452
mutter('storing file {%s} in revision {%s}',
453
self.file_id, self.revision)
454
self._add_text_to_weave([], file_parents.keys(), weave_store, transaction)
457
raise NotImplementedError(self._snapshot_text)
456
459
def __eq__(self, other):
457
460
if not isinstance(other, InventoryEntry):
507
511
class RootEntry(InventoryEntry):
513
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
514
'text_id', 'parent_id', 'children', 'executable',
515
'revision', 'symlink_target', 'reference_revision']
509
517
def _check(self, checker, rev_id, tree):
510
518
"""See InventoryEntry._check"""
512
520
def __init__(self, file_id):
513
521
self.file_id = file_id
514
522
self.children = {}
515
self.kind = 'root_directory'
523
self.kind = 'directory'
516
524
self.parent_id = None
527
symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
528
' Please use InventoryDirectory instead.',
529
DeprecationWarning, stacklevel=2)
519
531
def __eq__(self, other):
520
532
if not isinstance(other, RootEntry):
527
539
class InventoryDirectory(InventoryEntry):
528
540
"""A directory in an inventory."""
542
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
543
'text_id', 'parent_id', 'children', 'executable',
544
'revision', 'symlink_target', 'reference_revision']
530
546
def _check(self, checker, rev_id, tree):
531
547
"""See InventoryEntry._check"""
532
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
548
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
533
549
raise BzrCheckError('directory {%s} has text in revision {%s}'
534
550
% (self.file_id, rev_id))
562
578
"""See InventoryEntry._put_on_disk."""
563
579
os.mkdir(fullpath)
581
def _snapshot_text(self, file_parents, work_tree, commit_builder):
582
"""See InventoryEntry._snapshot_text."""
583
commit_builder.modified_directory(self.file_id, file_parents)
566
586
class InventoryFile(InventoryEntry):
567
587
"""A file in an inventory."""
589
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
590
'text_id', 'parent_id', 'children', 'executable',
591
'revision', 'symlink_target', 'reference_revision']
569
593
def _check(self, checker, tree_revision_id, tree):
570
594
"""See InventoryEntry._check"""
571
595
t = (self.file_id, self.revision)
611
635
def detect_changes(self, old_entry):
612
636
"""See InventoryEntry.detect_changes."""
613
assert self.text_sha1 != None
614
assert old_entry.text_sha1 != None
637
assert self.text_sha1 is not None
638
assert old_entry.text_sha1 is not None
615
639
text_modified = (self.text_sha1 != old_entry.text_sha1)
616
640
meta_modified = (self.executable != old_entry.executable)
617
641
return text_modified, meta_modified
664
688
def _put_on_disk(self, fullpath, tree):
665
689
"""See InventoryEntry._put_on_disk."""
666
pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
690
osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
667
691
if tree.is_executable(self.file_id):
668
692
os.chmod(fullpath, 0755)
670
694
def _read_tree_state(self, path, work_tree):
671
695
"""See InventoryEntry._read_tree_state."""
672
696
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
697
# FIXME: 20050930 probe for the text size when getting sha1
698
# in _read_tree_state
673
699
self.executable = work_tree.is_executable(self.file_id, path=path)
702
return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
703
% (self.__class__.__name__,
675
710
def _forget_tree_state(self):
676
711
self.text_sha1 = None
677
self.executable = None
679
def _snapshot_text(self, file_parents, work_tree, versionedfile_store, transaction):
713
def _snapshot_text(self, file_parents, work_tree, commit_builder):
680
714
"""See InventoryEntry._snapshot_text."""
681
mutter('storing text of file {%s} in revision {%s} into %r',
682
self.file_id, self.revision, versionedfile_store)
683
# special case to avoid diffing on renames or
685
if (len(file_parents) == 1
686
and self.text_sha1 == file_parents.values()[0].text_sha1
687
and self.text_size == file_parents.values()[0].text_size):
688
previous_ie = file_parents.values()[0]
689
versionedfile = versionedfile_store.get_weave(self.file_id, transaction)
690
versionedfile.clone_text(self.revision, previous_ie.revision, file_parents.keys())
692
new_lines = work_tree.get_file(self.file_id).readlines()
693
self._add_text_to_weave(new_lines, file_parents.keys(), versionedfile_store,
695
self.text_sha1 = sha_strings(new_lines)
696
self.text_size = sum(map(len, new_lines))
715
def get_content_byte_lines():
716
return work_tree.get_file(self.file_id).readlines()
717
self.text_sha1, self.text_size = commit_builder.modified_file_text(
718
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
699
720
def _unchanged(self, previous_ie):
700
721
"""See InventoryEntry._unchanged."""
713
734
class InventoryLink(InventoryEntry):
714
735
"""A file in an inventory."""
716
__slots__ = ['symlink_target']
737
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
738
'text_id', 'parent_id', 'children', 'executable',
739
'revision', 'symlink_target', 'reference_revision']
718
741
def _check(self, checker, rev_id, tree):
719
742
"""See InventoryEntry._check"""
720
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
743
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
721
744
raise BzrCheckError('symlink {%s} has text in revision {%s}'
722
745
% (self.file_id, rev_id))
723
if self.symlink_target == None:
746
if self.symlink_target is None:
724
747
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
725
748
% (self.file_id, rev_id))
794
817
compatible = False
795
818
return compatible
820
def _snapshot_text(self, file_parents, work_tree, commit_builder):
821
"""See InventoryEntry._snapshot_text."""
822
commit_builder.modified_link(
823
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
798
853
class Inventory(object):
799
854
"""Inventory of versioned files in a tree.
828
883
May also look up by name:
830
885
>>> [x[0] for x in inv.iter_entries()]
832
887
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
833
888
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
834
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
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
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
836
894
def __init__(self, root_id=ROOT_ID, revision_id=None):
837
895
"""Create or read an inventory.
843
901
The inventory is created with a default root directory, with
846
# We are letting Branch.create() create a unique inventory
847
# root id. Rather than generating a random one here.
849
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
850
self.root = RootEntry(root_id)
904
if root_id is not None:
905
assert root_id.__class__ == str
906
self._set_root(InventoryDirectory(root_id, u'', None))
851
910
self.revision_id = revision_id
912
def _set_root(self, ie):
852
914
self._byid = {self.root.file_id: self.root}
856
917
# TODO: jam 20051218 Should copy also copy the revision_id?
857
other = Inventory(self.root.file_id)
918
entries = self.iter_entries()
919
other = Inventory(entries.next()[1].file_id)
858
920
# copy recursively so we know directories will be added before
859
921
# their children. There are more efficient ways than this...
860
for path, entry in self.iter_entries():
861
if entry == self.root:
922
for path, entry in entries():
863
923
other.add(entry.copy())
867
926
def __iter__(self):
868
927
return iter(self._byid)
871
929
def __len__(self):
872
930
"""Returns number of entries."""
873
931
return len(self._byid)
876
933
def iter_entries(self, from_dir=None):
877
934
"""Return (path, entry) pairs, in order by name."""
936
if self.root is None:
880
938
from_dir = self.root
881
940
elif isinstance(from_dir, basestring):
882
941
from_dir = self._byid[from_dir]
915
974
# if we finished all children, pop it off the stack
977
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
978
"""Iterate over the entries in a directory first order.
980
This returns all entries for a directory before returning
981
the entries for children of a directory. This is not
982
lexicographically sorted order, and is a hybrid between
983
depth-first and breadth-first.
985
: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
# TODO? Perhaps this should return the from_dir so that the root is
991
# yielded? or maybe an option?
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
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
elif isinstance(from_dir, basestring):
1006
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
stack = [(u'', from_dir)]
1029
cur_relpath, cur_dir = stack.pop()
1032
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
1034
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
1040
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))
1043
stack.extend(reversed(child_dirs))
918
1045
def entries(self):
919
1046
"""Return list of (path, ie) for all entries except the root.
947
1073
for name, child_ie in kids:
948
child_path = pathjoin(parent_path, name)
1074
child_path = osutils.pathjoin(parent_path, name)
949
1075
descend(child_ie, child_path)
950
1076
descend(self.root, u'')
955
1079
def __contains__(self, file_id):
956
1080
"""True if this entry contains a file with given id.
958
1082
>>> inv = Inventory()
959
1083
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
960
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
1084
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
961
1085
>>> '123' in inv
963
1087
>>> '456' in inv
966
return file_id in self._byid
1090
file_id = osutils.safe_file_id(file_id)
1091
return (file_id in self._byid)
969
1093
def __getitem__(self, file_id):
970
1094
"""Return the entry for given file_id.
972
1096
>>> inv = Inventory()
973
1097
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
974
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
1098
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
975
1099
>>> inv['123123'].name
1102
file_id = osutils.safe_file_id(file_id)
979
1104
return self._byid[file_id]
980
1105
except KeyError:
982
raise BzrError("can't look up file_id None")
984
raise BzrError("file_id {%s} not in inventory" % file_id)
1106
# really we're passing an inventory, not a tree...
1107
raise errors.NoSuchId(self, file_id)
987
1109
def get_file_kind(self, file_id):
1110
file_id = osutils.safe_file_id(file_id)
988
1111
return self._byid[file_id].kind
990
1113
def get_child(self, parent_id, filename):
1114
parent_id = osutils.safe_file_id(parent_id)
991
1115
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)
994
1127
def add(self, entry):
995
1128
"""Add entry to inventory.
1000
1133
Returns the new entry object.
1002
1135
if entry.file_id in self._byid:
1003
raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1005
if entry.parent_id == ROOT_ID or entry.parent_id is None:
1006
entry.parent_id = self.root.file_id
1009
parent = self._byid[entry.parent_id]
1011
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
1013
if parent.children.has_key(entry.name):
1014
raise BzrError("%s is already versioned" %
1015
pathjoin(self.id2path(parent.file_id), entry.name))
1017
self._byid[entry.file_id] = entry
1018
parent.children[entry.name] = entry
1136
raise errors.DuplicateFileId(entry.file_id,
1137
self._byid[entry.file_id])
1139
if entry.parent_id is None:
1140
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)
1022
1156
def add_path(self, relpath, kind, file_id=None, parent_id=None):
1023
1157
"""Add entry from a path.
1027
1161
Returns the new entry object."""
1029
parts = bzrlib.osutils.splitpath(relpath)
1163
parts = osutils.splitpath(relpath)
1031
1165
if len(parts) == 0:
1032
1166
if file_id is None:
1033
file_id = bzrlib.workingtree.gen_root_id()
1034
self.root = RootEntry(file_id)
1167
file_id = generate_ids.gen_root_id()
1169
file_id = osutils.safe_file_id(file_id)
1170
self.root = InventoryDirectory(file_id, '', None)
1035
1171
self._byid = {self.root.file_id: self.root}
1038
1174
parent_path = parts[:-1]
1039
1175
parent_id = self.path2id(parent_path)
1040
if parent_id == None:
1041
raise NotVersionedError(path=parent_path)
1176
if parent_id is None:
1177
raise errors.NotVersionedError(path=parent_path)
1042
1178
ie = make_entry(kind, parts[-1], parent_id, file_id)
1043
1179
return self.add(ie)
1074
1210
>>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1075
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1211
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1078
1214
>>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1079
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1215
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1083
1219
if not isinstance(other, Inventory):
1084
1220
return NotImplemented
1086
if len(self._byid) != len(other._byid):
1087
# shortcut: obviously not the same
1090
1222
return self._byid == other._byid
1093
1224
def __ne__(self, other):
1094
1225
return not self.__eq__(other)
1097
1227
def __hash__(self):
1098
1228
raise ValueError('not hashable')
1100
1230
def _iter_file_id_parents(self, file_id):
1101
1231
"""Yield the parents of file_id up to the root."""
1102
while file_id != None:
1232
file_id = osutils.safe_file_id(file_id)
1233
while file_id is not None:
1104
1235
ie = self._byid[file_id]
1105
1236
except KeyError:
1106
raise BzrError("file_id {%s} not found in inventory" % file_id)
1237
raise errors.NoSuchId(tree=None, file_id=file_id)
1108
1239
file_id = ie.parent_id
1164
1302
return parent.file_id
1167
1304
def has_filename(self, names):
1168
1305
return bool(self.path2id(names))
1171
1307
def has_id(self, file_id):
1172
return self._byid.has_key(file_id)
1308
file_id = osutils.safe_file_id(file_id)
1309
return (file_id in self._byid)
1311
def remove_recursive_id(self, file_id):
1312
"""Remove file_id, and children, from the inventory.
1314
:param file_id: A file_id to remove.
1316
file_id = osutils.safe_file_id(file_id)
1317
to_find_delete = [self._byid[file_id]]
1319
while to_find_delete:
1320
ie = to_find_delete.pop()
1321
to_delete.append(ie.file_id)
1322
if ie.kind == 'directory':
1323
to_find_delete.extend(ie.children.values())
1324
for file_id in reversed(to_delete):
1326
del self._byid[file_id]
1327
if ie.parent_id is not None:
1328
del self[ie.parent_id].children[ie.name]
1175
1330
def rename(self, file_id, new_parent_id, new_name):
1176
1331
"""Move a file within the inventory.
1178
1333
This can change either the name, or the parent, or both.
1180
This does not move the working file."""
1335
This does not move the working file.
1337
file_id = osutils.safe_file_id(file_id)
1181
1338
if not is_valid_name(new_name):
1182
1339
raise BzrError("not an acceptable filename: %r" % new_name)
1201
1358
file_ie.name = new_name
1202
1359
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
1205
1373
def make_entry(kind, name, parent_id, file_id=None):
1206
1374
"""Create an inventory entry.
1211
1379
:param file_id: the file_id to use. if None, one will be created.
1213
1381
if file_id is None:
1214
file_id = bzrlib.workingtree.gen_file_id(name)
1215
if kind == 'directory':
1216
return InventoryDirectory(file_id, name, parent_id)
1217
elif kind == 'file':
1218
return InventoryFile(file_id, name, parent_id)
1219
elif kind == 'symlink':
1220
return InventoryLink(file_id, name, parent_id)
1382
file_id = generate_ids.gen_file_id(name)
1384
file_id = osutils.safe_file_id(file_id)
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
norm_name, can_access = osutils.normalized_filename(name)
1391
if norm_name != name:
1395
# TODO: jam 20060701 This would probably be more useful
1396
# if the error was raised with the full path
1397
raise errors.InvalidNormalization(name)
1400
factory = entry_factory[kind]
1222
1402
raise BzrError("unknown kind %r" % kind)
1403
return factory(file_id, name, parent_id)
1226
1406
_NAME_RE = None
1228
1408
def is_valid_name(name):
1229
1409
global _NAME_RE
1230
if _NAME_RE == None:
1410
if _NAME_RE is None:
1231
1411
_NAME_RE = re.compile(r'^[^/\\]+$')
1233
1413
return bool(_NAME_RE.match(name))