81
78
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
82
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
79
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
83
80
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
84
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
85
>>> shouldbe = {0: '', 1: 'src', 2: pathjoin('src','hello.c')}
81
InventoryFile('2323', 'hello.c', parent_id='123')
82
>>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
86
83
>>> for ix, j in enumerate(i.iter_entries()):
87
84
... print (j[0] == shouldbe[ix], j[1])
89
(True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
90
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
91
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
86
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
87
(True, InventoryFile('2323', 'hello.c', parent_id='123'))
92
88
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
93
89
Traceback (most recent call last):
95
91
BzrError: inventory already contains entry with id {2323}
96
92
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
97
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
93
InventoryFile('2324', 'bye.c', parent_id='123')
98
94
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
99
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
95
InventoryDirectory('2325', 'wibble', parent_id='123')
100
96
>>> i.path2id('src/wibble')
104
100
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
105
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
101
InventoryFile('2326', 'wibble.c', parent_id='2325')
107
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
103
InventoryFile('2326', 'wibble.c', parent_id='2325')
108
104
>>> for path, entry in i.iter_entries():
110
106
... assert i.path2id(path)
127
122
RENAMED = 'renamed'
128
123
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()
132
135
def detect_changes(self, old_entry):
133
136
"""Return a (text_modified, meta_modified) from this to old_entry.
314
317
fullpath = pathjoin(dest, dp)
315
318
self._put_on_disk(fullpath, tree)
316
# mutter(" export {%s} kind %s to %s", self.file_id,
317
# self.kind, fullpath)
319
mutter(" export {%s} kind %s to %s", self.file_id,
319
322
def _put_on_disk(self, fullpath, tree):
320
323
"""Put this entry onto disk at fullpath, from tree tree."""
321
324
raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
323
326
def sorted_children(self):
324
return sorted(self.children.items())
327
l = self.children.items()
327
332
def versionable_kind(kind):
397
402
return 'unchanged'
399
404
def __repr__(self):
400
return ("%s(%r, %r, parent_id=%r, revision=%r)"
405
return ("%s(%r, %r, parent_id=%r)"
401
406
% (self.__class__.__name__,
407
411
def snapshot(self, revision, path, previous_entries,
408
work_tree, commit_builder):
412
work_tree, weave_store, transaction):
409
413
"""Make a snapshot of this entry which may or may not have changed.
411
415
This means that all its fields are populated, that it has its
412
416
text stored in the text store or weave.
414
# mutter('new parents of %s are %r', path, previous_entries)
418
mutter('new parents of %s are %r', path, previous_entries)
415
419
self._read_tree_state(path, work_tree)
416
# TODO: Where should we determine whether to reuse a
417
# previous revision id or create a new revision? 20060606
418
420
if len(previous_entries) == 1:
419
421
# cannot be unchanged unless there is only one parent file rev.
420
422
parent_ie = previous_entries.values()[0]
421
423
if self._unchanged(parent_ie):
422
# mutter("found unchanged entry")
424
mutter("found unchanged entry")
423
425
self.revision = parent_ie.revision
424
426
return "unchanged"
425
427
return self._snapshot_into_revision(revision, previous_entries,
426
work_tree, commit_builder)
428
work_tree, weave_store, transaction)
428
430
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
431
weave_store, transaction):
430
432
"""Record this revision unconditionally into a store.
432
434
The entry's last-changed revision property (`revision`) is updated to
437
439
:returns: String description of the commit (e.g. "merged", "modified"), etc.
439
# mutter('new revision {%s} for {%s}', revision, self.file_id)
441
mutter('new revision {%s} for {%s}', revision, self.file_id)
440
442
self.revision = revision
441
self._snapshot_text(previous_entries, work_tree, commit_builder)
443
self._snapshot_text(previous_entries, work_tree, weave_store,
443
def _snapshot_text(self, file_parents, work_tree, commit_builder):
446
def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
444
447
"""Record the 'text' of this entry, whatever form that takes.
446
449
This default implementation simply adds an empty text.
448
raise NotImplementedError(self._snapshot_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)
450
455
def __eq__(self, other):
451
456
if not isinstance(other, InventoryEntry):
501
506
class RootEntry(InventoryEntry):
503
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
504
'text_id', 'parent_id', 'children', 'executable',
505
'revision', 'symlink_target']
507
508
def _check(self, checker, rev_id, tree):
508
509
"""See InventoryEntry._check"""
510
511
def __init__(self, file_id):
511
512
self.file_id = file_id
512
513
self.children = {}
513
self.kind = 'directory'
514
self.kind = 'root_directory'
514
515
self.parent_id = None
517
warn('RootEntry is deprecated as of bzr 0.10. Please use '
518
'InventoryDirectory instead.',
519
DeprecationWarning, stacklevel=2)
521
518
def __eq__(self, other):
522
519
if not isinstance(other, RootEntry):
529
526
class InventoryDirectory(InventoryEntry):
530
527
"""A directory in an inventory."""
532
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
533
'text_id', 'parent_id', 'children', 'executable',
534
'revision', 'symlink_target']
536
529
def _check(self, checker, rev_id, tree):
537
530
"""See InventoryEntry._check"""
538
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
531
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
539
532
raise BzrCheckError('directory {%s} has text in revision {%s}'
540
533
% (self.file_id, rev_id))
568
561
"""See InventoryEntry._put_on_disk."""
569
562
os.mkdir(fullpath)
571
def _snapshot_text(self, file_parents, work_tree, commit_builder):
572
"""See InventoryEntry._snapshot_text."""
573
commit_builder.modified_directory(self.file_id, file_parents)
576
565
class InventoryFile(InventoryEntry):
577
566
"""A file in an inventory."""
579
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
580
'text_id', 'parent_id', 'children', 'executable',
581
'revision', 'symlink_target']
583
568
def _check(self, checker, tree_revision_id, tree):
584
569
"""See InventoryEntry._check"""
585
570
t = (self.file_id, self.revision)
684
669
def _read_tree_state(self, path, work_tree):
685
670
"""See InventoryEntry._read_tree_state."""
686
self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
687
# FIXME: 20050930 probe for the text size when getting sha1
688
# in _read_tree_state
689
self.executable = work_tree.is_executable(self.file_id, path=path)
692
return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
693
% (self.__class__.__name__,
671
self.text_sha1 = work_tree.get_file_sha1(self.file_id)
672
self.executable = work_tree.is_executable(self.file_id)
700
674
def _forget_tree_state(self):
701
675
self.text_sha1 = None
676
self.executable = None
703
def _snapshot_text(self, file_parents, work_tree, commit_builder):
678
def _snapshot_text(self, file_parents, work_tree, versionedfile_store, transaction):
704
679
"""See InventoryEntry._snapshot_text."""
705
def get_content_byte_lines():
706
return work_tree.get_file(self.file_id).readlines()
707
self.text_sha1, self.text_size = commit_builder.modified_file_text(
708
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
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))
710
698
def _unchanged(self, previous_ie):
711
699
"""See InventoryEntry._unchanged."""
724
712
class InventoryLink(InventoryEntry):
725
713
"""A file in an inventory."""
727
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
728
'text_id', 'parent_id', 'children', 'executable',
729
'revision', 'symlink_target']
715
__slots__ = ['symlink_target']
731
717
def _check(self, checker, rev_id, tree):
732
718
"""See InventoryEntry._check"""
733
if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
719
if self.text_sha1 != None or self.text_size != None or self.text_id != None:
734
720
raise BzrCheckError('symlink {%s} has text in revision {%s}'
735
721
% (self.file_id, rev_id))
736
if self.symlink_target is None:
722
if self.symlink_target == None:
737
723
raise BzrCheckError('symlink {%s} has no target in revision {%s}'
738
724
% (self.file_id, rev_id))
846
827
May also look up by name:
848
829
>>> [x[0] for x in inv.iter_entries()]
850
831
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
851
832
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
852
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
833
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
854
835
def __init__(self, root_id=ROOT_ID, revision_id=None):
855
836
"""Create or read an inventory.
865
846
# root id. Rather than generating a random one here.
866
847
#if root_id is None:
867
848
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
868
self.root = InventoryDirectory(root_id, '', None)
869
# FIXME: this isn't ever used, changing it to self.revision may break
870
# things. TODO make everything use self.revision_id
849
self.root = RootEntry(root_id)
871
850
self.revision_id = revision_id
872
851
self._byid = {self.root.file_id: self.root}
875
855
# TODO: jam 20051218 Should copy also copy the revision_id?
876
entries = self.iter_entries()
877
other = Inventory(entries.next()[1].file_id)
856
other = Inventory(self.root.file_id)
878
857
# copy recursively so we know directories will be added before
879
858
# their children. There are more efficient ways than this...
880
for path, entry in entries():
859
for path, entry in self.iter_entries():
860
if entry == self.root:
881
862
other.add(entry.copy())
884
866
def __iter__(self):
885
867
return iter(self._byid)
887
870
def __len__(self):
888
871
"""Returns number of entries."""
889
872
return len(self._byid)
891
875
def iter_entries(self, from_dir=None):
892
876
"""Return (path, entry) pairs, in order by name."""
897
elif isinstance(from_dir, basestring):
898
from_dir = self._byid[from_dir]
900
# unrolling the recursive called changed the time from
901
# 440ms/663ms (inline/total) to 116ms/116ms
902
children = from_dir.children.items()
904
children = collections.deque(children)
905
stack = [(u'', children)]
907
from_dir_relpath, children = stack[-1]
910
name, ie = children.popleft()
912
# we know that from_dir_relpath never ends in a slash
913
# and 'f' doesn't begin with one, we can do a string op, rather
914
# than the checks of pathjoin(), though this means that all paths
916
path = from_dir_relpath + '/' + name
920
if ie.kind != 'directory':
923
# But do this child first
924
new_children = ie.children.items()
926
new_children = collections.deque(new_children)
927
stack.append((path, new_children))
928
# Break out of inner loop, so that we start outer loop with child
931
# if we finished all children, pop it off the stack
934
def iter_entries_by_dir(self, from_dir=None):
935
"""Iterate over the entries in a directory first order.
937
This returns all entries for a directory before returning
938
the entries for children of a directory. This is not
939
lexicographically sorted order, and is a hybrid between
940
depth-first and breadth-first.
942
:return: This yields (path, entry) pairs
944
# TODO? Perhaps this should return the from_dir so that the root is
945
# yielded? or maybe an option?
950
elif isinstance(from_dir, basestring):
951
from_dir = self._byid[from_dir]
953
stack = [(u'', from_dir)]
955
cur_relpath, cur_dir = stack.pop()
958
for child_name, child_ie in sorted(cur_dir.children.iteritems()):
960
child_relpath = cur_relpath + child_name
962
yield child_relpath, child_ie
964
if child_ie.kind == 'directory':
965
child_dirs.append((child_relpath+'/', child_ie))
966
stack.extend(reversed(child_dirs))
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
968
892
def entries(self):
969
893
"""Return list of (path, ie) for all entries except the root.
1013
940
return file_id in self._byid
1015
943
def __getitem__(self, file_id):
1016
944
"""Return the entry for given file_id.
1018
946
>>> inv = Inventory()
1019
947
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
1020
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
948
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
1021
949
>>> inv['123123'].name
1025
953
return self._byid[file_id]
1026
954
except KeyError:
1028
956
raise BzrError("can't look up file_id None")
1030
958
raise BzrError("file_id {%s} not in inventory" % file_id)
1032
961
def get_file_kind(self, file_id):
1033
962
return self._byid[file_id].kind
1035
964
def get_child(self, parent_id, filename):
1036
965
return self[parent_id].children.get(filename)
1038
968
def add(self, entry):
1039
969
"""Add entry to inventory.
1116
1048
>>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1117
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1049
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1120
1052
>>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1121
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1053
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1125
1057
if not isinstance(other, Inventory):
1126
1058
return NotImplemented
1060
if len(self._byid) != len(other._byid):
1061
# shortcut: obviously not the same
1128
1064
return self._byid == other._byid
1130
1067
def __ne__(self, other):
1131
1068
return not self.__eq__(other)
1133
1071
def __hash__(self):
1134
1072
raise ValueError('not hashable')
1136
1074
def _iter_file_id_parents(self, file_id):
1137
1075
"""Yield the parents of file_id up to the root."""
1138
while file_id is not None:
1076
while file_id != None:
1140
1078
ie = self._byid[file_id]
1141
1079
except KeyError: