~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: John Arbash Meinel
  • Date: 2007-05-04 18:59:36 UTC
  • mto: This revision was merged to the branch mainline in revision 2643.
  • Revision ID: john@arbash-meinel.com-20070504185936-1mjdoqmtz74xe5mg
A C implementation of _fields_to_entry_0_parents drops the time from 400ms to 330ms for a 21k-entry tree

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
142
141
        """
143
142
        return False, False
144
143
 
145
 
    @deprecated_method(symbol_versioning.one_zero)
146
144
    def diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
147
145
             output_to, reverse=False):
148
146
        """Perform a diff from this to to_entry.
163
161
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
164
162
             output_to, reverse=False):
165
163
        """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.
 
164
 
 
165
    def find_previous_heads(self, previous_inventories,
 
166
                            versioned_file_store,
 
167
                            transaction,
 
168
                            entry_vf=None):
 
169
        """Return the revisions and entries that directly precede this.
 
170
 
 
171
        Returned as a map from revision to inventory entry.
 
172
 
 
173
        This is a map containing the file revisions in all parents
 
174
        for which the file exists, and its revision is not a parent of
 
175
        any other. If the file is new, the set will be empty.
 
176
 
 
177
        :param versioned_file_store: A store where ancestry data on this
 
178
                                     file id can be queried.
 
179
        :param transaction: The transaction that queries to the versioned 
 
180
                            file store should be completed under.
 
181
        :param entry_vf: The entry versioned file, if its already available.
175
182
        """
 
183
        def get_ancestors(weave, entry):
 
184
            return set(weave.get_ancestry(entry.revision))
176
185
        # revision:ie mapping for each ie found in previous_inventories.
177
186
        candidates = {}
 
187
        # revision:ie mapping with one revision for each head.
 
188
        heads = {}
 
189
        # revision: ancestor list for each head
 
190
        head_ancestors = {}
178
191
        # identify candidate head revision ids.
179
192
        for inv in previous_inventories:
180
193
            if self.file_id in inv:
181
194
                ie = inv[self.file_id]
182
195
                assert ie.file_id == self.file_id
 
196
                if ie.kind != self.kind:
 
197
                    # Can't be a candidate if the kind has changed.
 
198
                    continue
183
199
                if ie.revision in candidates:
184
200
                    # same revision value in two different inventories:
185
201
                    # correct possible inconsistencies:
196
212
                else:
197
213
                    # add this revision as a candidate.
198
214
                    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 = {}
 
215
 
224
216
        # common case optimisation
225
217
        if len(candidates) == 1:
226
218
            # if there is only one candidate revision found
227
 
            # then we can avoid opening the versioned file to access ancestry:
 
219
            # then we can opening the versioned file to access ancestry:
228
220
            # there cannot be any ancestors to eliminate when there is 
229
221
            # 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().
 
222
            heads[ie.revision] = ie
 
223
            return heads
237
224
 
238
225
        # eliminate ancestors amongst the available candidates:
239
226
        # heads are those that are not an ancestor of any other candidate
240
227
        # - 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
228
        for ie in candidates.values():
246
229
            # may be an ancestor of a known head:
247
230
            already_present = 0 != len(
433
416
                   self.parent_id,
434
417
                   self.revision))
435
418
 
 
419
    def snapshot(self, revision, path, previous_entries,
 
420
                 work_tree, commit_builder):
 
421
        """Make a snapshot of this entry which may or may not have changed.
 
422
        
 
423
        This means that all its fields are populated, that it has its
 
424
        text stored in the text store or weave.
 
425
        """
 
426
        # mutter('new parents of %s are %r', path, previous_entries)
 
427
        self._read_tree_state(path, work_tree)
 
428
        # TODO: Where should we determine whether to reuse a
 
429
        # previous revision id or create a new revision? 20060606
 
430
        if len(previous_entries) == 1:
 
431
            # cannot be unchanged unless there is only one parent file rev.
 
432
            parent_ie = previous_entries.values()[0]
 
433
            if self._unchanged(parent_ie):
 
434
                # mutter("found unchanged entry")
 
435
                self.revision = parent_ie.revision
 
436
                return "unchanged"
 
437
        return self._snapshot_into_revision(revision, previous_entries, 
 
438
                                            work_tree, commit_builder)
 
439
 
 
440
    def _snapshot_into_revision(self, revision, previous_entries, work_tree,
 
441
                                commit_builder):
 
442
        """Record this revision unconditionally into a store.
 
443
 
 
444
        The entry's last-changed revision property (`revision`) is updated to 
 
445
        that of the new revision.
 
446
        
 
447
        :param revision: id of the new revision that is being recorded.
 
