~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: John Arbash Meinel
  • Date: 2006-10-11 00:23:23 UTC
  • mfrom: (2070 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2071.
  • Revision ID: john@arbash-meinel.com-20061011002323-82ba88c293d7caff
[merge] bzr.dev 2070

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
39
39
import bzrlib
40
40
from bzrlib import (
41
41
    errors,
42
 
    generate_ids,
43
42
    osutils,
44
43
    symbol_versioning,
45
 
    workingtree,
46
44
    )
47
45
""")
48
46
 
94
92
    >>> for ix, j in enumerate(i.iter_entries()):
95
93
    ...   print (j[0] == shouldbe[ix], j[1])
96
94
    ... 
97
 
    (True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
 
95
    (True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
98
96
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
99
97
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
 
98
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
 
99
    Traceback (most recent call last):
 
100
    ...
 
101
    BzrError: inventory already contains entry with id {2323}
100
102
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
101
103
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
102
104
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
291
293
        self.text_sha1 = None
292
294
        self.text_size = None
293
295
        self.file_id = file_id
294
 
        assert isinstance(file_id, (str, None.__class__)), \
295
 
            'bad type %r for %r' % (type(file_id), file_id)
296
296
        self.name = name
297
297
        self.text_id = text_id
298
298
        self.parent_id = parent_id
299
299
        self.symlink_target = None
300
 
        self.reference_revision = None
301
300
 
302
301
    def kind_character(self):
303
302
        """Return a short kind indicator useful for appending to names."""
332
331
 
333
332
    @staticmethod
334
333
    def versionable_kind(kind):
335
 
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
 
334
        return (kind in ('file', 'directory', 'symlink'))
336
335
 
337
336
    def check(self, checker, rev_id, inv, tree):
338
337
        """Check this inventory entry is intact.
383
382
            return 'added'
384
383
        elif new_entry is None:
385
384
            return 'removed'
386
 
        if old_entry.kind != new_entry.kind:
387
 
            return 'modified'
388
385
        text_modified, meta_modified = new_entry.detect_changes(old_entry)
389
386
        if text_modified or meta_modified:
390
387
            modified = True
470
467
                and (self.kind == other.kind)
471
468
                and (self.revision == other.revision)
472
469
                and (self.executable == other.executable)
473
 
                and (self.reference_revision == other.reference_revision)
474
470
                )
475
471
 
476
472
    def __ne__(self, other):
511
507
class RootEntry(InventoryEntry):
512
508
 
513
509
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
514
 
                 'text_id', 'parent_id', 'children', 'executable',
515
 
                 'revision', 'symlink_target', 'reference_revision']
 
510
                 'text_id', 'parent_id', 'children', 'executable', 
 
511
                 'revision', 'symlink_target']
516
512
 
517
513
    def _check(self, checker, rev_id, tree):
518
514
        """See InventoryEntry._check"""
540
536
    """A directory in an inventory."""
541
537
 
542
538
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
543
 
                 'text_id', 'parent_id', 'children', 'executable',
544
 
                 'revision', 'symlink_target', 'reference_revision']
 
539
                 'text_id', 'parent_id', 'children', 'executable', 
 
540
                 'revision', 'symlink_target']
545
541
 
546
542
    def _check(self, checker, rev_id, tree):
547
543
        """See InventoryEntry._check"""
587
583
    """A file in an inventory."""
588
584
 
589
585
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
590
 
                 'text_id', 'parent_id', 'children', 'executable',
591
 
                 'revision', 'symlink_target', 'reference_revision']
 
586
                 'text_id', 'parent_id', 'children', 'executable', 
 
587
                 'revision', 'symlink_target']
592
588
 
593
589
    def _check(self, checker, tree_revision_id, tree):
594
590
        """See InventoryEntry._check"""
735
731
    """A file in an inventory."""
736
732
 
737
733
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
738
 
                 'text_id', 'parent_id', 'children', 'executable',
739
 
                 'revision', 'symlink_target', 'reference_revision']
 
734
                 'text_id', 'parent_id', 'children', 'executable', 
 
735
                 'revision', 'symlink_target']
740
736
 
741
737
    def _check(self, checker, rev_id, tree):
742
738
        """See InventoryEntry._check"""
823
819
            self.file_id, file_parents, self.symlink_target)
824
820
 
825
821
 
826
 
class TreeReference(InventoryEntry):
827
 
    
828
 
    kind = 'tree-reference'
829
 
    
830
 
    def __init__(self, file_id, name, parent_id, revision=None,
831
 
                 reference_revision=None):
832
 
        InventoryEntry.__init__(self, file_id, name, parent_id)
833
 
        self.revision = revision
834
 
        self.reference_revision = reference_revision
835
 
 
836
 
    def copy(self):
837
 
        return TreeReference(self.file_id, self.name, self.parent_id,
838
 
                             self.revision, self.reference_revision)
839
 
 
840
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
841
 
        commit_builder.modified_reference(self.file_id, file_parents)
842
 
 
843
 
    def _read_tree_state(self, path, work_tree):
844
 
        """Populate fields in the inventory entry from the given tree.
845
 
        """
846
 
        self.reference_revision = work_tree.get_reference_revision(
847
 
            self.file_id, path)
848
 
 
849
 
    def _forget_tree_state(self):
850
 
        self.reference_revision = None 
851
 
 
852
 
 
853
822
class Inventory(object):
854
823
    """Inventory of versioned files in a tree.
855
824
 
886
855
    ['', u'hello.c']
887
856
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
888
857
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
889
 
    Traceback (most recent call last):
890
 
    BzrError: parent_id {TREE_ROOT} not in inventory
891
 
    >>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
892
858
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
893
859
    """
894
860
    def __init__(self, root_id=ROOT_ID, revision_id=None):
901
867
        The inventory is created with a default root directory, with
902
868
        an id of None.
903
869
        """
 
870
        # We are letting Branch.create() create a unique inventory
 
871
        # root id. Rather than generating a random one here.
 
872
        #if root_id is None:
 
873
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
904
874
        if root_id is not None:
905
 
            assert root_id.__class__ == str
906
 
            self._set_root(InventoryDirectory(root_id, u'', None))
 
875
            self._set_root(InventoryDirectory(root_id, '', None))
907
876
        else:
908
877
            self.root = None
909
878
            self._byid = {}
 
879
        # FIXME: this isn't ever used, changing it to self.revision may break
 
880
        # things. TODO make everything use self.revision_id
910
881
        self.revision_id = revision_id
911
882
 
912
883
    def _set_root(self, ie):
933
904
    def iter_entries(self, from_dir=None):
934
905
        """Return (path, entry) pairs, in order by name."""
935
906
        if from_dir is None:
936
 
            if self.root is None:
937
 
                return
 
907
            assert self.root
938
908
            from_dir = self.root
939
909
            yield '', self.root
940
910
        elif isinstance(from_dir, basestring):
974
944
                # if we finished all children, pop it off the stack
975
945
                stack.pop()
976
946
 
977
 
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
 
947
    def iter_entries_by_dir(self, from_dir=None):
978
948
        """Iterate over the entries in a directory first order.
979
949
 
980
950
        This returns all entries for a directory before returning
984
954
 
985
955
        :return: This yields (path, entry) pairs
986
956
        """
987
 
        if specific_file_ids:
988
 
            safe = osutils.safe_file_id
989
 
            specific_file_ids = set(safe(fid) for fid in specific_file_ids)
990
957
        # TODO? Perhaps this should return the from_dir so that the root is
991
958
        # yielded? or maybe an option?
992
959
        if from_dir is None:
993
 
            if self.root is None:
994
 
                return
995
 
            # Optimize a common case
996
 
            if specific_file_ids is not None and len(specific_file_ids) == 1:
997
 
                file_id = list(specific_file_ids)[0]
998
 
                if file_id in self:
999
 
                    yield self.id2path(file_id), self[file_id]
1000
 
                return 
 
960
            assert self.root
1001
961
            from_dir = self.root
1002
 
            if (specific_file_ids is None or 
1003
 
                self.root.file_id in specific_file_ids):
1004
 
                yield u'', self.root
 
962
            yield '', self.root
1005
963
        elif isinstance(from_dir, basestring):
1006
964
            from_dir = self._byid[from_dir]
1007
 
 
1008
 
        if specific_file_ids is not None:
1009
 
            # TODO: jam 20070302 This could really be done as a loop rather
1010
 
            #       than a bunch of recursive calls.
1011
 
            parents = set()
1012
 
            byid = self._byid
1013
 
            def add_ancestors(file_id):
1014
 
                if file_id not in byid:
1015
 
                    return
1016
 
                parent_id = byid[file_id].parent_id
1017
 
                if parent_id is None:
1018
 
                    return
1019
 
                if parent_id not in parents:
1020
 
                    parents.add(parent_id)
1021
 
                    add_ancestors(parent_id)
1022
 
            for file_id in specific_file_ids:
1023
 
                add_ancestors(file_id)
1024
 
        else:
1025
 
            parents = None
1026
965
            
1027
966
        stack = [(u'', from_dir)]
1028
967
        while stack:
1033
972
 
1034
973
                child_relpath = cur_relpath + child_name
1035
974
 
1036
 
                if (specific_file_ids is None or 
1037
 
                    child_ie.file_id in specific_file_ids):
1038
 
                    yield child_relpath, child_ie
 
975
                yield child_relpath, child_ie
1039
976
 
1040
977
                if child_ie.kind == 'directory':
1041
 
                    if parents is None or child_ie.file_id in parents:
1042
 
                        child_dirs.append((child_relpath+'/', child_ie))
 
978
                    child_dirs.append((child_relpath+'/', child_ie))
1043
979
            stack.extend(reversed(child_dirs))
1044
980
 
1045
981
    def entries(self):
1087
1023
        >>> '456' in inv
1088
1024
        False
1089
1025
        """
1090
 
        file_id = osutils.safe_file_id(file_id)
1091
1026
        return (file_id in self._byid)
1092
1027
 
1093
1028
    def __getitem__(self, file_id):
1099
1034
        >>> inv['123123'].name
1100
1035
        'hello.c'
1101
1036
        """
1102
 
        file_id = osutils.safe_file_id(file_id)
1103
1037
        try:
1104
1038
            return self._byid[file_id]
1105
1039
        except KeyError:
1106
 
            # really we're passing an inventory, not a tree...
1107
 
            raise errors.NoSuchId(self, file_id)
 
1040
            if file_id is None:
 
1041
                raise BzrError("can't look up file_id None")
 
1042
            else:
 
1043
                raise BzrError("file_id {%s} not in inventory" % file_id)
1108
1044
 
1109
1045
    def get_file_kind(self, file_id):
1110
 
        file_id = osutils.safe_file_id(file_id)
1111
1046
        return self._byid[file_id].kind
1112
1047
 
1113
1048
    def get_child(self, parent_id, filename):
1114
 
        parent_id = osutils.safe_file_id(parent_id)
1115
1049
        return self[parent_id].children.get(filename)
1116
1050
 
1117
 
    def _add_child(self, entry):
1118
 
        """Add an entry to the inventory, without adding it to its parent"""
1119
 
        if entry.file_id in self._byid:
1120
 
            raise BzrError("inventory already contains entry with id {%s}" %
1121
 
                           entry.file_id)
1122
 
        self._byid[entry.file_id] = entry
1123
 
        for child in getattr(entry, 'children', {}).itervalues():
1124
 
            self._add_child(child)
1125
 
        return entry
1126
 
 
1127
1051
    def add(self, entry):
1128
1052
        """Add entry to inventory.
1129
1053
 
1133
1057
        Returns the new entry object.
1134
1058
        """
1135
1059
        if entry.file_id in self._byid:
1136
 
            raise errors.DuplicateFileId(entry.file_id,
1137
 
                                         self._byid[entry.file_id])
 
1060
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1138
1061
 
1139
1062
        if entry.parent_id is None:
1140
1063
            assert self.root is None and len(self._byid) == 0
1141
 
            self.root = entry
1142
 
        else:
1143
 
            try:
1144
 
                parent = self._byid[entry.parent_id]
1145
 
            except KeyError:
1146
 
                raise BzrError("parent_id {%s} not in inventory" %
1147
 
                               entry.parent_id)
1148
 
 
1149
 
            if entry.name in parent.children:
1150
 
                raise BzrError("%s is already versioned" %
1151
 
                        osutils.pathjoin(self.id2path(parent.file_id),
1152
 
                        entry.name))
1153
 
            parent.children[entry.name] = entry
1154
 
        return self._add_child(entry)
 
1064
            self._set_root(entry)
 
1065
            return entry
 
1066
        if entry.parent_id == ROOT_ID:
 
1067
            assert self.root is not None, self
 
1068
            entry.parent_id = self.root.file_id
 
1069
 
 
1070
        try:
 
1071
            parent = self._byid[entry.parent_id]
 
1072
        except KeyError:
 
1073
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
 
1074
 
 
1075
        if entry.name in parent.children:
 
1076
            raise BzrError("%s is already versioned" %
 
1077
                    osutils.pathjoin(self.id2path(parent.file_id), entry.name))
 
1078
 
 
1079
        self._byid[entry.file_id] = entry
 
1080
        parent.children[entry.name] = entry
 
1081
        return entry
1155
1082
 
1156
1083
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
1157
1084
        """Add entry from a path.
1164
1091
 
1165
1092
        if len(parts) == 0:
1166
1093
            if file_id is None:
1167
 
                file_id = generate_ids.gen_root_id()
1168
 
            else:
1169
 
                file_id = osutils.safe_file_id(file_id)
 
1094
                file_id = bzrlib.workingtree.gen_root_id()
1170
1095
            self.root = InventoryDirectory(file_id, '', None)
1171
1096
            self._byid = {self.root.file_id: self.root}
1172
 
            return self.root
 
1097
            return
1173
1098
        else:
1174
1099
            parent_path = parts[:-1]
1175
1100
            parent_id = self.path2id(parent_path)
1190
1115
        >>> '123' in inv
1191
1116
        False
1192
1117
        """
1193
 
        file_id = osutils.safe_file_id(file_id)
1194
1118
        ie = self[file_id]
1195
1119
 
1196
1120
        assert ie.parent_id is None or \
1229
1153
 
1230
1154
    def _iter_file_id_parents(self, file_id):
1231
1155
        """Yield the parents of file_id up to the root."""
1232
 
        file_id = osutils.safe_file_id(file_id)
1233
1156
        while file_id is not None:
1234
1157
            try:
1235
1158
                ie = self._byid[file_id]
1236
1159
            except KeyError:
1237
 
                raise errors.NoSuchId(tree=None, file_id=file_id)
 
1160
                raise BzrError("file_id {%s} not found in inventory" % file_id)
1238
1161
            yield ie
1239
1162
            file_id = ie.parent_id
1240
1163
 
1246
1169
        is equal to the depth of the file in the tree, counting the
1247
1170
        root directory as depth 1.
1248
1171
        """
1249
 
        file_id = osutils.safe_file_id(file_id)
1250
1172
        p = []
1251
1173
        for parent in self._iter_file_id_parents(file_id):
1252
1174
            p.insert(0, parent.file_id)
1261
1183
        >>> print i.id2path('foo-id')
1262
1184
        src/foo.c
