79
79
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
80
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
80
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
81
81
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
82
InventoryFile('2323', 'hello.c', parent_id='123')
82
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
83
83
>>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
84
84
>>> for ix, j in enumerate(i.iter_entries()):
85
85
... 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'))
87
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
88
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
89
89
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
90
90
Traceback (most recent call last):
92
92
BzrError: inventory already contains entry with id {2323}
93
93
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
94
InventoryFile('2324', 'bye.c', parent_id='123')
94
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
95
95
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
96
InventoryDirectory('2325', 'wibble', parent_id='123')
96
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
97
97
>>> i.path2id('src/wibble')
101
101
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
102
InventoryFile('2326', 'wibble.c', parent_id='2325')
102
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
104
InventoryFile('2326', 'wibble.c', parent_id='2325')
104
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
105
105
>>> for path, entry in i.iter_entries():
107
107
... assert i.path2id(path)
123
123
RENAMED = 'renamed'
124
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
128
def detect_changes(self, old_entry):
137
129
"""Return a (text_modified, meta_modified) from this to old_entry.
403
393
return 'unchanged'
405
395
def __repr__(self):
406
return ("%s(%r, %r, parent_id=%r)"
396
return ("%s(%r, %r, parent_id=%r, revision=%r)"
407
397
% (self.__class__.__name__,
412
403
def snapshot(self, revision, path, previous_entries,
413
work_tree, weave_store, transaction):
404
work_tree, commit_builder):
414
405
"""Make a snapshot of this entry which may or may not have changed.
416
407
This means that all its fields are populated, that it has its
426
419
self.revision = parent_ie.revision
427
420
return "unchanged"
428
421
return self._snapshot_into_revision(revision, previous_entries,
429
work_tree, weave_store, transaction)
422
work_tree, commit_builder)
431
424
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
432
weave_store, transaction):
433
426
"""Record this revision unconditionally into a store.
435
428
The entry's last-changed revision property (`revision`) is updated to
442
435
mutter('new revision {%s} for {%s}', revision, self.file_id)
443
436
self.revision = revision
444
self._snapshot_text(previous_entries, work_tree, weave_store,
437
self._snapshot_text(previous_entries, work_tree, commit_builder)
447
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
439
def _snapshot_text(self, file_parents, work_tree, commit_builder):
448
440
"""Record the 'text' of this entry, whatever form that takes.
450
442
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)
444
raise NotImplementedError(self._snapshot_text)
456
446
def __eq__(self, other):
457
447
if not isinstance(other, InventoryEntry):
507
497
class InventoryDirectory(InventoryEntry):
508
498
"""A directory in an inventory."""
500
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
501
'text_id', 'parent_id', 'children', 'executable',
502
'revision', 'symlink_target']
510
504
def _check(self, checker, rev_id, tree):
511
505
"""See InventoryEntry._check"""
512
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
506
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
513
507
raise BzrCheckError('directory {%s} has text in revision {%s}'
514
508
% (self.file_id, rev_id))
542
536
"""See InventoryEntry._put_on_disk."""
543
537
os.mkdir(fullpath)
539
def _snapshot_text(self, file_parents, work_tree, commit_builder):
540
"""See InventoryEntry._snapshot_text."""
541
commit_builder.modified_directory(self.file_id, file_parents)
546
544
class InventoryFile(InventoryEntry):
547
545
"""A file in an inventory."""
547
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
548
'text_id', 'parent_id', 'children', 'executable',
549
'revision', 'symlink_target']
549
551
def _check(self, checker, tree_revision_id, tree):
550
552
"""See InventoryEntry._check"""
551
553
t = (self.file_id, self.revision)
591
593
def detect_changes(self, old_entry):
592
594
"""See InventoryEntry.detect_changes."""
593
assert self.text_sha1 != None
594
assert old_entry.text_sha1 != None
595
assert self.text_sha1 is not None
596
assert old_entry.text_sha1 is not None
595
597
text_modified = (self.text_sha1 != old_entry.text_sha1)
596
598
meta_modified = (self.executable != old_entry.executable)
597
599
return text_modified, meta_modified
650
652
def _read_tree_state(self, path, work_tree):
651
653
"""See InventoryEntry._read_tree_state."""
652
654
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
655
# FIXME: 20050930 probe for the text size when getting sha1
656
# in _read_tree_state
653
657
self.executable = work_tree.is_executable(self.file_id, path=path)
660
return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
661
% (self.__class__.__name__,
655
668
def _forget_tree_state(self):
656
669
self.text_sha1 = None
657
670
self.executable = None
659
def _snapshot_text(self, file_parents, work_tree, versionedfile_store, transaction):
672
def _snapshot_text(self, file_parents, work_tree, commit_builder):
660
673
"""See InventoryEntry._snapshot_text."""
661
mutter('storing text of file {%s} in revision {%s} into %r',
662
self.file_id, self.revision, versionedfile_store)
663
# special case to avoid diffing on renames or
665
if (len(file_parents) == 1
666
and self.text_sha1 == file_parents.values()[0].text_sha1
667
and self.text_size == file_parents.values()[0].text_size):
668
previous_ie = file_parents.values()[0]
669
versionedfile = versionedfile_store.get_weave(self.file_id, transaction)
670
versionedfile.clone_text(self.revision, previous_ie.revision, file_parents.keys())
672
new_lines = work_tree.get_file(self.file_id).readlines()
673
self._add_text_to_weave(new_lines, file_parents.keys(), versionedfile_store,
675
self.text_sha1 = sha_strings(new_lines)
676
self.text_size = sum(map(len, new_lines))
674
def get_content_byte_lines():
675
return work_tree.get_file(self.file_id).readlines()
676
self.text_sha1, self.text_size = commit_builder.modified_file_text(
677
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
679
679
def _unchanged(self, previous_ie):
680
680
"""See InventoryEntry._unchanged."""
693
693
class InventoryLink(InventoryEntry):
694
694
"""A file in an inventory."""
696
__slots__ = ['symlink_target']
696
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
697
'text_id', 'parent_id', 'children', 'executable',
698
'revision', 'symlink_target']
698
700
def _check(self, checker, rev_id, tree):
699
701
"""See InventoryEntry._check"""
700
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
702
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
701
703
raise BzrCheckError('symlink {%s} has text in revision {%s}'
702
704
% (self.file_id, rev_id))
703
if self.symlink_target == None:
705
if self.symlink_target is None:
704
706
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
705
707
% (self.file_id, rev_id))
812
819
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
813
820
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
814
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
821
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
816
823
def __init__(self, root_id=ROOT_ID, revision_id=None):
817
824
"""Create or read an inventory.
896
899
# if we finished all children, pop it off the stack
902
def iter_entries_by_dir(self, from_dir=None):
903
"""Iterate over the entries in a directory first order.
905
This returns all entries for a directory before returning
906
the entries for children of a directory. This is not
907
lexicographically sorted order, and is a hybrid between
908
depth-first and breadth-first.
910
:return: This yields (path, entry) pairs
912
# TODO? Perhaps this should return the from_dir so that the root is
913
# yielded? or maybe an option?
917
elif isinstance(from_dir, basestring):
918
from_dir = self._byid[from_dir]
920
stack = [(u'', from_dir)]
922
cur_relpath, cur_dir = stack.pop()
925
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
927
child_relpath = cur_relpath + child_name
929
yield child_relpath, child_ie
931
if child_ie.kind == 'directory':
932
child_dirs.append((child_relpath+'/', child_ie))
933
stack.extend(reversed(child_dirs))
899
935
def entries(self):
900
936
"""Return list of (path, ie) for all entries except the root.
931
966
descend(self.root, u'')
936
969
def __contains__(self, file_id):
937
970
"""True if this entry contains a file with given id.
939
972
>>> inv = Inventory()
940
973
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
941
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
974
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
947
980
return file_id in self._byid
950
982
def __getitem__(self, file_id):
951
983
"""Return the entry for given file_id.
953
985
>>> inv = Inventory()
954
986
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
955
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
987
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
956
988
>>> inv['123123'].name
960
992
return self._byid[file_id]
963
995
raise BzrError("can't look up file_id None")
965
997
raise BzrError("file_id {%s} not in inventory" % file_id)
968
999
def get_file_kind(self, file_id):
969
1000
return self._byid[file_id].kind
971
1002
def get_child(self, parent_id, filename):
972
1003
return self[parent_id].children.get(filename)
975
1005
def add(self, entry):
976
1006
"""Add entry to inventory.
1055
1083
>>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1056
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1084
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1059
1087
>>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1060
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1088
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1064
1092
if not isinstance(other, Inventory):
1065
1093
return NotImplemented
1067
if len(self._byid) != len(other._byid):
1068
# shortcut: obviously not the same
1071
1095
return self._byid == other._byid
1074
1097
def __ne__(self, other):
1075
1098
return not self.__eq__(other)
1078
1100
def __hash__(self):
1079
1101
raise ValueError('not hashable')
1081
1103
def _iter_file_id_parents(self, file_id):
1082
1104
"""Yield the parents of file_id up to the root."""
1083
while file_id != None:
1105
while file_id is not None:
1085
1107
ie = self._byid[file_id]
1086
1108
except KeyError: