~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Alexander Belchenko
  • Date: 2007-01-30 23:05:35 UTC
  • mto: This revision was merged to the branch mainline in revision 2259.
  • Revision ID: bialix@ukr.net-20070130230535-kx1rd478rtigyc3v
standalone installer: win98 support

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
50
50
    BzrCheckError,
51
51
    BzrError,
52
52
    )
53
 
from bzrlib.symbol_versioning import deprecated_method
54
53
from bzrlib.trace import mutter
55
54
 
56
55
 
95
94
    >>> for ix, j in enumerate(i.iter_entries()):
96
95
    ...   print (j[0] == shouldbe[ix], j[1])
97
96
    ... 
98
 
    (True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
 
97
    (True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
99
98
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
100
99
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
 
100
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
 
101
    Traceback (most recent call last):
 
102
    ...
 
103
    BzrError: inventory already contains entry with id {2323}
101
104
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
102
105
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
103
106
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
142
145
        """
143
146
        return False, False
144
147
 
145
 
    @deprecated_method(symbol_versioning.one_zero)
146
148
    def diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
147
149
             output_to, reverse=False):
148
150
        """Perform a diff from this to to_entry.
163
165
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
164
166
             output_to, reverse=False):
165
167
        """Perform a diff between two entries of the same kind."""
166
 
    
167
 
    def parent_candidates(self, previous_inventories):
168
 
        """Find possible per-file graph parents.
169
 
 
170
 
        This is currently defined by:
171
 
         - Select the last changed revision in the parent inventory.
172
 
         - Do deal with a short lived bug in bzr 0.8's development two entries
173
 
           that have the same last changed but different 'x' bit settings are
174
 
           changed in-place.
 
168
 
 
169
    def find_previous_heads(self, previous_inventories,
 
170
                            versioned_file_store,
 
171
                            transaction,
 
172
                            entry_vf=None):
 
173
        """Return the revisions and entries that directly precede this.
 
174
 
 
175
        Returned as a map from revision to inventory entry.
 
176
 
 
177
        This is a map containing the file revisions in all parents
 
178
        for which the file exists, and its revision is not a parent of
 
179
        any other. If the file is new, the set will be empty.
 
180
 
 
181
        :param versioned_file_store: A store where ancestry data on this
 
182
                                     file id can be queried.
 
183
        :param transaction: The transaction that queries to the versioned 
 
184
                            file store should be completed under.
 
185
        :param entry_vf: The entry versioned file, if its already available.
175
186
        """
 
187
        def get_ancestors(weave, entry):
 
188
            return set(weave.get_ancestry(entry.revision))
176
189
        # revision:ie mapping for each ie found in previous_inventories.
177
190
        candidates = {}
 
191
        # revision:ie mapping with one revision for each head.
 
192
        heads = {}
 
193
        # revision: ancestor list for each head
 
194
        head_ancestors = {}
178
195
        # identify candidate head revision ids.
179
196
        for inv in previous_inventories:
180
197
            if self.file_id in inv:
196
213
                else:
197
214
                    # add this revision as a candidate.
198
215
                    candidates[ie.revision] = ie
199
 
        return candidates
200
 
 
201
 
    @deprecated_method(symbol_versioning.zero_ninetyone)
202
 
    def find_previous_heads(self, previous_inventories,
203
 
                            versioned_file_store,
204
 
                            transaction,
205
 
                            entry_vf=None):
206
 
        """Return the revisions and entries that directly precede this.
207
 
 
208
 
        Returned as a map from revision to inventory entry.
209
 
 
210
 
        This is a map containing the file revisions in all parents
211
 
        for which the file exists, and its revision is not a parent of
212
 
        any other. If the file is new, the set will be empty.
213
 
 
214
 
        :param versioned_file_store: A store where ancestry data on this
215
 
                                     file id can be queried.
216
 
        :param transaction: The transaction that queries to the versioned 
217
 
                            file store should be completed under.
218
 
        :param entry_vf: The entry versioned file, if its already available.
219
 
        """
220
 
        candidates = self.parent_candidates(previous_inventories)
221
 
 
222
 
        # revision:ie mapping with one revision for each head.
223
 
        heads = {}
 
216
 
224
217
        # common case optimisation
225
218
        if len(candidates) == 1:
226
219
            # if there is only one candidate revision found
227
 
            # then we can avoid opening the versioned file to access ancestry:
 
220
            # then we can opening the versioned file to access ancestry:
228
221
            # there cannot be any ancestors to eliminate when there is 
229
222
            # only one revision available.
230
 
            return candidates
231
 
        
232
 
        # --- what follows is now encapsulated in repository.get_graph.heads(), 
233
 
        #     but that is not accessible from here as we have no repository
234
 
        #     pointer. Note that the repository.get_graph.heads() call can return
235
 
        #     different results *at the moment* because of the kind-changing check
236
 
        #     we have in parent_candidates().
 
223
            heads[ie.revision] = ie
 
224
            return heads
237
225
 
238
226
        # eliminate ancestors amongst the available candidates:
239
227
        # heads are those that are not an ancestor of any other candidate
240
228
        # - this provides convergence at a per-file level.
241
 
        def get_ancestors(weave, entry):
242
 
            return set(weave.get_ancestry(entry.revision, topo_sorted=False))
243
 
        # revision: ancestor list for each head
244
 
        head_ancestors = {}
245
229
        for ie in candidates.values():
246
230
            # may be an ancestor of a known head:
247
231
            already_present = 0 != len(
311
295
        self.text_sha1 = None
312
296
        self.text_size = None
313
297
        self.file_id = file_id
314
 
        assert isinstance(file_id, (str, None.__class__)), \
315
 
            'bad type %r for %r' % (type(file_id), file_id)
316
298
        self.name = name
317
299
        self.text_id = text_id
318
300
        self.parent_id = parent_id
319
301
        self.symlink_target = None
320
 
        self.reference_revision = None
321
302
 
322
303
    def kind_character(self):
323
304
        """Return a short kind indicator useful for appending to names."""
352
333
 
353
334
    @staticmethod
354
335
    def versionable_kind(kind):
355
 
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
 
336
        return (kind in ('file', 'directory', 'symlink'))
356
337
 
357
338
    def check(self, checker, rev_id, inv, tree):
358
339
        """Check this inventory entry is intact.
403
384
            return 'added'
404
385
        elif new_entry is None:
405
386
            return 'removed'
406
 
        if old_entry.kind != new_entry.kind:
407
 
            return 'modified'
408
387
        text_modified, meta_modified = new_entry.detect_changes(old_entry)
409
388
        if text_modified or meta_modified:
410
389
            modified = True
433
412
                   self.parent_id,
434
413
                   self.revision))
435
414
 
 
415
    def snapshot(self, revision, path, previous_entries,
 
416
                 work_tree, commit_builder):
 
417
        """Make a snapshot of this entry which may or may not have changed.
 
418
        
 
419
        This means that all its fields are populated, that it has its
 
420
        text stored in the text store or weave.
 
421
        """
 
422
        # mutter('new parents of %s are %r', path, previous_entries)
 
423
        self._read_tree_state(path, work_tree)
 
424
        # TODO: Where should we determine whether to reuse a
 
425
        # previous revision id or create a new revision? 20060606
 
426
        if len(previous_entries) == 1:
 
427
            # cannot be unchanged unless there is only one parent file rev.
 
428
            parent_ie = previous_entries.values()[0]
 
429
            if self._unchanged(parent_ie):
 
430
                # mutter("found unchanged entry")
 
431
                self.revision = parent_ie.revision
 
432
                return "unchanged"
 
433
        return self._snapshot_into_revision(revision, previous_entries, 
 
434
                                            work_tree, commit_builder)
 
435
 
 
436
    def _snapshot_into_revision(self, revision, previous_entries, work_tree,
 
437
                                commit_builder):
 
438
        """Record this revision unconditionally into a store.
 
439
 
 
440
        The entry's last-changed revision property (`revision`) is updated to 
 
441
        that of the new revision.
 
442
        
 
443
        :param revision: id of the new revision that is being recorded.
 
444
 
 
445
        :returns: String description of the commit (e.g. "merged", "modified"), etc.
 
446
        """
 
447
        # mutter('new revision {%s} for {%s}', revision, self.file_id)
 
448
        self.revision = revision
 
449
        self._snapshot_text(previous_entries, work_tree, commit_builder)
 
450
 
 
451
    def _snapshot_text(self, file_parents, work_tree, commit_builder): 
 
452
        """Record the 'text' of this entry, whatever form that takes.
 
453
        
 
454
        This default implementation simply adds an empty text.
 
455
        """
 
456
        raise NotImplementedError(self._snapshot_text)
 
457
 
436
458
    def __eq__(self, other):
437
459
        if not isinstance(other, InventoryEntry):
438
460
            return NotImplemented
447
469
                and (self.kind == other.kind)
448
470
                and (self.revision == other.revision)
449
471
                and (self.executable == other.executable)
450
 
                and (self.reference_revision == other.reference_revision)
451
472
                )
452
473
 
453
474
    def __ne__(self, other):
468
489
        # renamed
469
490
        elif previous_ie.name != self.name:
470
491
            compatible = False
471
 
        elif previous_ie.kind != self.kind:
472
 
            compatible = False
473
492
        return compatible
474
493
 
475
494
    def _read_tree_state(self, path, work_tree):
490
509
class RootEntry(InventoryEntry):
491
510
 
492
511
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
493
 
                 'text_id', 'parent_id', 'children', 'executable',
494
 
                 'revision', 'symlink_target', 'reference_revision']
 
512
                 'text_id', 'parent_id', 'children', 'executable', 
 
513
                 'revision', 'symlink_target']
495
514
 
496
515
    def _check(self, checker, rev_id, tree):
497
516
        """See InventoryEntry._check"""
519
538
    """A directory in an inventory."""
520
539
 
521
540
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
522
 
                 'text_id', 'parent_id', 'children', 'executable',
523
 
                 'revision', 'symlink_target', 'reference_revision']
 
541
                 'text_id', 'parent_id', 'children', 'executable', 
 
542
                 'revision', 'symlink_target']
524
543
 
525
544
    def _check(self, checker, rev_id, tree):
526
545
        """See InventoryEntry._check"""
557
576
        """See InventoryEntry._put_on_disk."""
558
577
        os.mkdir(fullpath)
559
578
 
 
579
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
580
        """See InventoryEntry._snapshot_text."""
 
581
        commit_builder.modified_directory(self.file_id, file_parents)
 
582
 
560
583
 
561
584
class InventoryFile(InventoryEntry):
562
585
    """A file in an inventory."""
563
586
 
564
587
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
565
 
                 'text_id', 'parent_id', 'children', 'executable',
566
 
                 'revision', 'symlink_target', 'reference_revision']
 
588
                 'text_id', 'parent_id', 'children', 'executable', 
 
589
                 'revision', 'symlink_target']
567
590
 
568
591
    def _check(self, checker, tree_revision_id, tree):
569
592
        """See InventoryEntry._check"""
571
594
        if t in checker.checked_texts:
572
595
            prev_sha = checker.checked_texts[t]
573
596
            if prev_sha != self.text_sha1:
574
 
                raise BzrCheckError(
575
 
                    'mismatched sha1 on {%s} in {%s} (%s != %s) %r' %
576
 
                    (self.file_id, tree_revision_id, prev_sha, self.text_sha1,
577
 
                     t))
 
597
                raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
 
598
                                    (self.file_id, tree_revision_id))
578
599
            else:
579
600
                checker.repeated_text_cnt += 1
580
601
                return
581
602
 
582
603
        if self.file_id not in checker.checked_weaves:
583
604
            mutter('check weave {%s}', self.file_id)
584
 
            w = tree._get_weave(self.file_id)
 
605
            w = tree.get_weave(self.file_id)
585
606
            # Not passing a progress bar, because it creates a new
586
607
            # progress, which overwrites the current progress,
587
608
            # and doesn't look nice
588
609
            w.check()
589
610
            checker.checked_weaves[self.file_id] = True
590
611
        else:
591
 
            w = tree._get_weave(self.file_id)
 
612
            w = tree.get_weave(self.file_id)
592
613
 
593
614
        mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
594
615
        checker.checked_text_cnt += 1
620
641
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
621
642
             output_to, reverse=False):
622
643
        """See InventoryEntry._diff."""
623
 
        from bzrlib.diff import DiffText
624
 
        from_file_id = self.file_id
625
 
        if to_entry:
626
 
            to_file_id = to_entry.file_id
627
 
        else:
628
 
            to_file_id = None
629
 
        if reverse:
630
 
            to_file_id, from_file_id = from_file_id, to_file_id
631
 
            tree, to_tree = to_tree, tree
632
 
            from_label, to_label = to_label, from_label
633
 
        differ = DiffText(tree, to_tree, output_to, 'utf-8', '', '',
634
 
                          text_diff)
635
 
        return differ.diff_text(from_file_id, to_file_id, from_label, to_label)
 
644
        try:
 
645
            from_text = tree.get_file(self.file_id).readlines()
 
646
            if to_entry:
 
647
                to_text = to_tree.get_file(to_entry.file_id).readlines()
 
648
            else:
 
649
                to_text = []
 
650
            if not reverse:
 
651
                text_diff(from_label, from_text,
 
652
                          to_label, to_text, output_to)
 
653
            else:
 
654
                text_diff(to_label, to_text,
 
655
                          from_label, from_text, output_to)
 
656
        except errors.BinaryFile:
 
657
            if reverse:
 
658
                label_pair = (to_label, from_label)
 
659
            else:
 
660
                label_pair = (from_label, to_label)
 
661
            print >> output_to, "Binary files %s and %s differ" % label_pair
636
662
 
637
663
    def has_text(self):
638
664
        """See InventoryEntry.has_text."""
682
708
    def _forget_tree_state(self):
683
709
        self.text_sha1 = None
684
710
 
 
711
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
712
        """See InventoryEntry._snapshot_text."""
 
713
        def get_content_byte_lines():
 
714
            return work_tree.get_file(self.file_id).readlines()
 
715
        self.text_sha1, self.text_size = commit_builder.modified_file_text(
 
716
            self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
 
717
 
685
718
    def _unchanged(self, previous_ie):
686
719
        """See InventoryEntry._unchanged."""
687
720
        compatible = super(InventoryFile, self)._unchanged(previous_ie)
700
733
    """A file in an inventory."""
701
734
 
702
735
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
703
 
                 'text_id', 'parent_id', 'children', 'executable',
704
 
                 'revision', 'symlink_target', 'reference_revision']
 
736
                 'text_id', 'parent_id', 'children', 'executable', 
 
737
                 'revision', 'symlink_target']
705
738
 
706
739
    def _check(self, checker, rev_id, tree):
707
740
        """See InventoryEntry._check"""
730
763
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
731
764
             output_to, reverse=False):
732
765
        """See InventoryEntry._diff."""
733
 
        from bzrlib.diff import DiffSymlink
734
 
        old_target = self.symlink_target
 
766
        from_text = self.symlink_target
735
767
        if to_entry is not None:
736
 
            new_target = to_entry.symlink_target
737
 
        else:
738
 
            new_target = None
739
 
        if not reverse:
740
 
            old_tree = tree
741
 
            new_tree = to_tree
742
 
        else:
743
 
            old_tree = to_tree
744
 
            new_tree = tree
745
 
            new_target, old_target = old_target, new_target
746
 
        differ = DiffSymlink(old_tree, new_tree, output_to)
747
 
        return differ.diff_symlink(old_target, new_target)
 
768
            to_text = to_entry.symlink_target
 
769
            if reverse:
 
770
                temp = from_text
 
771
                from_text = to_text
 
772
                to_text = temp
 
773
            print >>output_to, '=== target changed %r => %r' % (from_text, to_text)
 
774
        else:
 
775
            if not reverse:
 
776
                print >>output_to, '=== target was %r' % self.symlink_target
 
777
            else:
 
778
                print >>output_to, '=== target is %r' % self.symlink_target
748
779
 
749
780
    def __init__(self, file_id, name, parent_id):
750
781
        super(InventoryLink, self).__init__(file_id, name, parent_id)
784
815
            compatible = False
785
816
        return compatible
786
817
 
787
 
 
788
 
class TreeReference(InventoryEntry):
789
 
    
790
 
    kind = 'tree-reference'
791
 
    
792
 
    def __init__(self, file_id, name, parent_id, revision=None,
793
 
                 reference_revision=None):
794
 
        InventoryEntry.__init__(self, file_id, name, parent_id)
795
 
        self.revision = revision
796
 
        self.reference_revision = reference_revision
797
 
 
798
 
    def copy(self):
799
 
        return TreeReference(self.file_id, self.name, self.parent_id,
800
 
                             self.revision, self.reference_revision)
801
 
 
802
 
    def _read_tree_state(self, path, work_tree):
803
 
        """Populate fields in the inventory entry from the given tree.
804
 
        """
805
 
        self.reference_revision = work_tree.get_reference_revision(
806
 
            self.file_id, path)
807
 
 
808
 
    def _forget_tree_state(self):
809
 
        self.reference_revision = None 
810
 
 
811
 
    def _unchanged(self, previous_ie):
812
 
        """See InventoryEntry._unchanged."""
813
 
        compatible = super(TreeReference, self)._unchanged(previous_ie)
814
 
        if self.reference_revision != previous_ie.reference_revision:
815
 
            compatible = False
816
 
        return compatible
 
818
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
819
        """See InventoryEntry._snapshot_text."""
 
820
        commit_builder.modified_link(
 
821
            self.file_id, file_parents, self.symlink_target)
817
822
 
818
823
 
819
824
class Inventory(object):
868
873
        an id of None.
869
874
        """
870
875
        if root_id is not None:
871
 
            assert root_id.__class__ == str
872
 
            self._set_root(InventoryDirectory(root_id, u'', None))
 
876
            self._set_root(InventoryDirectory(root_id, '', None))
873
877
        else:
874
878
            self.root = None
875
879
            self._byid = {}
876
880
        self.revision_id = revision_id
877
881
 
878
 
    def __repr__(self):
879
 
        return "<Inventory object at %x, contents=%r>" % (id(self), self._byid)
880
 
 
881
 
    def apply_delta(self, delta):
882
 
        """Apply a delta to this inventory.
883
 
 
884
 
        :param delta: A list of changes to apply. After all the changes are
885
 
            applied the final inventory must be internally consistent, but it
886
 
            is ok to supply changes which, if only half-applied would have an
887
 
            invalid result - such as supplying two changes which rename two
888
 
            files, 'A' and 'B' with each other : [('A', 'B', 'A-id', a_entry),
889
 
            ('B', 'A', 'B-id', b_entry)].
890
 
 
891
 
            Each change is a tuple, of the form (old_path, new_path, file_id,
892
 
            new_entry).
893
 
            
894
 
            When new_path is None, the change indicates the removal of an entry
895
 
            from the inventory and new_entry will be ignored (using None is
896
 
            appropriate). If new_path is not None, then new_entry must be an
897
 
            InventoryEntry instance, which will be incorporated into the
898
 
            inventory (and replace any existing entry with the same file id).
899
 
            
900
 
            When old_path is None, the change indicates the addition of
901
 
            a new entry to the inventory.
902
 
            
903
 
            When neither new_path nor old_path are None, the change is a
904
 
            modification to an entry, such as a rename, reparent, kind change
905
 
            etc. 
906
 
 
907
 
            The children attribute of new_entry is ignored. This is because
908
 
            this method preserves children automatically across alterations to
909
 
            the parent of the children, and cases where the parent id of a
910
 
            child is changing require the child to be passed in as a separate
911
 
            change regardless. E.g. in the recursive deletion of a directory -
912
 
            the directory's children must be included in the delta, or the
913
 
            final inventory will be invalid.
914
 
        """
915
 
        children = {}
916
 
        # Remove all affected items which were in the original inventory,
917
 
        # starting with the longest paths, thus ensuring parents are examined
918
 
        # after their children, which means that everything we examine has no
919
 
        # modified children remaining by the time we examine it.
920
 
        for old_path, file_id in sorted(((op, f) for op, np, f, e in delta
921
 
                                        if op is not None), reverse=True):
922
 
            if file_id not in self:
923
 
                # adds come later
924
 
                continue
925
 
            # Preserve unaltered children of file_id for later reinsertion.
926
 
            children[file_id] = getattr(self[file_id], 'children', {})
927
 
            # Remove file_id and the unaltered children. If file_id is not
928
 
            # being deleted it will be reinserted back later.
929
 
            self.remove_recursive_id(file_id)
930
 
        # Insert all affected which should be in the new inventory, reattaching
931
 
        # their children if they had any. This is done from shortest path to
932
 
        # longest, ensuring that items which were modified and whose parents in
933
 
        # the resulting inventory were also modified, are inserted after their
934
 
        # parents.
935
 
        for new_path, new_entry in sorted((np, e) for op, np, f, e in
936
 
                                          delta if np is not None):
937
 
            if new_entry.kind == 'directory':
938
 
                new_entry.children = children.get(new_entry.file_id, {})
939
 
            self.add(new_entry)
940
 
 
941
882
    def _set_root(self, ie):
942
883
        self.root = ie
943
884
        self._byid = {self.root.file_id: self.root}
945
886
    def copy(self):
946
887
        # TODO: jam 20051218 Should copy also copy the revision_id?
947
888
        entries = self.iter_entries()
948
 
        if self.root is None:
949
 
            return Inventory(root_id=None)
950
889
        other = Inventory(entries.next()[1].file_id)
951
890
        # copy recursively so we know directories will be added before
952
891
        # their children.  There are more efficient ways than this...
953
 
        for path, entry in entries:
 
892
        for path, entry in entries():
954
893
            other.add(entry.copy())
955
894
        return other
956
895
 
1005
944
                # if we finished all children, pop it off the stack
1006
945
                stack.pop()
1007
946
 
1008
 
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
1009
 
        yield_parents=False):
 
947
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
1010
948
        """Iterate over the entries in a directory first order.
1011
949
 
1012
950
        This returns all entries for a directory before returning
1014
952
        lexicographically sorted order, and is a hybrid between
1015
953
        depth-first and breadth-first.
1016
954
 
1017
 
        :param yield_parents: If True, yield the parents from the root leading
1018
 
            down to specific_file_ids that have been requested. This has no
1019
 
            impact if specific_file_ids is None.
1020
955
        :return: This yields (path, entry) pairs
1021
956
        """
1022
 
        if specific_file_ids and not isinstance(specific_file_ids, set):
1023
 
            specific_file_ids = set(specific_file_ids)
1024
957
        # TODO? Perhaps this should return the from_dir so that the root is
1025
958
        # yielded? or maybe an option?
1026
959
        if from_dir is None:
1027
960
            if self.root is None:
1028
961
                return
1029
962
            # Optimize a common case
1030
 
            if (not yield_parents and specific_file_ids is not None and
1031
 
                len(specific_file_ids) == 1):
 
963
            if specific_file_ids is not None and len(specific_file_ids) == 1:
1032
964
                file_id = list(specific_file_ids)[0]
1033
965
                if file_id in self:
1034
966
                    yield self.id2path(file_id), self[file_id]
1035
967
                return 
1036
968
            from_dir = self.root
1037
 
            if (specific_file_ids is None or yield_parents or
 
969
            if (specific_file_ids is None or 
1038
970
                self.root.file_id in specific_file_ids):
1039
 
                yield u'', self.root
 
971
                yield '', self.root
1040
972
        elif isinstance(from_dir, basestring):
1041
973
            from_dir = self._byid[from_dir]
1042
974
 
1043
975
        if specific_file_ids is not None:
1044
 
            # TODO: jam 20070302 This could really be done as a loop rather
1045
 
            #       than a bunch of recursive calls.
1046
976
            parents = set()
1047
 
            byid = self._byid
1048
977
            def add_ancestors(file_id):
1049
 
                if file_id not in byid:
 
978
                if file_id not in self:
1050
979
                    return
1051
 
                parent_id = byid[file_id].parent_id
 
980
                parent_id = self[file_id].parent_id
1052
981
                if parent_id is None:
1053
982
                    return
1054
983
                if parent_id not in parents:
1069
998
                child_relpath = cur_relpath + child_name
1070
999
 
1071
1000
                if (specific_file_ids is None or 
1072
 
                    child_ie.file_id in specific_file_ids or
1073
 
                    (yield_parents and child_ie.file_id in parents)):
 
1001
                    child_ie.file_id in specific_file_ids):
1074
1002
                    yield child_relpath, child_ie
1075
1003
 
1076
1004
                if child_ie.kind == 'directory':
1078
1006
                        child_dirs.append((child_relpath+'/', child_ie))
1079
1007
            stack.extend(reversed(child_dirs))
1080
1008
 
1081
 
    def make_entry(self, kind, name, parent_id, file_id=None):
1082
 
        """Simple thunk to bzrlib.inventory.make_entry."""
1083
 
        return make_entry(kind, name, parent_id, file_id)
1084
 
 
1085
1009
    def entries(self):
1086
1010
        """Return list of (path, ie) for all entries except the root.
1087
1011
 
1150
1074
    def get_child(self, parent_id, filename):
1151
1075
        return self[parent_id].children.get(filename)
1152
1076
 
1153
 
    def _add_child(self, entry):
1154
 
        """Add an entry to the inventory, without adding it to its parent"""
1155
 
        if entry.file_id in self._byid:
1156
 
            raise BzrError("inventory already contains entry with id {%s}" %
1157
 
                           entry.file_id)
1158
 
        self._byid[entry.file_id] = entry
1159
 
        for child in getattr(entry, 'children', {}).itervalues():
1160
 
            self._add_child(child)
1161
 
        return entry
1162
 
 
1163
1077
    def add(self, entry):
1164
1078
        """Add entry to inventory.
1165
1079
 
1169
1083
        Returns the new entry object.
1170
1084
        """
1171
1085
        if entry.file_id in self._byid:
1172
 
            raise errors.DuplicateFileId(entry.file_id,
1173
 
                                         self._byid[entry.file_id])
 
1086
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1174
1087
 
1175
1088
        if entry.parent_id is None:
1176
1089
            assert self.root is None and len(self._byid) == 0
1177
 
            self.root = entry
1178
 
        else:
1179
 
            try:
1180
 
                parent = self._byid[entry.parent_id]
1181
 
            except KeyError:
1182
 
                raise BzrError("parent_id {%s} not in inventory" %
1183
 
                               entry.parent_id)
1184
 
 
1185
 
            if entry.name in parent.children:
1186
 
                raise BzrError("%s is already versioned" %
1187
 
                        osutils.pathjoin(self.id2path(parent.file_id),
1188
 
                        entry.name).encode('utf-8'))
1189
 
            parent.children[entry.name] = entry
1190
 
        return self._add_child(entry)
 
1090
            self._set_root(entry)
 
1091
            return entry
 
1092
        try:
 
1093
            parent = self._byid[entry.parent_id]
 
1094
        except KeyError:
 
1095
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
 
1096
 
 
1097
        if entry.name in parent.children:
 
1098
            raise BzrError("%s is already versioned" %
 
1099
                    osutils.pathjoin(self.id2path(parent.file_id), entry.name))
 
1100
 
 
1101
        self._byid[entry.file_id] = entry
 
1102
        parent.children[entry.name] = entry
 
1103
        return entry
1191
1104
 
1192
1105
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
1193
1106
        """Add entry from a path.
1266
1179
            try:
1267
1180
                ie = self._byid[file_id]
1268
1181
            except KeyError:
1269
 
                raise errors.NoSuchId(tree=None, file_id=file_id)
 
1182
                raise BzrError("file_id {%s} not found in inventory" % file_id)
1270
1183
            yield ie
1271
1184
            file_id = ie.parent_id
1272
1185
 
1352
1265
        for file_id in reversed(to_delete):
1353
1266
            ie = self[file_id]
1354
1267
            del self._byid[file_id]
1355
 
        if ie.parent_id is not None:
1356
 
            del self[ie.parent_id].children[ie.name]
1357
 
        else:
1358
 
            self.root = None
 
1268
            if ie.parent_id is not None:
 
1269
                del self[ie.parent_id].children[ie.name]
1359
1270
 
1360
1271
    def rename(self, file_id, new_parent_id, new_name):
1361
1272
        """Move a file within the inventory.
1362
1273
 
1363
1274
        This can change either the name, or the parent, or both.
1364
1275
 
1365
 
        This does not move the working file.
1366
 
        """
1367
 
        new_name = ensure_normalized_name(new_name)
 
1276
        This does not move the working file."""
1368
1277
        if not is_valid_name(new_name):
1369
1278
            raise BzrError("not an acceptable filename: %r" % new_name)
1370
1279
 
1392
1301
        return self.root is not None and file_id == self.root.file_id
1393
1302
 
1394
1303
 
1395
 
entry_factory = {
1396
 
    'directory': InventoryDirectory,
1397
 
    'file': InventoryFile,
1398
 
    'symlink': InventoryLink,
1399
 
    'tree-reference': TreeReference
1400
 
}
1401
 
 
1402
1304
def make_entry(kind, name, parent_id, file_id=None):
1403
1305
    """Create an inventory entry.
1404
1306
 
1409
1311
    """
1410
1312
    if file_id is None:
1411
1313
        file_id = generate_ids.gen_file_id(name)
1412
 
    name = ensure_normalized_name(name)
1413
 
    try:
1414
 
        factory = entry_factory[kind]
1415
 
    except KeyError:
1416
 
        raise BzrError("unknown kind %r" % kind)
1417
 
    return factory(file_id, name, parent_id)
1418
 
 
1419
 
 
1420
 
def ensure_normalized_name(name):
1421
 
    """Normalize name.
1422
 
 
1423
 
    :raises InvalidNormalization: When name is not normalized, and cannot be
1424
 
        accessed on this platform by the normalized path.
1425
 
    :return: The NFC normalised version of name.
1426
 
    """
1427
 
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
1428
 
    # keep them synchronised.
1429
 
    # we dont import normalized_filename directly because we want to be
1430
 
    # able to change the implementation at runtime for tests.
 
1314
 
1431
1315
    norm_name, can_access = osutils.normalized_filename(name)
1432
1316
    if norm_name != name:
1433
1317
        if can_access:
1434
 
            return norm_name
 
1318
            name = norm_name
1435
1319
        else:
1436
1320
            # TODO: jam 20060701 This would probably be more useful
1437
1321
            #       if the error was raised with the full path
1438
1322
            raise errors.InvalidNormalization(name)
1439
 
    return name
 
1323
 
 
1324
    if kind == 'directory':
 
1325
        return InventoryDirectory(file_id, name, parent_id)
 
1326
    elif kind == 'file':
 
1327
        return InventoryFile(file_id, name, parent_id)
 
1328
    elif kind == 'symlink':
 
1329
        return InventoryLink(file_id, name, parent_id)
 
1330
    else:
 
1331
        raise BzrError("unknown kind %r" % kind)
1440
1332
 
1441
1333
 
1442
1334
_NAME_RE = None