448
 
 
449
        :returns: String description of the commit (e.g. "merged", "modified"), etc.
 
450
        """
 
451
        # mutter('new revision {%s} for {%s}', revision, self.file_id)
 
452
        self.revision = revision
 
453
        self._snapshot_text(previous_entries, work_tree, commit_builder)
 
454
 
 
455
    def _snapshot_text(self, file_parents, work_tree, commit_builder): 
 
456
        """Record the 'text' of this entry, whatever form that takes.
 
457
        
 
458
        This default implementation simply adds an empty text.
 
459
        """
 
460
        raise NotImplementedError(self._snapshot_text)
 
461
 
436
462
    def __eq__(self, other):
437
463
        if not isinstance(other, InventoryEntry):
438
464
            return NotImplemented
557
583
        """See InventoryEntry._put_on_disk."""
558
584
        os.mkdir(fullpath)
559
585
 
 
586
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
587
        """See InventoryEntry._snapshot_text."""
 
588
        commit_builder.modified_directory(self.file_id, file_parents)
 
589
 
560
590
 
561
591
class InventoryFile(InventoryEntry):
562
592
    """A file in an inventory."""
571
601
        if t in checker.checked_texts:
572
602
            prev_sha = checker.checked_texts[t]
573
603
            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))
 
604
                raise BzrCheckError('mismatched sha1 on {%s} in {%s}' %
 
605
                                    (self.file_id, tree_revision_id))
578
606
            else:
579
607
                checker.repeated_text_cnt += 1
580
608
                return
581
609
 
582
610
        if self.file_id not in checker.checked_weaves:
583
611
            mutter('check weave {%s}', self.file_id)
584
 
            w = tree._get_weave(self.file_id)
 
612
            w = tree.get_weave(self.file_id)
585
613
            # Not passing a progress bar, because it creates a new
586
614
            # progress, which overwrites the current progress,
587
615
            # and doesn't look nice
588
616
            w.check()
589
617
            checker.checked_weaves[self.file_id] = True
590
618
        else:
591
 
            w = tree._get_weave(self.file_id)
 
619
            w = tree.get_weave(self.file_id)
592
620
 
593
621
        mutter('check version {%s} of {%s}', tree_revision_id, self.file_id)
594
622
        checker.checked_text_cnt += 1
620
648
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
621
649
             output_to, reverse=False):
622
650
        """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)
 
651
        try:
 
652
            from_text = tree.get_file(self.file_id).readlines()
 
653
            if to_entry:
 
654
                to_text = to_tree.get_file(to_entry.file_id).readlines()
 
655
            else:
 
656
                to_text = []
 
657
            if not reverse:
 
658
                text_diff(from_label, from_text,
 
659
                          to_label, to_text, output_to)
 
660
            else:
 
661
                text_diff(to_label, to_text,
 
662
                          from_label, from_text, output_to)
 
663
        except errors.BinaryFile:
 
664
            if reverse:
 
665
                label_pair = (to_label, from_label)
 
666
            else:
 
667
                label_pair = (from_label, to_label)
 
668
            print >> output_to, "Binary files %s and %s differ" % label_pair
636
669
 
637
670
    def has_text(self):
638
671
        """See InventoryEntry.has_text."""
682
715
    def _forget_tree_state(self):
683
716
        self.text_sha1 = None
684
717
 
 
718
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
719
        """See InventoryEntry._snapshot_text."""
 
720
        def get_content_byte_lines():
 
721
            return work_tree.get_file(self.file_id).readlines()
 
722
        self.text_sha1, self.text_size = commit_builder.modified_file_text(
 
723
            self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
 
724
 
685
725
    def _unchanged(self, previous_ie):
686
726
        """See InventoryEntry._unchanged."""
687
727
        compatible = super(InventoryFile, self)._unchanged(previous_ie)
730
770
    def _diff(self, text_diff, from_label, tree, to_label, to_entry, to_tree,
731
771
             output_to, reverse=False):
732
772
        """See InventoryEntry._diff."""
733
 
        from bzrlib.diff import DiffSymlink
734
 
        old_target = self.symlink_target
 
773
        from_text = self.symlink_target
735
774
        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)
 
775
            to_text = to_entry.symlink_target
 
776
            if reverse:
 
777
                temp = from_text
 
778
                from_text = to_text
 
779
                to_text = temp
 
780
            print >>output_to, '=== target changed %r => %r' % (from_text, to_text)
 
781
        else:
 
782
            if not reverse:
 
783
                print >>output_to, '=== target was %r' % self.symlink_target
 
784
            else:
 
785
                print >>output_to, '=== target is %r' % self.symlink_target
748
786
 
749
787
    def __init__(self, file_id, name, parent_id):
750
788
        super(InventoryLink, self).__init__(file_id, name, parent_id)
784
822
            compatible = False
785
823
        return compatible
786
824
 
 
825
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
826
        """See InventoryEntry._snapshot_text."""
 
827
        commit_builder.modified_link(
 
828
            self.file_id, file_parents, self.symlink_target)
 
829
 
787
830
 
788
831
class TreeReference(InventoryEntry):
789
832
    
799
842
        return TreeReference(self.file_id, self.name, self.parent_id,
800
843
                             self.revision, self.reference_revision)
801
844
 
 
845
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
846
        commit_builder.modified_reference(self.file_id, file_parents)
 
847
 
802
848
    def _read_tree_state(self, path, work_tree):
803
849
        """Populate fields in the inventory entry from the given tree.
804
850
        """
808
854
    def _forget_tree_state(self):
809
855
        self.reference_revision = None 
810
856
 
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
817
 
 
818
857
 
819
858
class Inventory(object):
820
859
    """Inventory of versioned files in a tree.
875
914
            self._byid = {}
876
915
        self.revision_id = revision_id
877
916
 
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
917
    def _set_root(self, ie):
942
918
        self.root = ie
943
919
        self._byid = {self.root.file_id: self.root}
945
921
    def copy(self):
946
922
        # TODO: jam 20051218 Should copy also copy the revision_id?
947
923
        entries = self.iter_entries()
948
 
        if self.root is None:
949
 
            return Inventory(root_id=None)
950
924
        other = Inventory(entries.next()[1].file_id)
951
925
        # copy recursively so we know directories will be added before
952
926
        # their children.  There are more efficient ways than this...
953
 
        for path, entry in entries:
 
927
        for path, entry in entries():
954
928
            other.add(entry.copy())
955
929
        return other
956
930
 
1005
979
                # if we finished all children, pop it off the stack
1006
980
                stack.pop()
1007
981
 
1008
 
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None,
1009
 
        yield_parents=False):
 
982
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
1010
983
        """Iterate over the entries in a directory first order.
1011
984
 
1012
985
        This returns all entries for a directory before returning
1014
987
        lexicographically sorted order, and is a hybrid between
1015
988
        depth-first and breadth-first.
1016
989
 
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
990
        :return: This yields (path, entry) pairs
1021
991
        """
1022
 
        if specific_file_ids and not isinstance(specific_file_ids, set):
1023
 
            specific_file_ids = set(specific_file_ids)
 
992
        if specific_file_ids:
 
993
            safe = osutils.safe_file_id
 
994
            specific_file_ids = set(safe(fid) for fid in specific_file_ids)
1024
995
        # TODO? Perhaps this should return the from_dir so that the root is
1025
996
        # yielded? or maybe an option?
1026
997
        if from_dir is None:
1027
998
            if self.root is None:
1028
999
                return
1029
1000
            # Optimize a common case
1030
 
            if (not yield_parents and specific_file_ids is not None and
1031
 
                len(specific_file_ids) == 1):
 
1001
            if specific_file_ids is not None and len(specific_file_ids) == 1:
1032
1002
                file_id = list(specific_file_ids)[0]
1033
1003
                if file_id in self:
1034
1004
                    yield self.id2path(file_id), self[file_id]
1035
1005
                return 
1036
1006
            from_dir = self.root
1037
 
            if (specific_file_ids is None or yield_parents or
 
1007
            if (specific_file_ids is None or 
1038
1008
                self.root.file_id in specific_file_ids):
1039
1009
                yield u'', self.root
1040
1010
        elif isinstance(from_dir, basestring):
1069
1039
                child_relpath = cur_relpath + child_name
1070
1040
 
1071
1041
                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)):
 
1042
                    child_ie.file_id in specific_file_ids):
1074
1043
                    yield child_relpath, child_ie
1075
1044
 
1076
1045
                if child_ie.kind == 'directory':
1078
1047
                        child_dirs.append((child_relpath+'/', child_ie))
1079
1048
            stack.extend(reversed(child_dirs))
1080
1049
 
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
1050
    def entries(self):
1086
1051
        """Return list of (path, ie) for all entries except the root.
1087
1052
 
1127
1092
        >>> '456' in inv
1128
1093
        False
1129
1094
        """
 
1095
        file_id = osutils.safe_file_id(file_id)
1130
1096
        return (file_id in self._byid)
1131
1097
 
1132
1098
    def __getitem__(self, file_id):
1138
1104
        >>> inv['123123'].name
1139
1105
        'hello.c'
1140
1106
        """
 
1107
        file_id = osutils.safe_file_id(file_id)
1141
1108
        try:
1142
1109
            return self._byid[file_id]
1143
1110
        except KeyError:
1145
1112
            raise errors.NoSuchId(self, file_id)
1146
1113
 
1147
1114
    def get_file_kind(self, file_id):
 
1115
        file_id = osutils.safe_file_id(file_id)
1148
1116
        return self._byid[file_id].kind
1149
1117
 
1150
1118
    def get_child(self, parent_id, filename):
 
1119
        parent_id = osutils.safe_file_id(parent_id)
1151
1120
        return self[parent_id].children.get(filename)
1152
1121
 
1153
1122
    def _add_child(self, entry):
1201
1170
        if len(parts) == 0:
1202
1171
            if file_id is None:
1203
1172
                file_id = generate_ids.gen_root_id()
 
1173
            else:
 
1174
                file_id = osutils.safe_file_id(file_id)
1204
1175
            self.root = InventoryDirectory(file_id, '', None)
1205
1176
            self._byid = {self.root.file_id: self.root}
1206
1177
            return self.root
1224
1195
        >>> '123' in inv
1225
1196
        False
1226
1197
        """
 
1198
        file_id = osutils.safe_file_id(file_id)
1227
1199
        ie = self[file_id]
1228
1200
 
1229
1201
        assert ie.parent_id is None or \
1262
1234
 
1263
1235
    def _iter_file_id_parents(self, file_id):
1264
1236
        """Yield the parents of file_id up to the root."""
 
1237
        file_id = osutils.safe_file_id(file_id)
1265
1238
        while file_id is not None:
1266
1239
            try:
1267
1240
                ie = self._byid[file_id]
1278
1251
        is equal to the depth of the file in the tree, counting the
1279
1252
        root directory as depth 1.
1280
1253
        """
 
1254
        file_id = osutils.safe_file_id(file_id)
1281
1255
        p = []
1282
1256
        for parent in self._iter_file_id_parents(file_id):
1283
1257
            p.insert(0, parent.file_id)
1292
1266
        >>> print i.id2path('foo-id')
1293
1267
        src/foo.c
1294
1268
        """
 
1269
        file_id = osutils.safe_file_id(file_id)
1295
1270
        # get all names, skipping root
1296
1271
        return '/'.join(reversed(
1297
1272
            [parent.name for parent in 
1335
1310
        return bool(self.path2id(names))
1336
1311
 
1337
1312
    def has_id(self, file_id):
 
1313
        file_id = osutils.safe_file_id(file_id)
1338
1314
        return (file_id in self._byid)
1339
1315
 
1340
1316
    def remove_recursive_id(self, file_id):
1342
1318
        
1343
1319
        :param file_id: A file_id to remove.
1344
1320
        """
 
1321
        file_id = osutils.safe_file_id(file_id)
1345
1322
        to_find_delete = [self._byid[file_id]]
1346
1323
        to_delete = []
1347
1324
        while to_find_delete:
1364
1341
 
1365
1342
        This does not move the working file.
1366
1343
        """
1367
 
        new_name = ensure_normalized_name(new_name)
 
1344
        file_id = osutils.safe_file_id(file_id)
1368
1345
        if not is_valid_name(new_name):
1369
1346
            raise BzrError("not an acceptable filename: %r" % new_name)
1370
1347
 
1389
1366
        file_ie.parent_id = new_parent_id
1390
1367
 
1391
1368
    def is_root(self, file_id):
 
1369
        file_id = osutils.safe_file_id(file_id)
1392
1370
        return self.root is not None and file_id == self.root.file_id
1393
1371
 
1394
1372
 
1409
1387
    """
1410
1388
    if file_id is None:
1411
1389
        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
 
    """
 
1390
    else:
 
1391
        file_id = osutils.safe_file_id(file_id)
 
1392
 
1427
1393
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
1428
1394
    # keep them synchronised.
1429
1395
    # we dont import normalized_filename directly because we want to be
1431
1397
    norm_name, can_access = osutils.normalized_filename(name)
1432
1398
    if norm_name != name:
1433
1399
        if can_access:
1434
 
            return norm_name
 
1400
            name = norm_name
1435
1401
        else:
1436
1402
            # TODO: jam 20060701 This would probably be more useful
1437
1403
            #       if the error was raised with the full path
1438
1404
            raise errors.InvalidNormalization(name)
1439
 
    return name
 
1405
 
 
1406
    try:
 
1407
        factory = entry_factory[kind]
 
1408
    except KeyError:
 
1409
        raise BzrError("unknown kind %r" % kind)
 
1410
    return factory(file_id, name, parent_id)
1440
1411
 
1441
1412
 
1442
1413
_NAME_RE = None