78
79
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
79
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
80
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
80
81
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
81
InventoryFile('2323', 'hello.c', parent_id='123')
82
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
82
83
>>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
83
84
>>> for ix, j in enumerate(i.iter_entries()):
84
85
... print (j[0] == shouldbe[ix], j[1])
86
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
87
(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))
88
89
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
89
90
Traceback (most recent call last):
91
92
BzrError: inventory already contains entry with id {2323}
92
93
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
93
InventoryFile('2324', 'bye.c', parent_id='123')
94
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
94
95
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
95
InventoryDirectory('2325', 'wibble', parent_id='123')
96
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
96
97
>>> i.path2id('src/wibble')
100
101
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
101
InventoryFile('2326', 'wibble.c', parent_id='2325')
102
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
103
InventoryFile('2326', 'wibble.c', parent_id='2325')
104
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
104
105
>>> for path, entry in i.iter_entries():
106
107
... assert i.path2id(path)
122
123
RENAMED = 'renamed'
123
124
MODIFIED_AND_RENAMED = 'modified and renamed'
125
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
126
'text_id', 'parent_id', 'children', 'executable',
129
def _add_text_to_weave(self, new_lines, parents, weave_store, transaction):
130
versionedfile = weave_store.get_weave_or_empty(self.file_id,
132
versionedfile.add_lines(self.revision, parents, new_lines)
133
versionedfile.clear_cache()
135
128
def detect_changes(self, old_entry):
136
129
"""Return a (text_modified, meta_modified) from this to old_entry.
402
393
return 'unchanged'
404
395
def __repr__(self):
405
return ("%s(%r, %r, parent_id=%r)"
396
return ("%s(%r, %r, parent_id=%r, revision=%r)"
406
397
% (self.__class__.__name__,
411
403
def snapshot(self, revision, path, previous_entries,
412
work_tree, weave_store, transaction):
404
work_tree, commit_builder):
413
405
"""Make a snapshot of this entry which may or may not have changed.
415
407
This means that all its fields are populated, that it has its
425
419
self.revision = parent_ie.revision
426
420
return "unchanged"
427
421
return self._snapshot_into_revision(revision, previous_entries,
428
work_tree, weave_store, transaction)
422
work_tree, commit_builder)
430
424
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
431
weave_store, transaction):
432
426
"""Record this revision unconditionally into a store.
434
428
The entry's last-changed revision property (`revision`) is updated to
441
435
mutter('new revision {%s} for {%s}', revision, self.file_id)
442
436
self.revision = revision
443
self._snapshot_text(previous_entries, work_tree, weave_store,
437
self._snapshot_text(previous_entries, work_tree, commit_builder)
446
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
439
def _snapshot_text(self, file_parents, work_tree, commit_builder):
447
440
"""Record the 'text' of this entry, whatever form that takes.
449
442
This default implementation simply adds an empty text.
451
mutter('storing file {%s} in revision {%s}',
452
self.file_id, self.revision)
453
self._add_text_to_weave([], file_parents.keys(), weave_store, transaction)
444
raise NotImplementedError(self._snapshot_text)
455
446
def __eq__(self, other):
456
447
if not isinstance(other, InventoryEntry):
506
497
class RootEntry(InventoryEntry):
499
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
500
'text_id', 'parent_id', 'children', 'executable',
501
'revision', 'symlink_target']
508
503
def _check(self, checker, rev_id, tree):
509
504
"""See InventoryEntry._check"""
526
522
class InventoryDirectory(InventoryEntry):
527
523
"""A directory in an inventory."""
525
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
526
'text_id', 'parent_id', 'children', 'executable',
527
'revision', 'symlink_target']
529
529
def _check(self, checker, rev_id, tree):
530
530
"""See InventoryEntry._check"""
531
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
531
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
532
532
raise BzrCheckError('directory {%s} has text in revision {%s}'
533
533
% (self.file_id, rev_id))
561
561
"""See InventoryEntry._put_on_disk."""
562
562
os.mkdir(fullpath)
564
def _snapshot_text(self, file_parents, work_tree, commit_builder):
565
"""See InventoryEntry._snapshot_text."""
566
commit_builder.modified_directory(self.file_id, file_parents)
565
569
class InventoryFile(InventoryEntry):
566
570
"""A file in an inventory."""
572
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
573
'text_id', 'parent_id', 'children', 'executable',
574
'revision', 'symlink_target']
568
576
def _check(self, checker, tree_revision_id, tree):
569
577
"""See InventoryEntry._check"""
570
578
t = (self.file_id, self.revision)
610
618
def detect_changes(self, old_entry):
611
619
"""See InventoryEntry.detect_changes."""
612
assert self.text_sha1 != None
613
assert old_entry.text_sha1 != None
620
assert self.text_sha1 is not None
621
assert old_entry.text_sha1 is not None
614
622
text_modified = (self.text_sha1 != old_entry.text_sha1)
615
623
meta_modified = (self.executable != old_entry.executable)
616
624
return text_modified, meta_modified
669
677
def _read_tree_state(self, path, work_tree):
670
678
"""See InventoryEntry._read_tree_state."""
671
self.text_sha1 = work_tree.get_file_sha1(self.file_id)
672
self.executable = work_tree.is_executable(self.file_id)
679
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
680
# FIXME: 20050930 probe for the text size when getting sha1
681
# in _read_tree_state
682
self.executable = work_tree.is_executable(self.file_id, path=path)
685
return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
686
% (self.__class__.__name__,
674
693
def _forget_tree_state(self):
675
694
self.text_sha1 = None
676
self.executable = None
678
def _snapshot_text(self, file_parents, work_tree, versionedfile_store, transaction):
696
def _snapshot_text(self, file_parents, work_tree, commit_builder):
679
697
"""See InventoryEntry._snapshot_text."""
680
mutter('storing text of file {%s} in revision {%s} into %r',
681
self.file_id, self.revision, versionedfile_store)
682
# special case to avoid diffing on renames or
684
if (len(file_parents) == 1
685
and self.text_sha1 == file_parents.values()[0].text_sha1
686
and self.text_size == file_parents.values()[0].text_size):
687
previous_ie = file_parents.values()[0]
688
versionedfile = versionedfile_store.get_weave(self.file_id, transaction)
689
versionedfile.clone_text(self.revision, previous_ie.revision, file_parents.keys())
691
new_lines = work_tree.get_file(self.file_id).readlines()
692
self._add_text_to_weave(new_lines, file_parents.keys(), versionedfile_store,
694
self.text_sha1 = sha_strings(new_lines)
695
self.text_size = sum(map(len, new_lines))
698
def get_content_byte_lines():
699
return work_tree.get_file(self.file_id).readlines()
700
self.text_sha1, self.text_size = commit_builder.modified_file_text(
701
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
698
703
def _unchanged(self, previous_ie):
699
704
"""See InventoryEntry._unchanged."""
712
717
class InventoryLink(InventoryEntry):
713
718
"""A file in an inventory."""
715
__slots__ = ['symlink_target']
720
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
721
'text_id', 'parent_id', 'children', 'executable',
722
'revision', 'symlink_target']
717
724
def _check(self, checker, rev_id, tree):
718
725
"""See InventoryEntry._check"""
719
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
726
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
720
727
raise BzrCheckError('symlink {%s} has text in revision {%s}'
721
728
% (self.file_id, rev_id))
722
if self.symlink_target == None:
729
if self.symlink_target is None:
723
730
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
724
731
% (self.file_id, rev_id))
827
839
May also look up by name:
829
841
>>> [x[0] for x in inv.iter_entries()]
831
843
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
832
844
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
833
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
845
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
835
847
def __init__(self, root_id=ROOT_ID, revision_id=None):
836
848
"""Create or read an inventory.
862
875
other.add(entry.copy())
866
878
def __iter__(self):
867
879
return iter(self._byid)
870
881
def __len__(self):
871
882
"""Returns number of entries."""
872
883
return len(self._byid)
875
885
def iter_entries(self, from_dir=None):
876
886
"""Return (path, entry) pairs, in order by name."""
880
elif isinstance(from_dir, basestring):
881
from_dir = self._byid[from_dir]
883
kids = from_dir.children.items()
885
for name, ie in kids:
887
if ie.kind == 'directory':
888
for cn, cie in self.iter_entries(from_dir=ie.file_id):
889
yield pathjoin(name, cn), cie
890
elif isinstance(from_dir, basestring):
891
from_dir = self._byid[from_dir]
893
# unrolling the recursive called changed the time from
894
# 440ms/663ms (inline/total) to 116ms/116ms
895
children = from_dir.children.items()
897
children = collections.deque(children)
898
stack = [(u'', children)]
900
from_dir_relpath, children = stack[-1]
903
name, ie = children.popleft()
905
# we know that from_dir_relpath never ends in a slash
906
# and 'f' doesn't begin with one, we can do a string op, rather
907
# than the checks of pathjoin(), though this means that all paths
909
path = from_dir_relpath + '/' + name
913
if ie.kind != 'directory':
916
# But do this child first
917
new_children = ie.children.items()
919
new_children = collections.deque(new_children)
920
stack.append((path, new_children))
921
# Break out of inner loop, so that we start outer loop with child
924
# if we finished all children, pop it off the stack
927
def iter_entries_by_dir(self, from_dir=None):
928
"""Iterate over the entries in a directory first order.
930
This returns all entries for a directory before returning
931
the entries for children of a directory. This is not
932
lexicographically sorted order, and is a hybrid between
933
depth-first and breadth-first.
935
:return: This yields (path, entry) pairs
937
# TODO? Perhaps this should return the from_dir so that the root is
938
# yielded? or maybe an option?
942
elif isinstance(from_dir, basestring):
943
from_dir = self._byid[from_dir]
945
stack = [(u'', from_dir)]
947
cur_relpath, cur_dir = stack.pop()
950
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
952
child_relpath = cur_relpath + child_name
954
yield child_relpath, child_ie
956
if child_ie.kind == 'directory':
957
child_dirs.append((child_relpath+'/', child_ie))
958
stack.extend(reversed(child_dirs))
892
960
def entries(self):
893
961
"""Return list of (path, ie) for all entries except the root.
924
991
descend(self.root, u'')
929
994
def __contains__(self, file_id):
930
995
"""True if this entry contains a file with given id.
932
997
>>> inv = Inventory()
933
998
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
934
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
999
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
935
1000
>>> '123' in inv
937
1002
>>> '456' in inv
940
1005
return file_id in self._byid
943
1007
def __getitem__(self, file_id):
944
1008
"""Return the entry for given file_id.
946
1010
>>> inv = Inventory()
947
1011
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
948
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
1012
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
949
1013
>>> inv['123123'].name
953
1017
return self._byid[file_id]
954
1018
except KeyError:
956
1020
raise BzrError("can't look up file_id None")
958
1022
raise BzrError("file_id {%s} not in inventory" % file_id)
961
1024
def get_file_kind(self, file_id):
962
1025
return self._byid[file_id].kind
964
1027
def get_child(self, parent_id, filename):
965
1028
return self[parent_id].children.get(filename)
968
1030
def add(self, entry):
969
1031
"""Add entry to inventory.
1048
1108
>>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1049
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1109
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1052
1112
>>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1053
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1113
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1057
1117
if not isinstance(other, Inventory):
1058
1118
return NotImplemented
1060
if len(self._byid) != len(other._byid):
1061
# shortcut: obviously not the same
1064
1120
return self._byid == other._byid
1067
1122
def __ne__(self, other):
1068
1123
return not self.__eq__(other)
1071
1125
def __hash__(self):
1072
1126
raise ValueError('not hashable')
1074
1128
def _iter_file_id_parents(self, file_id):
1075
1129
"""Yield the parents of file_id up to the root."""
1076
while file_id != None:
1130
while file_id is not None:
1078
1132
ie = self._byid[file_id]
1079
1133
except KeyError: