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 RootEntry(InventoryEntry):
499
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
500
'text_id', 'parent_id', 'children', 'executable',
501
'revision', 'symlink_target']
509
503
def _check(self, checker, rev_id, tree):
510
504
"""See InventoryEntry._check"""
527
522
class InventoryDirectory(InventoryEntry):
528
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']
530
529
def _check(self, checker, rev_id, tree):
531
530
"""See InventoryEntry._check"""
532
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:
533
532
raise BzrCheckError('directory {%s} has text in revision {%s}'
534
533
% (self.file_id, rev_id))
562
561
"""See InventoryEntry._put_on_disk."""
563
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)
566
569
class InventoryFile(InventoryEntry):
567
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']
569
576
def _check(self, checker, tree_revision_id, tree):
570
577
"""See InventoryEntry._check"""
571
578
t = (self.file_id, self.revision)
611
618
def detect_changes(self, old_entry):
612
619
"""See InventoryEntry.detect_changes."""
613
assert self.text_sha1 != None
614
assert old_entry.text_sha1 != None
620
assert self.text_sha1 is not None
621
assert old_entry.text_sha1 is not None
615
622
text_modified = (self.text_sha1 != old_entry.text_sha1)
616
623
meta_modified = (self.executable != old_entry.executable)
617
624
return text_modified, meta_modified
670
677
def _read_tree_state(self, path, work_tree):
671
678
"""See InventoryEntry._read_tree_state."""
672
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
673
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__,
675
693
def _forget_tree_state(self):
676
694
self.text_sha1 = None
677
695
self.executable = None
679
def _snapshot_text(self, file_parents, work_tree, versionedfile_store, transaction):
697
def _snapshot_text(self, file_parents, work_tree, commit_builder):
680
698
"""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))
699
def get_content_byte_lines():
700
return work_tree.get_file(self.file_id).readlines()
701
self.text_sha1, self.text_size = commit_builder.modified_file_text(
702
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
699
704
def _unchanged(self, previous_ie):
700
705
"""See InventoryEntry._unchanged."""
713
718
class InventoryLink(InventoryEntry):
714
719
"""A file in an inventory."""
716
__slots__ = ['symlink_target']
721
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
722
'text_id', 'parent_id', 'children', 'executable',
723
'revision', 'symlink_target']
718
725
def _check(self, checker, rev_id, tree):
719
726
"""See InventoryEntry._check"""
720
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
727
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
721
728
raise BzrCheckError('symlink {%s} has text in revision {%s}'
722
729
% (self.file_id, rev_id))
723
if self.symlink_target == None:
730
if self.symlink_target is None:
724
731
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
725
732
% (self.file_id, rev_id))
832
844
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
833
845
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
834
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
846
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
836
848
def __init__(self, root_id=ROOT_ID, revision_id=None):
837
849
"""Create or read an inventory.
848
860
#if root_id is None:
849
861
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
850
862
self.root = RootEntry(root_id)
863
# FIXME: this isn't ever used, changing it to self.revision may break
864
# things. TODO make everything use self.revision_id
851
865
self.revision_id = revision_id
852
866
self._byid = {self.root.file_id: self.root}
856
869
# TODO: jam 20051218 Should copy also copy the revision_id?
857
870
other = Inventory(self.root.file_id)
915
925
# if we finished all children, pop it off the stack
928
def iter_entries_by_dir(self, from_dir=None):
929
"""Iterate over the entries in a directory first order.
931
This returns all entries for a directory before returning
932
the entries for children of a directory. This is not
933
lexicographically sorted order, and is a hybrid between
934
depth-first and breadth-first.
936
:return: This yields (path, entry) pairs
938
# TODO? Perhaps this should return the from_dir so that the root is
939
# yielded? or maybe an option?
943
elif isinstance(from_dir, basestring):
944
from_dir = self._byid[from_dir]
946
stack = [(u'', from_dir)]
948
cur_relpath, cur_dir = stack.pop()
951
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
953
child_relpath = cur_relpath + child_name
955
yield child_relpath, child_ie
957
if child_ie.kind == 'directory':
958
child_dirs.append((child_relpath+'/', child_ie))
959
stack.extend(reversed(child_dirs))
918
961
def entries(self):
919
962
"""Return list of (path, ie) for all entries except the root.
950
992
descend(self.root, u'')
955
995
def __contains__(self, file_id):
956
996
"""True if this entry contains a file with given id.
958
998
>>> inv = Inventory()
959
999
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
960
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
1000
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
961
1001
>>> '123' in inv
963
1003
>>> '456' in inv
966
1006
return file_id in self._byid
969
1008
def __getitem__(self, file_id):
970
1009
"""Return the entry for given file_id.
972
1011
>>> inv = Inventory()
973
1012
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
974
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
1013
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
975
1014
>>> inv['123123'].name
979
1018
return self._byid[file_id]
980
1019
except KeyError:
982
1021
raise BzrError("can't look up file_id None")
984
1023
raise BzrError("file_id {%s} not in inventory" % file_id)
987
1025
def get_file_kind(self, file_id):
988
1026
return self._byid[file_id].kind
990
1028
def get_child(self, parent_id, filename):
991
1029
return self[parent_id].children.get(filename)
994
1031
def add(self, entry):
995
1032
"""Add entry to inventory.
1074
1109
>>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1075
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1110
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1078
1113
>>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1079
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1114
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1083
1118
if not isinstance(other, Inventory):
1084
1119
return NotImplemented
1086
if len(self._byid) != len(other._byid):
1087
# shortcut: obviously not the same
1090
1121
return self._byid == other._byid
1093
1123
def __ne__(self, other):
1094
1124
return not self.__eq__(other)
1097
1126
def __hash__(self):
1098
1127
raise ValueError('not hashable')
1100
1129
def _iter_file_id_parents(self, file_id):
1101
1130
"""Yield the parents of file_id up to the root."""
1102
while file_id != None:
1131
while file_id is not None:
1104
1133
ie = self._byid[file_id]
1105
1134
except KeyError: