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.osutils import (pumpfile, quotefn, splitpath, joinpath,
40
pathjoin, sha_strings)
41
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
42
BzrError, BzrCheckError, BinaryFile)
53
43
from bzrlib.trace import mutter
89
79
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
90
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
80
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
91
81
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
92
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
93
>>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
82
InventoryFile('2323', 'hello.c', parent_id='123')
83
>>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
94
84
>>> for ix, j in enumerate(i.iter_entries()):
95
85
... print (j[0] == shouldbe[ix], j[1])
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))
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}
100
93
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
101
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
94
InventoryFile('2324', 'bye.c', parent_id='123')
102
95
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
103
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
96
InventoryDirectory('2325', 'wibble', parent_id='123')
104
97
>>> i.path2id('src/wibble')
108
101
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
109
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
102
InventoryFile('2326', 'wibble.c', parent_id='2325')
111
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
104
InventoryFile('2326', 'wibble.c', parent_id='2325')
112
105
>>> for path, entry in i.iter_entries():
114
107
... assert i.path2id(path)
131
123
RENAMED = 'renamed'
132
124
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.
289
286
assert isinstance(name, basestring), name
290
287
if '/' in name or '\\' in name:
291
raise errors.InvalidEntryName(name=name)
288
raise InvalidEntryName(name=name)
292
289
self.executable = False
293
290
self.revision = None
294
291
self.text_sha1 = None
295
292
self.text_size = None
296
293
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
295
self.text_id = text_id
301
296
self.parent_id = parent_id
302
297
self.symlink_target = None
303
self.reference_revision = None
305
299
def kind_character(self):
306
300
"""Return a short kind indicator useful for appending to names."""
307
301
raise BzrError('unknown kind %r' % self.kind)
309
known_kinds = ('file', 'directory', 'symlink')
303
known_kinds = ('file', 'directory', 'symlink', 'root_directory')
311
305
def _put_in_tar(self, item, tree):
312
306
"""populate item for stashing in a tar, and return the content stream.
322
316
This is a template method - implement _put_on_disk in subclasses.
324
fullpath = osutils.pathjoin(dest, dp)
318
fullpath = pathjoin(dest, dp)
325
319
self._put_on_disk(fullpath, tree)
326
# mutter(" export {%s} kind %s to %s", self.file_id,
327
# self.kind, fullpath)
320
mutter(" export {%s} kind %s to %s", self.file_id,
329
323
def _put_on_disk(self, fullpath, tree):
330
324
"""Put this entry onto disk at fullpath, from tree tree."""
331
325
raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
333
327
def sorted_children(self):
334
return sorted(self.children.items())
328
l = self.children.items()
337
333
def versionable_kind(kind):
338
return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
334
return kind in ('file', 'directory', 'symlink')
340
336
def check(self, checker, rev_id, inv, tree):
341
337
"""Check this inventory entry is intact.
409
403
return 'unchanged'
411
405
def __repr__(self):
412
return ("%s(%r, %r, parent_id=%r, revision=%r)"
406
return ("%s(%r, %r, parent_id=%r)"
413
407
% (self.__class__.__name__,
419
412
def snapshot(self, revision, path, previous_entries,
420
work_tree, commit_builder):
413
work_tree, weave_store, transaction):
421
414
"""Make a snapshot of this entry which may or may not have changed.
423
416
This means that all its fields are populated, that it has its
424
417
text stored in the text store or weave.
426
# mutter('new parents of %s are %r', path, previous_entries)
419
mutter('new parents of %s are %r', path, previous_entries)
427
420
self._read_tree_state(path, work_tree)
428
# TODO: Where should we determine whether to reuse a
429
# previous revision id or create a new revision? 20060606
430
421
if len(previous_entries) == 1:
431
422
# cannot be unchanged unless there is only one parent file rev.
432
423
parent_ie = previous_entries.values()[0]
433
424
if self._unchanged(parent_ie):
434
# mutter("found unchanged entry")
425
mutter("found unchanged entry")
435
426
self.revision = parent_ie.revision
436
427
return "unchanged"
437
428
return self._snapshot_into_revision(revision, previous_entries,
438
work_tree, commit_builder)
429
work_tree, weave_store, transaction)
440
431
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
432
weave_store, transaction):
442
433
"""Record this revision unconditionally into a store.
444
435
The entry's last-changed revision property (`revision`) is updated to
449
440
:returns: String description of the commit (e.g. "merged", "modified"), etc.
451
# mutter('new revision {%s} for {%s}', revision, self.file_id)
442
mutter('new revision {%s} for {%s}', revision, self.file_id)
452
443
self.revision = revision
453
self._snapshot_text(previous_entries, work_tree, commit_builder)
444
self._snapshot_text(previous_entries, work_tree, weave_store,
455
def _snapshot_text(self, file_parents, work_tree, commit_builder):
447
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
456
448
"""Record the 'text' of this entry, whatever form that takes.
458
450
This default implementation simply adds an empty text.
460
raise NotImplementedError(self._snapshot_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)
462
456
def __eq__(self, other):
463
457
if not isinstance(other, InventoryEntry):
516
507
class RootEntry(InventoryEntry):
518
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
519
'text_id', 'parent_id', 'children', 'executable',
520
'revision', 'symlink_target', 'reference_revision']
522
509
def _check(self, checker, rev_id, tree):
523
510
"""See InventoryEntry._check"""
525
512
def __init__(self, file_id):
526
513
self.file_id = file_id
527
514
self.children = {}
528
self.kind = 'directory'
515
self.kind = 'root_directory'
529
516
self.parent_id = None
532
symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
533
' Please use InventoryDirectory instead.',
534
DeprecationWarning, stacklevel=2)
536
519
def __eq__(self, other):
537
520
if not isinstance(other, RootEntry):
544
527
class InventoryDirectory(InventoryEntry):
545
528
"""A directory in an inventory."""
547
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
548
'text_id', 'parent_id', 'children', 'executable',
549
'revision', 'symlink_target', 'reference_revision']
551
530
def _check(self, checker, rev_id, tree):
552
531
"""See InventoryEntry._check"""
553
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
532
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
554
533
raise BzrCheckError('directory {%s} has text in revision {%s}'
555
534
% (self.file_id, rev_id))
583
562
"""See InventoryEntry._put_on_disk."""
584
563
os.mkdir(fullpath)
586
def _snapshot_text(self, file_parents, work_tree, commit_builder):
587
"""See InventoryEntry._snapshot_text."""
588
commit_builder.modified_directory(self.file_id, file_parents)
591
566
class InventoryFile(InventoryEntry):
592
567
"""A file in an inventory."""
594
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
595
'text_id', 'parent_id', 'children', 'executable',
596
'revision', 'symlink_target', 'reference_revision']
598
569
def _check(self, checker, tree_revision_id, tree):
599
570
"""See InventoryEntry._check"""
600
571
t = (self.file_id, self.revision)
640
611
def detect_changes(self, old_entry):
641
612
"""See InventoryEntry.detect_changes."""
642
assert self.text_sha1 is not None
643
assert old_entry.text_sha1 is not None
613
assert self.text_sha1 != None
614
assert old_entry.text_sha1 != None
644
615
text_modified = (self.text_sha1 != old_entry.text_sha1)
645
616
meta_modified = (self.executable != old_entry.executable)
646
617
return text_modified, meta_modified
693
664
def _put_on_disk(self, fullpath, tree):
694
665
"""See InventoryEntry._put_on_disk."""
695
osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
666
pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
696
667
if tree.is_executable(self.file_id):
697
668
os.chmod(fullpath, 0755)
699
670
def _read_tree_state(self, path, work_tree):
700
671
"""See InventoryEntry._read_tree_state."""
701
672
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
702
# FIXME: 20050930 probe for the text size when getting sha1
703
# in _read_tree_state
704
673
self.executable = work_tree.is_executable(self.file_id, path=path)
707
return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
708
% (self.__class__.__name__,
715
675
def _forget_tree_state(self):
716
676
self.text_sha1 = None
677
self.executable = None
718
def _snapshot_text(self, file_parents, work_tree, commit_builder):
679
def _snapshot_text(self, file_parents, work_tree, versionedfile_store, transaction):
719
680
"""See InventoryEntry._snapshot_text."""
720
def get_content_byte_lines():
721
return work_tree.get_file(self.file_id).readlines()
722
self.text_sha1, self.text_size = commit_builder.modified_file_text(
723
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
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))
725
699
def _unchanged(self, previous_ie):
726
700
"""See InventoryEntry._unchanged."""
739
713
class InventoryLink(InventoryEntry):
740
714
"""A file in an inventory."""
742
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
743
'text_id', 'parent_id', 'children', 'executable',
744
'revision', 'symlink_target', 'reference_revision']
716
__slots__ = ['symlink_target']
746
718
def _check(self, checker, rev_id, tree):
747
719
"""See InventoryEntry._check"""
748
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
720
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
749
721
raise BzrCheckError('symlink {%s} has text in revision {%s}'
750
722
% (self.file_id, rev_id))
751
if self.symlink_target is None:
723
if self.symlink_target == None:
752
724
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
753
725
% (self.file_id, rev_id))
822
794
compatible = False
823
795
return compatible
825
def _snapshot_text(self, file_parents, work_tree, commit_builder):
826
"""See InventoryEntry._snapshot_text."""
827
commit_builder.modified_link(
828
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
798
class Inventory(object):
859
799
"""Inventory of versioned files in a tree.
888
828
May also look up by name:
890
830
>>> [x[0] for x in inv.iter_entries()]
892
832
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
893
833
>>> 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
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
834
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
899
836
def __init__(self, root_id=ROOT_ID, revision_id=None):
900
837
"""Create or read an inventory.
906
843
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))
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)
915
851
self.revision_id = revision_id
917
def _set_root(self, ie):
919
852
self._byid = {self.root.file_id: self.root}
922
856
# TODO: jam 20051218 Should copy also copy the revision_id?
923
entries = self.iter_entries()
924
other = Inventory(entries.next()[1].file_id)
857
other = Inventory(self.root.file_id)
925
858
# copy recursively so we know directories will be added before
926
859
# their children. There are more efficient ways than this...
927
for path, entry in entries():
860
for path, entry in self.iter_entries():
861
if entry == self.root:
928
863
other.add(entry.copy())
931
867
def __iter__(self):
932
868
return iter(self._byid)
934
871
def __len__(self):
935
872
"""Returns number of entries."""
936
873
return len(self._byid)
938
876
def iter_entries(self, from_dir=None):
939
877
"""Return (path, entry) pairs, in order by name."""
941
if self.root is None:
943
880
from_dir = self.root
945
881
elif isinstance(from_dir, basestring):
946
882
from_dir = self._byid[from_dir]
979
915
# if we finished all children, pop it off the stack
982
def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
983
"""Iterate over the entries in a directory first order.
985
This returns all entries for a directory before returning
986
the entries for children of a directory. This is not
987
lexicographically sorted order, and is a hybrid between
988
depth-first and breadth-first.
990
: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
# TODO? Perhaps this should return the from_dir so that the root is
996
# yielded? or maybe an option?
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
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
elif isinstance(from_dir, basestring):
1011
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
stack = [(u'', from_dir)]
1034
cur_relpath, cur_dir = stack.pop()
1037
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
1039
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
1045
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))
1048
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
918
def entries(self):
1055
919
"""Return list of (path, ie) for all entries except the root.
1082
947
for name, child_ie in kids:
1083
child_path = osutils.pathjoin(parent_path, name)
948
child_path = pathjoin(parent_path, name)
1084
949
descend(child_ie, child_path)
1085
950
descend(self.root, u'')
1088
955
def __contains__(self, file_id):
1089
956
"""True if this entry contains a file with given id.
1091
958
>>> inv = Inventory()
1092
959
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1093
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
960
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
1094
961
>>> '123' in inv
1096
963
>>> '456' in inv
1099
file_id = osutils.safe_file_id(file_id)
1100
return (file_id in self._byid)
966
return file_id in self._byid
1102
969
def __getitem__(self, file_id):
1103
970
"""Return the entry for given file_id.
1105
972
>>> inv = Inventory()
1106
973
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
1107
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
974
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
1108
975
>>> inv['123123'].name
1111
file_id = osutils.safe_file_id(file_id)
1113
979
return self._byid[file_id]
1114
980
except KeyError:
1115
# really we're passing an inventory, not a tree...
1116
raise errors.NoSuchId(self, file_id)
982
raise BzrError("can't look up file_id None")
984
raise BzrError("file_id {%s} not in inventory" % file_id)
1118
987
def get_file_kind(self, file_id):
1119
file_id = osutils.safe_file_id(file_id)
1120
988
return self._byid[file_id].kind
1122
990
def get_child(self, parent_id, filename):
1123
parent_id = osutils.safe_file_id(parent_id)
1124
991
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
994
def add(self, entry):
1137
995
"""Add entry to inventory.
1142
1000
Returns the new entry object.
1144
1002
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)
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
1165
1022
def add_path(self, relpath, kind, file_id=None, parent_id=None):
1166
1023
"""Add entry from a path.
1170
1027
Returns the new entry object."""
1172
parts = osutils.splitpath(relpath)
1029
parts = bzrlib.osutils.splitpath(relpath)
1174
1031
if len(parts) == 0:
1175
1032
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)
1033
file_id = bzrlib.workingtree.gen_root_id()
1034
self.root = RootEntry(file_id)
1180
1035
self._byid = {self.root.file_id: self.root}
1183
1038
parent_path = parts[:-1]
1184
1039
parent_id = self.path2id(parent_path)
1185
if parent_id is None:
1186
raise errors.NotVersionedError(path=parent_path)
1040
if parent_id == None:
1041
raise NotVersionedError(path=parent_path)
1187
1042
ie = make_entry(kind, parts[-1], parent_id, file_id)
1188
1043
return self.add(ie)
1219
1074
>>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1220
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1075
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1223
1078
>>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1224
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1079
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1228
1083
if not isinstance(other, Inventory):
1229
1084
return NotImplemented
1086
if len(self._byid) != len(other._byid):
1087
# shortcut: obviously not the same
1231
1090
return self._byid == other._byid
1233
1093
def __ne__(self, other):
1234
1094
return not self.__eq__(other)
1236
1097
def __hash__(self):
1237
1098
raise ValueError('not hashable')
1239
1100
def _iter_file_id_parents(self, file_id):
1240
1101
"""Yield the parents of file_id up to the root."""
1241
file_id = osutils.safe_file_id(file_id)
1242
while file_id is not None:
1102
while file_id != None:
1244
1104
ie = self._byid[file_id]
1245
1105
except KeyError:
1246
raise errors.NoSuchId(tree=None, file_id=file_id)
1106
raise BzrError("file_id {%s} not found in inventory" % file_id)
1248
1108
file_id = ie.parent_id
1311
1164
return parent.file_id
1313
1167
def has_filename(self, names):
1314
1168
return bool(self.path2id(names))
1316
1171
def has_id(self, file_id):
1317
file_id = osutils.safe_file_id(file_id)
1318
return (file_id in self._byid)
1172
return self._byid.has_key(file_id)
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]
1341
1175
def rename(self, file_id, new_parent_id, new_name):
1342
1176
"""Move a file within the inventory.
1344
1178
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)
1180
This does not move the working file."""
1349
1181
if not is_valid_name(new_name):
1350
1182
raise BzrError("not an acceptable filename: %r" % new_name)
1369
1201
file_ie.name = new_name
1370
1202
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
1205
def make_entry(kind, name, parent_id, file_id=None):
1385
1206
"""Create an inventory entry.
1390
1211
:param file_id: the file_id to use. if None, one will be created.
1392
1213
if file_id is None:
1393
file_id = generate_ids.gen_file_id(name)
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)
1395
file_id = osutils.safe_file_id(file_id)
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
norm_name, can_access = osutils.normalized_filename(name)
1402
if norm_name != name:
1406
# TODO: jam 20060701 This would probably be more useful
1407
# if the error was raised with the full path
1408
raise errors.InvalidNormalization(name)
1411
factory = entry_factory[kind]
1413
1222
raise BzrError("unknown kind %r" % kind)
1414
return factory(file_id, name, parent_id)
1417
1226
_NAME_RE = None
1419
1228
def is_valid_name(name):
1420
1229
global _NAME_RE
1421
if _NAME_RE is None:
1230
if _NAME_RE == None:
1422
1231
_NAME_RE = re.compile(r'^[^/\\]+$')
1424
1233
return bool(_NAME_RE.match(name))