1263
1185
        """
1264
 
        file_id = osutils.safe_file_id(file_id)
1265
1186
        # get all names, skipping root
1266
1187
        return '/'.join(reversed(
1267
1188
            [parent.name for parent in 
1284
1205
        # mutter("lookup path %r" % name)
1285
1206
 
1286
1207
        parent = self.root
1287
 
        if parent is None:
1288
 
            return None
1289
1208
        for f in name:
1290
1209
            try:
1291
 
                children = getattr(parent, 'children', None)
1292
 
                if children is None:
1293
 
                    return None
1294
 
                cie = children[f]
 
1210
                cie = parent.children[f]
1295
1211
                assert cie.name == f
1296
1212
                assert cie.parent_id == parent.file_id
1297
1213
                parent = cie
1305
1221
        return bool(self.path2id(names))
1306
1222
 
1307
1223
    def has_id(self, file_id):
1308
 
        file_id = osutils.safe_file_id(file_id)
1309
1224
        return (file_id in self._byid)
1310
1225
 
1311
1226
    def remove_recursive_id(self, file_id):
1313
1228
        
1314
1229
        :param file_id: A file_id to remove.
1315
1230
        """
1316
 
        file_id = osutils.safe_file_id(file_id)
1317
1231
        to_find_delete = [self._byid[file_id]]
1318
1232
        to_delete = []
1319
1233
        while to_find_delete:
1324
1238
        for file_id in reversed(to_delete):
1325
1239
            ie = self[file_id]
1326
1240
            del self._byid[file_id]
1327
 
        if ie.parent_id is not None:
1328
 
            del self[ie.parent_id].children[ie.name]
 
1241
            if ie.parent_id is not None:
 
1242
                del self[ie.parent_id].children[ie.name]
1329
1243
 
1330
1244
    def rename(self, file_id, new_parent_id, new_name):
1331
1245
        """Move a file within the inventory.
1332
1246
 
1333
1247
        This can change either the name, or the parent, or both.
1334
1248
 
1335
 
        This does not move the working file.
1336
 
        """
1337
 
        file_id = osutils.safe_file_id(file_id)
 
1249
        This does not move the working file."""
1338
1250
        if not is_valid_name(new_name):
1339
1251
            raise BzrError("not an acceptable filename: %r" % new_name)
1340
1252
 
1358
1270
        file_ie.name = new_name
1359
1271
        file_ie.parent_id = new_parent_id
1360
1272
 
1361
 
    def is_root(self, file_id):
1362
 
        file_id = osutils.safe_file_id(file_id)
1363
 
        return self.root is not None and file_id == self.root.file_id
1364
 
 
1365
 
 
1366
 
entry_factory = {
1367
 
    'directory': InventoryDirectory,
1368
 
    'file': InventoryFile,
1369
 
    'symlink': InventoryLink,
1370
 
    'tree-reference': TreeReference
1371
 
}
1372
1273
 
1373
1274
def make_entry(kind, name, parent_id, file_id=None):
1374
1275
    """Create an inventory entry.
1379
1280
    :param file_id: the file_id to use. if None, one will be created.
1380
1281
    """
1381
1282
    if file_id is None:
1382
 
        file_id = generate_ids.gen_file_id(name)
1383
 
    else:
1384
 
        file_id = osutils.safe_file_id(file_id)
 
1283
        file_id = bzrlib.workingtree.gen_file_id(name)
1385
1284
 
1386
 
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
1387
 
    # keep them synchronised.
1388
 
    # we dont import normalized_filename directly because we want to be
1389
 
    # able to change the implementation at runtime for tests.
1390
1285
    norm_name, can_access = osutils.normalized_filename(name)
1391
1286
    if norm_name != name:
1392
1287
        if can_access:
1396
1291
            #       if the error was raised with the full path
1397
1292
            raise errors.InvalidNormalization(name)
1398
1293
 
1399
 
    try:
1400
 
        factory = entry_factory[kind]
1401
 
    except KeyError:
 
1294
    if kind == 'directory':
 
1295
        return InventoryDirectory(file_id, name, parent_id)
 
1296
    elif kind == 'file':
 
1297
        return InventoryFile(file_id, name, parent_id)
 
1298
    elif kind == 'symlink':
 
1299
        return InventoryLink(file_id, name, parent_id)
 
1300
    else:
1402
1301
        raise BzrError("unknown kind %r" % kind)
1403
 
    return factory(file_id, name, parent_id)
1404
1302
 
1405
1303
 
1406
1304
_NAME_RE = None