79
90
>>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
80
InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
91
InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
81
92
>>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
82
InventoryFile('2323', 'hello.c', parent_id='123')
83
>>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
93
InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
94
>>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
84
95
>>> for ix, j in enumerate(i.iter_entries()):
85
96
... 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'))
89
>>> i.add(InventoryFile('2323', 'bye.c', '123'))
90
Traceback (most recent call last):
92
BzrError: inventory already contains entry with id {2323}
98
(True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
99
(True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
100
(True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
93
101
>>> i.add(InventoryFile('2324', 'bye.c', '123'))
94
InventoryFile('2324', 'bye.c', parent_id='123')
102
InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
95
103
>>> i.add(InventoryDirectory('2325', 'wibble', '123'))
96
InventoryDirectory('2325', 'wibble', parent_id='123')
104
InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
97
105
>>> i.path2id('src/wibble')
101
109
>>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
102
InventoryFile('2326', 'wibble.c', parent_id='2325')
110
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
104
InventoryFile('2326', 'wibble.c', parent_id='2325')
112
InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
105
113
>>> for path, entry in i.iter_entries():
107
... assert i.path2id(path)
134
142
return False, False
136
def diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
137
output_to, reverse=False):
138
"""Perform a diff from this to to_entry.
140
text_diff will be used for textual difference calculation.
141
This is a template method, override _diff in child classes.
143
self._read_tree_state(tree.id2path(self.file_id), tree)
145
# cannot diff from one kind to another - you must do a removal
146
# and an addif they do not match.
147
assert self.kind == to_entry.kind
148
to_entry._read_tree_state(to_tree.id2path(to_entry.file_id),
150
self._diff(text_diff, from_label, tree, to_label, to_entry, to_tree,
153
144
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
154
145
output_to, reverse=False):
155
146
"""Perform a diff between two entries of the same kind."""
157
def find_previous_heads(self, previous_inventories,
158
versioned_file_store,
161
"""Return the revisions and entries that directly precede this.
163
Returned as a map from revision to inventory entry.
165
This is a map containing the file revisions in all parents
166
for which the file exists, and its revision is not a parent of
167
any other. If the file is new, the set will be empty.
169
:param versioned_file_store: A store where ancestry data on this
170
file id can be queried.
171
:param transaction: The transaction that queries to the versioned
172
file store should be completed under.
173
:param entry_vf: The entry versioned file, if its already available.
148
def parent_candidates(self, previous_inventories):
149
"""Find possible per-file graph parents.
151
This is currently defined by:
152
- Select the last changed revision in the parent inventory.
153
- Do deal with a short lived bug in bzr 0.8's development two entries
154
that have the same last changed but different 'x' bit settings are
175
def get_ancestors(weave, entry):
176
return set(weave.get_ancestry(entry.revision))
177
157
# revision:ie mapping for each ie found in previous_inventories.
179
# revision:ie mapping with one revision for each head.
181
# revision: ancestor list for each head
183
159
# identify candidate head revision ids.
184
160
for inv in previous_inventories:
185
161
if self.file_id in inv:
186
162
ie = inv[self.file_id]
187
assert ie.file_id == self.file_id
188
163
if ie.revision in candidates:
189
164
# same revision value in two different inventories:
190
165
# correct possible inconsistencies:
196
171
ie.executable = False
197
172
except AttributeError:
199
# must now be the same.
200
assert candidates[ie.revision] == ie
202
175
# add this revision as a candidate.
203
176
candidates[ie.revision] = ie
205
# common case optimisation
206
if len(candidates) == 1:
207
# if there is only one candidate revision found
208
# then we can opening the versioned file to access ancestry:
209
# there cannot be any ancestors to eliminate when there is
210
# only one revision available.
211
heads[ie.revision] = ie
214
# eliminate ancestors amongst the available candidates:
215
# heads are those that are not an ancestor of any other candidate
216
# - this provides convergence at a per-file level.
217
for ie in candidates.values():
218
# may be an ancestor of a known head:
219
already_present = 0 != len(
220
[head for head in heads
221
if ie.revision in head_ancestors[head]])
223
# an ancestor of an analyzed candidate.
225
# not an ancestor of a known head:
226
# load the versioned file for this file id if needed
228
entry_vf = versioned_file_store.get_weave_or_empty(
229
self.file_id, transaction)
230
ancestors = get_ancestors(entry_vf, ie)
231
# may knock something else out:
232
check_heads = list(heads.keys())
233
for head in check_heads:
234
if head in ancestors:
235
# this previously discovered 'head' is not
236
# really a head - its an ancestor of the newly
239
head_ancestors[ie.revision] = ancestors
240
heads[ie.revision] = ie
243
179
def get_tar_item(self, root, dp, now, tree):
244
180
"""Get a tarfile item and a file stream for its content."""
245
item = tarfile.TarInfo(pathjoin(root, dp))
181
item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
246
182
# TODO: would be cool to actually set it to the timestamp of the
247
183
# revision it was last changed
393
331
return 'unchanged'
395
333
def __repr__(self):
396
return ("%s(%r, %r, parent_id=%r)"
334
return ("%s(%r, %r, parent_id=%r, revision=%r)"
397
335
% (self.__class__.__name__,
402
def snapshot(self, revision, path, previous_entries,
403
work_tree, commit_builder):
404
"""Make a snapshot of this entry which may or may not have changed.
406
This means that all its fields are populated, that it has its
407
text stored in the text store or weave.
409
mutter('new parents of %s are %r', path, previous_entries)
410
self._read_tree_state(path, work_tree)
411
# TODO: Where should we determine whether to reuse a
412
# previous revision id or create a new revision? 20060606
413
if len(previous_entries) == 1:
414
# cannot be unchanged unless there is only one parent file rev.
415
parent_ie = previous_entries.values()[0]
416
if self._unchanged(parent_ie):
417
mutter("found unchanged entry")
418
self.revision = parent_ie.revision
420
return self._snapshot_into_revision(revision, previous_entries,
421
work_tree, commit_builder)
423
def _snapshot_into_revision(self, revision, previous_entries, work_tree,
425
"""Record this revision unconditionally into a store.
427
The entry's last-changed revision property (`revision`) is updated to
428
that of the new revision.
430
:param revision: id of the new revision that is being recorded.
432
:returns: String description of the commit (e.g. "merged", "modified"), etc.
434
mutter('new revision {%s} for {%s}', revision, self.file_id)
435
self.revision = revision
436
self._snapshot_text(previous_entries, work_tree, commit_builder)
438
def _snapshot_text(self, file_parents, work_tree, commit_builder):
439
"""Record the 'text' of this entry, whatever form that takes.
441
This default implementation simply adds an empty text.
443
raise NotImplementedError(self._snapshot_text)
445
341
def __eq__(self, other):
446
342
if not isinstance(other, InventoryEntry):
496
395
class RootEntry(InventoryEntry):
498
397
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
499
'text_id', 'parent_id', 'children', 'executable',
500
'revision', 'symlink_target']
398
'text_id', 'parent_id', 'children', 'executable',
399
'revision', 'symlink_target', 'reference_revision']
502
401
def _check(self, checker, rev_id, tree):
503
402
"""See InventoryEntry._check"""
521
424
"""A directory in an inventory."""
523
426
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
524
'text_id', 'parent_id', 'children', 'executable',
525
'revision', 'symlink_target']
427
'text_id', 'parent_id', 'children', 'executable',
428
'revision', 'symlink_target', 'reference_revision']
527
430
def _check(self, checker, rev_id, tree):
528
431
"""See InventoryEntry._check"""
559
462
"""See InventoryEntry._put_on_disk."""
560
463
os.mkdir(fullpath)
562
def _snapshot_text(self, file_parents, work_tree, commit_builder):
563
"""See InventoryEntry._snapshot_text."""
564
commit_builder.modified_directory(self.file_id, file_parents)
567
466
class InventoryFile(InventoryEntry):
568
467
"""A file in an inventory."""
570
469
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
571
'text_id', 'parent_id', 'children', 'executable',
572
'revision', 'symlink_target']
470
'text_id', 'parent_id', 'children', 'executable',
471
'revision', 'symlink_target', 'reference_revision']
574
473
def _check(self, checker, tree_revision_id, tree):
575
474
"""See InventoryEntry._check"""
577
476
if t in checker.checked_texts:
578
477
prev_sha = checker.checked_texts[t]
579
478
if prev_sha != self.text_sha1:
580
raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
581
(self.file_id, tree_revision_id))
480
'mismatched sha1 on {%s} in {%s} (%s != %s) %r' %
481
(self.file_id, tree_revision_id, prev_sha, self.text_sha1,
583
484
checker.repeated_text_cnt += 1
586
487
if self.file_id not in checker.checked_weaves:
587
488
mutter('check weave {%s}', self.file_id)
588
w = tree.get_weave(self.file_id)
489
w = tree._get_weave(self.file_id)
589
490
# Not passing a progress bar, because it creates a new
590
491
# progress, which overwrites the current progress,
591
492
# and doesn't look nice
593
494
checker.checked_weaves[self.file_id] = True
595
w = tree.get_weave(self.file_id)
496
w = tree._get_weave(self.file_id)
597
498
mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
598
499
checker.checked_text_cnt += 1
599
500
# We can't check the length, because Weave doesn't store that
600
501
# information, and the whole point of looking at the weave's
601
502
# sha1sum is that we don't have to extract the text.
602
if self.text_sha1 != w.get_sha1(self.revision):
503
if self.text_sha1 != w.get_sha1s([self.revision])[0]:
603
504
raise BzrCheckError('text {%s} version {%s} wrong sha1'
604
505
% (self.file_id, self.revision))
605
506
checker.checked_texts[t] = self.text_sha1
624
523
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
625
524
output_to, reverse=False):
626
525
"""See InventoryEntry._diff."""
628
from_text = tree.get_file(self.file_id).readlines()
630
to_text = to_tree.get_file(to_entry.file_id).readlines()
634
text_diff(from_label, from_text,
635
to_label, to_text, output_to)
637
text_diff(to_label, to_text,
638
from_label, from_text, output_to)
641
label_pair = (to_label, from_label)
643
label_pair = (from_label, to_label)
644
print >> output_to, "Binary files %s and %s differ" % label_pair
526
from bzrlib.diff import DiffText
527
from_file_id = self.file_id
529
to_file_id = to_entry.file_id
533
to_file_id, from_file_id = from_file_id, to_file_id
534
tree, to_tree = to_tree, tree
535
from_label, to_label = to_label, from_label
536
differ = DiffText(tree, to_tree, output_to, 'utf-8', '', '',
538
return differ.diff_text(from_file_id, to_file_id, from_label, to_label)
646
540
def has_text(self):
647
541
"""See InventoryEntry.has_text."""
679
573
# in _read_tree_state
680
574
self.executable = work_tree.is_executable(self.file_id, path=path)
577
return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
578
% (self.__class__.__name__,
682
585
def _forget_tree_state(self):
683
586
self.text_sha1 = None
684
self.executable = None
686
def _snapshot_text(self, file_parents, work_tree, commit_builder):
687
"""See InventoryEntry._snapshot_text."""
688
def get_content_byte_lines():
689
return work_tree.get_file(self.file_id).readlines()
690
self.text_sha1, self.text_size = commit_builder.modified_file_text(
691
self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
693
588
def _unchanged(self, previous_ie):
694
589
"""See InventoryEntry._unchanged."""
708
603
"""A file in an inventory."""
710
605
__slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
711
'text_id', 'parent_id', 'children', 'executable',
712
'revision', 'symlink_target']
606
'text_id', 'parent_id', 'children', 'executable',
607
'revision', 'symlink_target', 'reference_revision']
714
609
def _check(self, checker, rev_id, tree):
715
610
"""See InventoryEntry._check"""
738
633
def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
739
634
output_to, reverse=False):
740
635
"""See InventoryEntry._diff."""
741
from_text = self.symlink_target
636
from bzrlib.diff import DiffSymlink
637
old_target = self.symlink_target
742
638
if to_entry is not None:
743
to_text = to_entry.symlink_target
748
print >>output_to, '=== target changed %r => %r' % (from_text, to_text)
751
print >>output_to, '=== target was %r' % self.symlink_target
753
print >>output_to, '=== target is %r' % self.symlink_target
639
new_target = to_entry.symlink_target
648
new_target, old_target = old_target, new_target
649
differ = DiffSymlink(old_tree, new_tree, output_to)
650
return differ.diff_symlink(old_target, new_target)
755
652
def __init__(self, file_id, name, parent_id):
756
653
super(InventoryLink, self).__init__(file_id, name, parent_id)
790
687
compatible = False
791
688
return compatible
793
def _snapshot_text(self, file_parents, work_tree, commit_builder):
794
"""See InventoryEntry._snapshot_text."""
795
commit_builder.modified_link(
796
self.file_id, file_parents, self.symlink_target)
691
class TreeReference(InventoryEntry):
693
kind = 'tree-reference'
695
def __init__(self, file_id, name, parent_id, revision=None,
696
reference_revision=None):
697
InventoryEntry.__init__(self, file_id, name, parent_id)
698
self.revision = revision
699
self.reference_revision = reference_revision
702
return TreeReference(self.file_id, self.name, self.parent_id,
703
self.revision, self.reference_revision)
705
def _read_tree_state(self, path, work_tree):
706
"""Populate fields in the inventory entry from the given tree.
708
self.reference_revision = work_tree.get_reference_revision(
711
def _forget_tree_state(self):
712
self.reference_revision = None
714
def _unchanged(self, previous_ie):
715
"""See InventoryEntry._unchanged."""
716
compatible = super(TreeReference, self)._unchanged(previous_ie)
717
if self.reference_revision != previous_ie.reference_revision:
799
722
class Inventory(object):
829
752
May also look up by name:
831
754
>>> [x[0] for x in inv.iter_entries()]
833
756
>>> inv = Inventory('TREE_ROOT-12345678-12345678')
834
757
>>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
835
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
758
Traceback (most recent call last):
759
BzrError: parent_id {TREE_ROOT} not in inventory
760
>>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
761
InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
837
763
def __init__(self, root_id=ROOT_ID, revision_id=None):
838
764
"""Create or read an inventory.
844
770
The inventory is created with a default root directory, with
847
# We are letting Branch.create() create a unique inventory
848
# root id. Rather than generating a random one here.
850
# root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
851
self.root = RootEntry(root_id)
773
if root_id is not None:
774
self._set_root(InventoryDirectory(root_id, u'', None))
852
778
self.revision_id = revision_id
781
return "<Inventory object at %x, contents=%r>" % (id(self), self._byid)
783
def apply_delta(self, delta):
784
"""Apply a delta to this inventory.
786
:param delta: A list of changes to apply. After all the changes are
787
applied the final inventory must be internally consistent, but it
788
is ok to supply changes which, if only half-applied would have an
789
invalid result - such as supplying two changes which rename two
790
files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
791
('B', 'A', 'B-id', b_entry)].
793
Each change is a tuple, of the form (old_path, new_path, file_id,
796
When new_path is None, the change indicates the removal of an entry
797
from the inventory and new_entry will be ignored (using None is
798
appropriate). If new_path is not None, then new_entry must be an
799
InventoryEntry instance, which will be incorporated into the
800
inventory (and replace any existing entry with the same file id).
802
When old_path is None, the change indicates the addition of
803
a new entry to the inventory.
805
When neither new_path nor old_path are None, the change is a
806
modification to an entry, such as a rename, reparent, kind change
809
The children attribute of new_entry is ignored. This is because
810
this method preserves children automatically across alterations to
811
the parent of the children, and cases where the parent id of a
812
child is changing require the child to be passed in as a separate
813
change regardless. E.g. in the recursive deletion of a directory -
814
the directory's children must be included in the delta, or the
815
final inventory will be invalid.
818
# Remove all affected items which were in the original inventory,
819
# starting with the longest paths, thus ensuring parents are examined
820
# after their children, which means that everything we examine has no
821
# modified children remaining by the time we examine it.
822
for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
823
if op is not None), reverse=True):
824
if file_id not in self:
827
# Preserve unaltered children of file_id for later reinsertion.
828
children[file_id] = getattr(self[file_id], 'children', {})
829
# Remove file_id and the unaltered children. If file_id is not
830
# being deleted it will be reinserted back later.
831
self.remove_recursive_id(file_id)
832
# Insert all affected which should be in the new inventory, reattaching
833
# their children if they had any. This is done from shortest path to
834
# longest, ensuring that items which were modified and whose parents in
835
# the resulting inventory were also modified, are inserted after their
837
for new_path, new_entry in sorted((np, e) for op, np, f, e in
838
delta if np is not None):
839
if new_entry.kind == 'directory':
840
new_entry.children = children.get(new_entry.file_id, {})
843
def _set_root(self, ie):
853
845
self._byid = {self.root.file_id: self.root}
856
848
# TODO: jam 20051218 Should copy also copy the revision_id?
857
other = Inventory(self.root.file_id)
849
entries = self.iter_entries()
850
if self.root is None:
851
return Inventory(root_id=None)
852
other = Inventory(entries.next()[1].file_id)
858
853
# copy recursively so we know directories will be added before
859
854
# their children. There are more efficient ways than this...
860
for path, entry in self.iter_entries():
861
if entry == self.root:
855
for path, entry in entries:
863
856
other.add(entry.copy())
920
916
lexicographically sorted order, and is a hybrid between
921
917
depth-first and breadth-first.
919
:param yield_parents: If True, yield the parents from the root leading
920
down to specific_file_ids that have been requested. This has no
921
impact if specific_file_ids is None.
923
922
:return: This yields (path, entry) pairs
924
if specific_file_ids and not isinstance(specific_file_ids, set):
925
specific_file_ids = set(specific_file_ids)
925
926
# TODO? Perhaps this should return the from_dir so that the root is
926
927
# yielded? or maybe an option?
927
928
if from_dir is None:
929
if self.root is None:
931
# Optimize a common case
932
if (not yield_parents and specific_file_ids is not None and
933
len(specific_file_ids) == 1):
934
file_id = list(specific_file_ids)[0]
936
yield self.id2path(file_id), self[file_id]
929
938
from_dir = self.root
939
if (specific_file_ids is None or yield_parents or
940
self.root.file_id in specific_file_ids):
930
942
elif isinstance(from_dir, basestring):
931
943
from_dir = self._byid[from_dir]
945
if specific_file_ids is not None:
946
# TODO: jam 20070302 This could really be done as a loop rather
947
# than a bunch of recursive calls.
950
def add_ancestors(file_id):
951
if file_id not in byid:
953
parent_id = byid[file_id].parent_id
954
if parent_id is None:
956
if parent_id not in parents:
957
parents.add(parent_id)
958
add_ancestors(parent_id)
959
for file_id in specific_file_ids:
960
add_ancestors(file_id)
933
964
stack = [(u'', from_dir)]
940
971
child_relpath = cur_relpath + child_name
942
yield child_relpath, child_ie
973
if (specific_file_ids is None or
974
child_ie.file_id in specific_file_ids or
975
(yield_parents and child_ie.file_id in parents)):
976
yield child_relpath, child_ie
944
978
if child_ie.kind == 'directory':
945
child_dirs.append((child_relpath+'/', child_ie))
979
if parents is None or child_ie.file_id in parents:
980
child_dirs.append((child_relpath+'/', child_ie))
946
981
stack.extend(reversed(child_dirs))
983
def make_entry(self, kind, name, parent_id, file_id=None):
984
"""Simple thunk to bzrlib.inventory.make_entry."""
985
return make_entry(kind, name, parent_id, file_id)
948
987
def entries(self):
949
988
"""Return list of (path, ie) for all entries except the root.
985
1024
>>> inv = Inventory()
986
1025
>>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
987
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
1026
InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
988
1027
>>> '123' in inv
990
1029
>>> '456' in inv
993
return file_id in self._byid
1032
return (file_id in self._byid)
995
1034
def __getitem__(self, file_id):
996
1035
"""Return the entry for given file_id.
998
1037
>>> inv = Inventory()
999
1038
>>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
1000
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
1039
InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
1001
1040
>>> inv['123123'].name
1005
1044
return self._byid[file_id]
1006
1045
except KeyError:
1008
raise BzrError("can't look up file_id None")
1010
raise BzrError("file_id {%s} not in inventory" % file_id)
1046
# really we're passing an inventory, not a tree...
1047
raise errors.NoSuchId(self, file_id)
1012
1049
def get_file_kind(self, file_id):
1013
1050
return self._byid[file_id].kind
1024
1071
Returns the new entry object.
1026
1073
if entry.file_id in self._byid:
1027
raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1029
if entry.parent_id == ROOT_ID or entry.parent_id is None:
1030
entry.parent_id = self.root.file_id
1033
parent = self._byid[entry.parent_id]
1035
raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
1037
if entry.name in parent.children:
1038
raise BzrError("%s is already versioned" %
1039
pathjoin(self.id2path(parent.file_id), entry.name))
1041
self._byid[entry.file_id] = entry
1042
parent.children[entry.name] = entry
1074
raise errors.DuplicateFileId(entry.file_id,
1075
self._byid[entry.file_id])
1077
if entry.parent_id is None:
1081
parent = self._byid[entry.parent_id]
1083
raise BzrError("parent_id {%s} not in inventory" %
1086
if entry.name in parent.children:
1087
raise BzrError("%s is already versioned" %
1088
osutils.pathjoin(self.id2path(parent.file_id),
1089
entry.name).encode('utf-8'))
1090
parent.children[entry.name] = entry
1091
return self._add_child(entry)
1045
1093
def add_path(self, relpath, kind, file_id=None, parent_id=None):
1046
1094
"""Add entry from a path.
1096
1140
>>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1097
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1141
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1100
1144
>>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1101
InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1145
InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1105
1149
if not isinstance(other, Inventory):
1106
1150
return NotImplemented
1108
if len(self._byid) != len(other._byid):
1109
# shortcut: obviously not the same
1112
1152
return self._byid == other._byid
1114
1154
def __ne__(self, other):
1187
1230
return bool(self.path2id(names))
1189
1232
def has_id(self, file_id):
1190
return self._byid.has_key(file_id)
1233
return (file_id in self._byid)
1235
def remove_recursive_id(self, file_id):
1236
"""Remove file_id, and children, from the inventory.
1238
:param file_id: A file_id to remove.
1240
to_find_delete = [self._byid[file_id]]
1242
while to_find_delete:
1243
ie = to_find_delete.pop()
1244
to_delete.append(ie.file_id)
1245
if ie.kind == 'directory':
1246
to_find_delete.extend(ie.children.values())
1247
for file_id in reversed(to_delete):
1249
del self._byid[file_id]
1250
if ie.parent_id is not None:
1251
del self[ie.parent_id].children[ie.name]
1192
1255
def rename(self, file_id, new_parent_id, new_name):
1193
1256
"""Move a file within the inventory.
1195
1258
This can change either the name, or the parent, or both.
1197
This does not move the working file."""
1260
This does not move the working file.
1262
new_name = ensure_normalized_name(new_name)
1198
1263
if not is_valid_name(new_name):
1199
1264
raise BzrError("not an acceptable filename: %r" % new_name)
1228
1303
:param file_id: the file_id to use. if None, one will be created.
1230
1305
if file_id is None:
1231
file_id = bzrlib.workingtree.gen_file_id(name)
1232
if kind == 'directory':
1233
return InventoryDirectory(file_id, name, parent_id)
1234
elif kind == 'file':
1235
return InventoryFile(file_id, name, parent_id)
1236
elif kind == 'symlink':
1237
return InventoryLink(file_id, name, parent_id)
1306
file_id = generate_ids.gen_file_id(name)
1307
name = ensure_normalized_name(name)
1309
factory = entry_factory[kind]
1239
1311
raise BzrError("unknown kind %r" % kind)
1312
return factory(file_id, name, parent_id)
1315
def ensure_normalized_name(name):
1318
:raises InvalidNormalization: When name is not normalized, and cannot be
1319
accessed on this platform by the normalized path.
1320
:return: The NFC normalised version of name.
1322
#------- This has been copied to bzrlib.dirstate.DirState.add, please
1323
# keep them synchronised.
1324
# we dont import normalized_filename directly because we want to be
1325
# able to change the implementation at runtime for tests.
1326
norm_name, can_access = osutils.normalized_filename(name)
1327
if norm_name != name:
1331
# TODO: jam 20060701 This would probably be more useful
1332
# if the error was raised with the full path
1333
raise errors.InvalidNormalization(name)
1243
1337
_NAME_RE = None