~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-06-12 02:17:42 UTC
  • mfrom: (2521.1.1 56322)
  • Revision ID: pqm@pqm.ubuntu.com-20070612021742-uetsy3g747iq3xkk
mergeĀ initĀ --create-prefix

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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
27
27
# created, but it's not for now.
28
28
ROOT_ID = "TREE_ROOT"
29
29
 
30
 
 
31
 
import collections
32
 
import os.path
 
30
import os
33
31
import re
34
32
import sys
 
33
 
 
34
from bzrlib.lazy_import import lazy_import
 
35
lazy_import(globals(), """
 
36
import collections
35
37
import tarfile
36
 
import types
37
 
from warnings import warn
38
38
 
39
39
import bzrlib
40
 
from bzrlib import errors, osutils
41
 
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
42
 
                            pathjoin, sha_strings)
43
 
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
44
 
                           BzrError, BzrCheckError, BinaryFile)
 
40
from bzrlib import (
 
41
    errors,
 
42
    generate_ids,
 
43
    osutils,
 
44
    symbol_versioning,
 
45
    workingtree,
 
46
    )
 
47
""")
 
48
 
 
49
from bzrlib.errors import (
 
50
    BzrCheckError,
 
51
    BzrError,
 
52
    )
45
53
from bzrlib.trace import mutter
46
54
 
47
55
 
82
90
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
83
91
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
84
92
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
85
 
    >>> shouldbe = {0: '', 1: 'src', 2: pathjoin('src','hello.c')}
 
93
    >>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
86
94
    >>> for ix, j in enumerate(i.iter_entries()):
87
95
    ...   print (j[0] == shouldbe[ix], j[1])
88
96
    ... 
89
 
    (True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
 
97
    (True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
90
98
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
91
99
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
92
 
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
93
 
    Traceback (most recent call last):
94
 
    ...
95
 
    BzrError: inventory already contains entry with id {2323}
96
100
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
97
101
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
98
102
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
189
193
            if self.file_id in inv:
190
194
                ie = inv[self.file_id]
191
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
192
199
                if ie.revision in candidates:
193
200
                    # same revision value in two different inventories:
194
201
                    # correct possible inconsistencies:
246
253
 
247
254
    def get_tar_item(self, root, dp, now, tree):
248
255
        """Get a tarfile item and a file stream for its content."""
249
 
        item = tarfile.TarInfo(pathjoin(root, dp).encode('utf8'))
 
256
        item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
250
257
        # TODO: would be cool to actually set it to the timestamp of the
251
258
        # revision it was last changed
252
259
        item.mtime = now
281
288
        """
282
289
        assert isinstance(name, basestring), name
283
290
        if '/' in name or '\\' in name:
284
 
            raise InvalidEntryName(name=name)
 
291
            raise errors.InvalidEntryName(name=name)
285
292
        self.executable = False
286
293
        self.revision = None
287
294
        self.text_sha1 = None
288
295
        self.text_size = None
289
296
        self.file_id = file_id
 
297
        assert isinstance(file_id, (str, None.__class__)), \
 
298
            'bad type %r for %r' % (type(file_id), file_id)
290
299
        self.name = name
291
300
        self.text_id = text_id
292
301
        self.parent_id = parent_id
293
302
        self.symlink_target = None
 
303
        self.reference_revision = None
294
304
 
295
305
    def kind_character(self):
296
306
        """Return a short kind indicator useful for appending to names."""
311
321
        
312
322
        This is a template method - implement _put_on_disk in subclasses.
313
323
        """
314
 
        fullpath = pathjoin(dest, dp)
 
324
        fullpath = osutils.pathjoin(dest, dp)
315
325
        self._put_on_disk(fullpath, tree)
316
326
        # mutter("  export {%s} kind %s to %s", self.file_id,
317
327
        #         self.kind, fullpath)
325
335
 
326
336
    @staticmethod
327
337
    def versionable_kind(kind):
328
 
        return (kind in ('file', 'directory', 'symlink'))
 
338
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
329
339
 
330
340
    def check(self, checker, rev_id, inv, tree):
331
341
        """Check this inventory entry is intact.
376
386
            return 'added'
377
387
        elif new_entry is None:
378
388
            return 'removed'
 
389
        if old_entry.kind != new_entry.kind:
 
390
            return 'modified'
379
391
        text_modified, meta_modified = new_entry.detect_changes(old_entry)
380
392
        if text_modified or meta_modified:
381
393
            modified = True
461
473
                and (self.kind == other.kind)
462
474
                and (self.revision == other.revision)
463
475
                and (self.executable == other.executable)
 
476
                and (self.reference_revision == other.reference_revision)
464
477
                )
465
478
 
466
479
    def __ne__(self, other):
481
494
        # renamed
482
495
        elif previous_ie.name != self.name:
483
496
            compatible = False
 
497
        elif previous_ie.kind != self.kind:
 
498
            compatible = False
484
499
        return compatible
485
500
 
486
501
    def _read_tree_state(self, path, work_tree):
501
516
class RootEntry(InventoryEntry):
502
517
 
503
518
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
504
 
                 'text_id', 'parent_id', 'children', 'executable', 
505
 
                 'revision', 'symlink_target']
 
519
                 'text_id', 'parent_id', 'children', 'executable',
 
520
                 'revision', 'symlink_target', 'reference_revision']
506
521
 
507
522
    def _check(self, checker, rev_id, tree):
508
523
        """See InventoryEntry._check"""
514
529
        self.parent_id = None
515
530
        self.name = u''
516
531
        self.revision = None
517
 
        warn('RootEntry is deprecated as of bzr 0.10.  Please use '
518
 
             'InventoryDirectory instead.',
519
 
            DeprecationWarning, stacklevel=2)
 
532
        symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
 
533
                               '  Please use InventoryDirectory instead.',
 
534
                               DeprecationWarning, stacklevel=2)
520
535
 
521
536
    def __eq__(self, other):
522
537
        if not isinstance(other, RootEntry):
530
545
    """A directory in an inventory."""
531
546
 
532
547
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
533
 
                 'text_id', 'parent_id', 'children', 'executable', 
534
 
                 'revision', 'symlink_target']
 
548
                 'text_id', 'parent_id', 'children', 'executable',
 
549
                 'revision', 'symlink_target', 'reference_revision']
535
550
 
536
551
    def _check(self, checker, rev_id, tree):
537
552
        """See InventoryEntry._check"""
577
592
    """A file in an inventory."""
578
593
 
579
594
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
580
 
                 'text_id', 'parent_id', 'children', 'executable', 
581
 
                 'revision', 'symlink_target']
 
595
                 'text_id', 'parent_id', 'children', 'executable',
 
596
                 'revision', 'symlink_target', 'reference_revision']
582
597
 
583
598
    def _check(self, checker, tree_revision_id, tree):
584
599
        """See InventoryEntry._check"""
645
660
            else:
646
661
                text_diff(to_label, to_text,
647
662
                          from_label, from_text, output_to)
648
 
        except BinaryFile:
 
663
        except errors.BinaryFile:
649
664
            if reverse:
650
665
                label_pair = (to_label, from_label)
651
666
            else:
677
692
 
678
693
    def _put_on_disk(self, fullpath, tree):
679
694
        """See InventoryEntry._put_on_disk."""
680
 
        pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
 
695
        osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
681
696
        if tree.is_executable(self.file_id):
682
697
            os.chmod(fullpath, 0755)
683
698
 
725
740
    """A file in an inventory."""
726
741
 
727
742
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
728
 
                 'text_id', 'parent_id', 'children', 'executable', 
729
 
                 'revision', 'symlink_target']
 
743
                 'text_id', 'parent_id', 'children', 'executable',
 
744
                 'revision', 'symlink_target', 'reference_revision']
730
745
 
731
746
    def _check(self, checker, rev_id, tree):
732
747
        """See InventoryEntry._check"""
813
828
            self.file_id, file_parents, self.symlink_target)
814
829
 
815
830
 
 
831
class TreeReference(InventoryEntry):
 
832
    
 
833
    kind = 'tree-reference'
 
834
    
 
835
    def __init__(self, file_id, name, parent_id, revision=None,
 
836
                 reference_revision=None):
 
837
        InventoryEntry.__init__(self, file_id, name, parent_id)
 
838
        self.revision = revision
 
839
        self.reference_revision = reference_revision
 
840
 
 
841
    def copy(self):
 
842
        return TreeReference(self.file_id, self.name, self.parent_id,
 
843
                             self.revision, self.reference_revision)
 
844
 
 
845
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
846
        commit_builder.modified_reference(self.file_id, file_parents)
 
847
 
 
848
    def _read_tree_state(self, path, work_tree):
 
849
        """Populate fields in the inventory entry from the given tree.
 
850
        """
 
851
        self.reference_revision = work_tree.get_reference_revision(
 
852
            self.file_id, path)
 
853
 
 
854
    def _forget_tree_state(self):
 
855
        self.reference_revision = None 
 
856
 
 
857
 
816
858
class Inventory(object):
817
859
    """Inventory of versioned files in a tree.
818
860
 
849
891
    ['', u'hello.c']
850
892
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
851
893
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
 
894
    Traceback (most recent call last):
 
895
    BzrError: parent_id {TREE_ROOT} not in inventory
 
896
    >>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
852
897
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
853
898
    """
854
899
    def __init__(self, root_id=ROOT_ID, revision_id=None):
861
906
        The inventory is created with a default root directory, with
862
907
        an id of None.
863
908
        """
864
 
        # We are letting Branch.create() create a unique inventory
865
 
        # root id. Rather than generating a random one here.
866
 
        #if root_id is None:
867
 
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
868
909
        if root_id is not None:
869
 
            self._set_root(InventoryDirectory(root_id, '', None))
 
910
            assert root_id.__class__ == str
 
911
            self._set_root(InventoryDirectory(root_id, u'', None))
870
912
        else:
871
913
            self.root = None
872
914
            self._byid = {}
873
 
        # FIXME: this isn't ever used, changing it to self.revision may break
874
 
        # things. TODO make everything use self.revision_id
875
915
        self.revision_id = revision_id
876
916
 
877
917
    def _set_root(self, ie):
898
938
    def iter_entries(self, from_dir=None):
899
939
        """Return (path, entry) pairs, in order by name."""
900
940
        if from_dir is None:
901
 
            assert self.root
 
941
            if self.root is None:
 
942
                return
902
943
            from_dir = self.root
903
944
            yield '', self.root
904
945
        elif isinstance(from_dir, basestring):
938
979
                # if we finished all children, pop it off the stack
939
980
                stack.pop()
940
981
 
941
 
    def iter_entries_by_dir(self, from_dir=None):
 
982
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
942
983
        """Iterate over the entries in a directory first order.
943
984
 
944
985
        This returns all entries for a directory before returning
948
989
 
949
990
        :return: This yields (path, entry) pairs
950
991
        """
 
992
        if specific_file_ids:
 
993
            safe = osutils.safe_file_id
 
994
            specific_file_ids = set(safe(fid) for fid in specific_file_ids)
951
995
        # TODO? Perhaps this should return the from_dir so that the root is
952
996
        # yielded? or maybe an option?
953
997
        if from_dir is None:
954
 
            assert self.root
 
998
            if self.root is None:
 
999
                return
 
1000
            # Optimize a common case
 
1001
            if specific_file_ids is not None and len(specific_file_ids) == 1:
 
1002
                file_id = list(specific_file_ids)[0]
 
1003
                if file_id in self:
 
1004
                    yield self.id2path(file_id), self[file_id]
 
1005
                return 
955
1006
            from_dir = self.root
956
 
            yield '', self.root
 
1007
            if (specific_file_ids is None or 
 
1008
                self.root.file_id in specific_file_ids):
 
1009
                yield u'', self.root
957
1010
        elif isinstance(from_dir, basestring):
958
1011
            from_dir = self._byid[from_dir]
 
1012
 
 
1013
        if specific_file_ids is not None:
 
1014
            # TODO: jam 20070302 This could really be done as a loop rather
 
1015
            #       than a bunch of recursive calls.
 
1016
            parents = set()
 
1017
            byid = self._byid
 
1018
            def add_ancestors(file_id):
 
1019
                if file_id not in byid:
 
1020
                    return
 
1021
                parent_id = byid[file_id].parent_id
 
1022
                if parent_id is None:
 
1023
                    return
 
1024
                if parent_id not in parents:
 
1025
                    parents.add(parent_id)
 
1026
                    add_ancestors(parent_id)
 
1027
            for file_id in specific_file_ids:
 
1028
                add_ancestors(file_id)
 
1029
        else:
 
1030
            parents = None
959
1031
            
960
1032
        stack = [(u'', from_dir)]
961
1033
        while stack:
966
1038
 
967
1039
                child_relpath = cur_relpath + child_name
968
1040
 
969
 
                yield child_relpath, child_ie
 
1041
                if (specific_file_ids is None or 
 
1042
                    child_ie.file_id in specific_file_ids):
 
1043
                    yield child_relpath, child_ie
970
1044
 
971
1045
                if child_ie.kind == 'directory':
972
 
                    child_dirs.append((child_relpath+'/', child_ie))
 
1046
                    if parents is None or child_ie.file_id in parents:
 
1047
                        child_dirs.append((child_relpath+'/', child_ie))
973
1048
            stack.extend(reversed(child_dirs))
974
1049
 
975
1050
    def entries(self):
982
1057
            kids = dir_ie.children.items()
983
1058
            kids.sort()
984
1059
            for name, ie in kids:
985
 
                child_path = pathjoin(dir_path, name)
 
1060
                child_path = osutils.pathjoin(dir_path, name)
986
1061
                accum.append((child_path, ie))
987
1062
                if ie.kind == 'directory':
988
1063
                    descend(ie, child_path)
1001
1076
            kids.sort()
1002
1077
 
1003
1078
            for name, child_ie in kids:
1004
 
                child_path = pathjoin(parent_path, name)
 
1079
                child_path = osutils.pathjoin(parent_path, name)
1005
1080
                descend(child_ie, child_path)
1006
1081
        descend(self.root, u'')
1007
1082
        return accum
1017
1092
        >>> '456' in inv
1018
1093
        False
1019
1094
        """
 
1095
        file_id = osutils.safe_file_id(file_id)
1020
1096
        return (file_id in self._byid)
1021
1097
 
1022
1098
    def __getitem__(self, file_id):
1028
1104
        >>> inv['123123'].name
1029
1105
        'hello.c'
1030
1106
        """
 
1107
        file_id = osutils.safe_file_id(file_id)
1031
1108
        try:
1032
1109
            return self._byid[file_id]
1033
1110
        except KeyError:
1034
 
            if file_id is None:
1035
 
                raise BzrError("can't look up file_id None")
1036
 
            else:
1037
 
                raise BzrError("file_id {%s} not in inventory" % file_id)
 
1111
            # really we're passing an inventory, not a tree...
 
1112
            raise errors.NoSuchId(self, file_id)
1038
1113
 
1039
1114
    def get_file_kind(self, file_id):
 
1115
        file_id = osutils.safe_file_id(file_id)
1040
1116
        return self._byid[file_id].kind
1041
1117
 
1042
1118
    def get_child(self, parent_id, filename):
 
1119
        parent_id = osutils.safe_file_id(parent_id)
1043
1120
        return self[parent_id].children.get(filename)
1044
1121
 
 
1122
    def _add_child(self, entry):
 
1123
        """Add an entry to the inventory, without adding it to its parent"""
 
1124
        if entry.file_id in self._byid:
 
1125
            raise BzrError("inventory already contains entry with id {%s}" %
 
1126
                           entry.file_id)
 
1127
        self._byid[entry.file_id] = entry
 
1128
        for child in getattr(entry, 'children', {}).itervalues():
 
1129
            self._add_child(child)
 
1130
        return entry
 
1131
 
1045
1132
    def add(self, entry):
1046
1133
        """Add entry to inventory.
1047
1134
 
1051
1138
        Returns the new entry object.
1052
1139
        """
1053
1140
        if entry.file_id in self._byid:
1054
 
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
 
1141
            raise errors.DuplicateFileId(entry.file_id,
 
1142
                                         self._byid[entry.file_id])
1055
1143
 
1056
1144
        if entry.parent_id is None:
1057
1145
            assert self.root is None and len(self._byid) == 0
1058
 
            self._set_root(entry)
1059
 
            return entry
1060
 
        if entry.parent_id == ROOT_ID:
1061
 
            assert self.root is not None, self
1062
 
            entry.parent_id = self.root.file_id
1063
 
 
1064
 
        try:
1065
 
            parent = self._byid[entry.parent_id]
1066
 
        except KeyError:
1067
 
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
1068
 
 
1069
 
        if entry.name in parent.children:
1070
 
            raise BzrError("%s is already versioned" %
1071
 
                    pathjoin(self.id2path(parent.file_id), entry.name))
1072
 
 
1073
 
        self._byid[entry.file_id] = entry
1074
 
        parent.children[entry.name] = entry
1075
 
        return entry
 
1146
            self.root = entry
 
1147
        else:
 
1148
            try:
 
1149
                parent = self._byid[entry.parent_id]
 
1150
            except KeyError:
 
1151
                raise BzrError("parent_id {%s} not in inventory" %
 
1152
                               entry.parent_id)
 
1153
 
 
1154
            if entry.name in parent.children:
 
1155
                raise BzrError("%s is already versioned" %
 
1156
                        osutils.pathjoin(self.id2path(parent.file_id),
 
1157
                        entry.name).encode('utf-8'))
 
1158
            parent.children[entry.name] = entry
 
1159
        return self._add_child(entry)
1076
1160
 
1077
1161
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
1078
1162
        """Add entry from a path.
1085
1169
 
1086
1170
        if len(parts) == 0:
1087
1171
            if file_id is None:
1088
 
                file_id = bzrlib.workingtree.gen_root_id()
 
1172
                file_id = generate_ids.gen_root_id()
 
1173
            else:
 
1174
                file_id = osutils.safe_file_id(file_id)
1089
1175
            self.root = InventoryDirectory(file_id, '', None)
1090
1176
            self._byid = {self.root.file_id: self.root}
1091
 
            return
 
1177
            return self.root
1092
1178
        else:
1093
1179
            parent_path = parts[:-1]
1094
1180
            parent_id = self.path2id(parent_path)
1095
1181
            if parent_id is None:
1096
 
                raise NotVersionedError(path=parent_path)
 
1182
                raise errors.NotVersionedError(path=parent_path)
1097
1183
        ie = make_entry(kind, parts[-1], parent_id, file_id)
1098
1184
        return self.add(ie)
1099
1185
 
1109
1195
        >>> '123' in inv
1110
1196
        False
1111
1197
        """
 
1198
        file_id = osutils.safe_file_id(file_id)
1112
1199
        ie = self[file_id]
1113
1200
 
1114
1201
        assert ie.parent_id is None or \
1147
1234
 
1148
1235
    def _iter_file_id_parents(self, file_id):
1149
1236
        """Yield the parents of file_id up to the root."""
 
1237
        file_id = osutils.safe_file_id(file_id)
1150
1238
        while file_id is not None:
1151
1239
            try:
1152
1240
                ie = self._byid[file_id]
1153
1241
            except KeyError:
1154
 
                raise BzrError("file_id {%s} not found in inventory" % file_id)
 
1242
                raise errors.NoSuchId(tree=None, file_id=file_id)
1155
1243
            yield ie
1156
1244
            file_id = ie.parent_id
1157
1245
 
1163
1251
        is equal to the depth of the file in the tree, counting the
1164
1252
        root directory as depth 1.
1165
1253
        """
 
1254
        file_id = osutils.safe_file_id(file_id)
1166
1255
        p = []
1167
1256
        for parent in self._iter_file_id_parents(file_id):
1168
1257
            p.insert(0, parent.file_id)
1177
1266
        >>> print i.id2path('foo-id')
1178
1267
        src/foo.c
1179
1268
        """
 
1269
        file_id = osutils.safe_file_id(file_id)
1180
1270
        # get all names, skipping root
1181
1271
        return '/'.join(reversed(
1182
1272
            [parent.name for parent in 
1193
1283
 
1194
1284
        Returns None IFF the path is not found.
1195
1285
        """
1196
 
        if isinstance(name, types.StringTypes):
1197
 
            name = splitpath(name)
 
1286
        if isinstance(name, basestring):
 
1287
            name = osutils.splitpath(name)
1198
1288
 
1199
1289
        # mutter("lookup path %r" % name)
1200
1290
 
1201
1291
        parent = self.root
 
1292
        if parent is None:
 
1293
            return None
1202
1294
        for f in name:
1203
1295
            try:
1204
 
                cie = parent.children[f]
 
1296
                children = getattr(parent, 'children', None)
 
1297
                if children is None:
 
1298
                    return None
 
1299
                cie = children[f]
1205
1300
                assert cie.name == f
1206
1301
                assert cie.parent_id == parent.file_id
1207
1302
                parent = cie
1215
1310
        return bool(self.path2id(names))
1216
1311
 
1217
1312
    def has_id(self, file_id):
 
1313
        file_id = osutils.safe_file_id(file_id)
1218
1314
        return (file_id in self._byid)
1219
1315
 
1220
1316
    def remove_recursive_id(self, file_id):
1222
1318
        
1223
1319
        :param file_id: A file_id to remove.
1224
1320
        """
 
1321
        file_id = osutils.safe_file_id(file_id)
1225
1322
        to_find_delete = [self._byid[file_id]]
1226
1323
        to_delete = []
1227
1324
        while to_find_delete:
1232
1329
        for file_id in reversed(to_delete):
1233
1330
            ie = self[file_id]
1234
1331
            del self._byid[file_id]
1235
 
            if ie.parent_id is not None:
1236
 
                del self[ie.parent_id].children[ie.name]
 
1332
        if ie.parent_id is not None:
 
1333
            del self[ie.parent_id].children[ie.name]
 
1334
        else:
 
1335
            self.root = None
1237
1336
 
1238
1337
    def rename(self, file_id, new_parent_id, new_name):
1239
1338
        """Move a file within the inventory.
1240
1339
 
1241
1340
        This can change either the name, or the parent, or both.
1242
1341
 
1243
 
        This does not move the working file."""
 
1342
        This does not move the working file.
 
1343
        """
 
1344
        file_id = osutils.safe_file_id(file_id)
1244
1345
        if not is_valid_name(new_name):
1245
1346
            raise BzrError("not an acceptable filename: %r" % new_name)
1246
1347
 
1264
1365
        file_ie.name = new_name
1265
1366
        file_ie.parent_id = new_parent_id
1266
1367
 
 
1368
    def is_root(self, file_id):
 
1369
        file_id = osutils.safe_file_id(file_id)
 
1370
        return self.root is not None and file_id == self.root.file_id
 
1371
 
 
1372
 
 
1373
entry_factory = {
 
1374
    'directory': InventoryDirectory,
 
1375
    'file': InventoryFile,
 
1376
    'symlink': InventoryLink,
 
1377
    'tree-reference': TreeReference
 
1378
}
1267
1379
 
1268
1380
def make_entry(kind, name, parent_id, file_id=None):
1269
1381
    """Create an inventory entry.
1274
1386
    :param file_id: the file_id to use. if None, one will be created.
1275
1387
    """
1276
1388
    if file_id is None:
1277
 
        file_id = bzrlib.workingtree.gen_file_id(name)
 
1389
        file_id = generate_ids.gen_file_id(name)
 
1390
    else:
 
1391
        file_id = osutils.safe_file_id(file_id)
1278
1392
 
 
1393
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
 
1394
    # keep them synchronised.
 
1395
    # we dont import normalized_filename directly because we want to be
 
1396
    # able to change the implementation at runtime for tests.
1279
1397
    norm_name, can_access = osutils.normalized_filename(name)
1280
1398
    if norm_name != name:
1281
1399
        if can_access:
1285
1403
            #       if the error was raised with the full path
1286
1404
            raise errors.InvalidNormalization(name)
1287
1405
 
1288
 
    if kind == 'directory':
1289
 
        return InventoryDirectory(file_id, name, parent_id)
1290
 
    elif kind == 'file':
1291
 
        return InventoryFile(file_id, name, parent_id)
1292
 
    elif kind == 'symlink':
1293
 
        return InventoryLink(file_id, name, parent_id)
1294
 
    else:
 
1406
    try:
 
1407
        factory = entry_factory[kind]
 
1408
    except KeyError:
1295
1409
        raise BzrError("unknown kind %r" % kind)
 
1410
    return factory(file_id, name, parent_id)
1296
1411
 
1297
1412
 
1298
1413
_NAME_RE = None