~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Aaron Bentley
  • Date: 2006-10-25 15:07:21 UTC
  • mfrom: (2095 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2098.
  • Revision ID: abentley@panoramicfeedback.com-20061025150721-71290b10eff4e691
Merge from bzr.dev

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'))
193
195
            if self.file_id in inv:
194
196
                ie = inv[self.file_id]
195
197
                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
199
198
                if ie.revision in candidates:
200
199
                    # same revision value in two different inventories:
201
200
                    # correct possible inconsistencies:
294
293
        self.text_sha1 = None
295
294
        self.text_size = None
296
295
        self.file_id = file_id
297
 
        assert isinstance(file_id, (str, None.__class__)), \
298
 
            'bad type %r for %r' % (type(file_id), file_id)
299
296
        self.name = name
300
297
        self.text_id = text_id
301
298
        self.parent_id = parent_id
302
299
        self.symlink_target = None
303
 
        self.reference_revision = None
304
300
 
305
301
    def kind_character(self):
306
302
        """Return a short kind indicator useful for appending to names."""
335
331
 
336
332
    @staticmethod
337
333
    def versionable_kind(kind):
338
 
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
 
334
        return (kind in ('file', 'directory', 'symlink'))
339
335
 
340
336
    def check(self, checker, rev_id, inv, tree):
341
337
        """Check this inventory entry is intact.
386
382
            return 'added'
387
383
        elif new_entry is None:
388
384
            return 'removed'
389
 
        if old_entry.kind != new_entry.kind:
390
 
            return 'modified'
391
385
        text_modified, meta_modified = new_entry.detect_changes(old_entry)
392
386
        if text_modified or meta_modified:
393
387
            modified = True
473
467
                and (self.kind == other.kind)
474
468
                and (self.revision == other.revision)
475
469
                and (self.executable == other.executable)
476
 
                and (self.reference_revision == other.reference_revision)
477
470
                )
478
471
 
479
472
    def __ne__(self, other):
494
487
        # renamed
495
488
        elif previous_ie.name != self.name:
496
489
            compatible = False
497
 
        elif previous_ie.kind != self.kind:
498
 
            compatible = False
499
490
        return compatible
500
491
 
501
492
    def _read_tree_state(self, path, work_tree):
516
507
class RootEntry(InventoryEntry):
517
508
 
518
509
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
519
 
                 'text_id', 'parent_id', 'children', 'executable',
520
 
                 'revision', 'symlink_target', 'reference_revision']
 
510
                 'text_id', 'parent_id', 'children', 'executable', 
 
511
                 'revision', 'symlink_target']
521
512
 
522
513
    def _check(self, checker, rev_id, tree):
523
514
        """See InventoryEntry._check"""
545
536
    """A directory in an inventory."""
546
537
 
547
538
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
548
 
                 'text_id', 'parent_id', 'children', 'executable',
549
 
                 'revision', 'symlink_target', 'reference_revision']
 
539
                 'text_id', 'parent_id', 'children', 'executable', 
 
540
                 'revision', 'symlink_target']
550
541
 
551
542
    def _check(self, checker, rev_id, tree):
552
543
        """See InventoryEntry._check"""
592
583
    """A file in an inventory."""
593
584
 
594
585
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
595
 
                 'text_id', 'parent_id', 'children', 'executable',
596
 
                 'revision', 'symlink_target', 'reference_revision']
 
586
                 'text_id', 'parent_id', 'children', 'executable', 
 
587
                 'revision', 'symlink_target']
597
588
 
598
589
    def _check(self, checker, tree_revision_id, tree):
599
590
        """See InventoryEntry._check"""
740
731
    """A file in an inventory."""
741
732
 
742
733
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
743
 
                 'text_id', 'parent_id', 'children', 'executable',
744
 
                 'revision', 'symlink_target', 'reference_revision']
 
734
                 'text_id', 'parent_id', 'children', 'executable', 
 
735
                 'revision', 'symlink_target']
745
736
 
746
737
    def _check(self, checker, rev_id, tree):
747
738
        """See InventoryEntry._check"""
828
819
            self.file_id, file_parents, self.symlink_target)
829
820
 
830
821
 
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
 
 
858
822
class Inventory(object):
859
823
    """Inventory of versioned files in a tree.
860
824
 
907
871
        an id of None.
908
872
        """
909
873
        if root_id is not None:
910
 
            assert root_id.__class__ == str
911
 
            self._set_root(InventoryDirectory(root_id, u'', None))
 
874
            self._set_root(InventoryDirectory(root_id, '', None))
912
875
        else:
913
876
            self.root = None
914
877
            self._byid = {}
979
942
                # if we finished all children, pop it off the stack
980
943
                stack.pop()
981
944
 
982
 
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
 
945
    def iter_entries_by_dir(self, from_dir=None):
983
946
        """Iterate over the entries in a directory first order.
984
947
 
985
948
        This returns all entries for a directory before returning
989
952
 
990
953
        :return: This yields (path, entry) pairs
991
954
        """
992
 
        if specific_file_ids:
993
 
            safe = osutils.safe_file_id
994
 
            specific_file_ids = set(safe(fid) for fid in specific_file_ids)
995
955
        # TODO? Perhaps this should return the from_dir so that the root is
996
956
        # yielded? or maybe an option?
997
957
        if from_dir is None:
998
958
            if self.root is None:
999
959
                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 
1006
960
            from_dir = self.root
1007
 
            if (specific_file_ids is None or 
1008
 
                self.root.file_id in specific_file_ids):
1009
 
                yield u'', self.root
 
961
            yield '', self.root
1010
962
        elif isinstance(from_dir, basestring):
1011
963
            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
1031
964
            
1032
965
        stack = [(u'', from_dir)]
1033
966
        while stack:
1038
971
 
1039
972
                child_relpath = cur_relpath + child_name
1040
973
 
1041
 
                if (specific_file_ids is None or 
1042
 
                    child_ie.file_id in specific_file_ids):
1043
 
                    yield child_relpath, child_ie
 
974
                yield child_relpath, child_ie
1044
975
 
1045
976
                if child_ie.kind == 'directory':
1046
 
                    if parents is None or child_ie.file_id in parents:
1047
 
                        child_dirs.append((child_relpath+'/', child_ie))
 
977
                    child_dirs.append((child_relpath+'/', child_ie))
1048
978
            stack.extend(reversed(child_dirs))
1049
979
 
1050
980
    def entries(self):
1092
1022
        >>> '456' in inv
1093
1023
        False
1094
1024
        """
1095
 
        file_id = osutils.safe_file_id(file_id)
1096
1025
        return (file_id in self._byid)
1097
1026
 
1098
1027
    def __getitem__(self, file_id):
1104
1033
        >>> inv['123123'].name
1105
1034
        'hello.c'
1106
1035
        """
1107
 
        file_id = osutils.safe_file_id(file_id)
1108
1036
        try:
1109
1037
            return self._byid[file_id]
1110
1038
        except KeyError:
1111
 
            # really we're passing an inventory, not a tree...
1112
 
            raise errors.NoSuchId(self, file_id)
 
1039
            if file_id is None:
 
1040
                raise BzrError("can't look up file_id None")
 
1041
            else:
 
1042
                raise BzrError("file_id {%s} not in inventory" % file_id)
1113
1043
 
1114
1044
    def get_file_kind(self, file_id):
1115
 
        file_id = osutils.safe_file_id(file_id)
1116
1045
        return self._byid[file_id].kind
1117
1046
 
1118
1047
    def get_child(self, parent_id, filename):
1119
 
        parent_id = osutils.safe_file_id(parent_id)
1120
1048
        return self[parent_id].children.get(filename)
1121
1049
 
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
 
 
1132
1050
    def add(self, entry):
1133
1051
        """Add entry to inventory.
1134
1052
 
1138
1056
        Returns the new entry object.
1139
1057
        """
1140
1058
        if entry.file_id in self._byid:
1141
 
            raise errors.DuplicateFileId(entry.file_id,
1142
 
                                         self._byid[entry.file_id])
 
1059
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1143
1060
 
1144
1061
        if entry.parent_id is None:
1145
1062
            assert self.root is None and len(self._byid) == 0
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)
 
1063
            self._set_root(entry)
 
1064
            return entry
 
1065
        try:
 
1066
            parent = self._byid[entry.parent_id]
 
1067
        except KeyError:
 
1068
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
 
1069
 
 
1070
        if entry.name in parent.children:
 
1071
            raise BzrError("%s is already versioned" %
 
1072
                    osutils.pathjoin(self.id2path(parent.file_id), entry.name))
 
1073
 
 
1074
        self._byid[entry.file_id] = entry
 
1075
        parent.children[entry.name] = entry
 
1076
        return entry
1160
1077
 
1161
1078
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
1162
1079
        """Add entry from a path.
1169
1086
 
1170
1087
        if len(parts) == 0:
1171
1088
            if file_id is None:
1172
 
                file_id = generate_ids.gen_root_id()
1173
 
            else:
1174
 
                file_id = osutils.safe_file_id(file_id)
 
1089
                file_id = bzrlib.workingtree.gen_root_id()
1175
1090
            self.root = InventoryDirectory(file_id, '', None)
1176
1091
            self._byid = {self.root.file_id: self.root}
1177
 
            return self.root
 
1092
            return
1178
1093
        else:
1179
1094
            parent_path = parts[:-1]
1180
1095
            parent_id = self.path2id(parent_path)
1195
1110
        >>> '123' in inv
1196
1111
        False
1197
1112
        """
1198
 
        file_id = osutils.safe_file_id(file_id)
1199
1113
        ie = self[file_id]
1200
1114
 
1201
1115
        assert ie.parent_id is None or \
1234
1148
 
1235
1149
    def _iter_file_id_parents(self, file_id):
1236
1150
        """Yield the parents of file_id up to the root."""
1237
 
        file_id = osutils.safe_file_id(file_id)
1238
1151
        while file_id is not None:
1239
1152
            try:
1240
1153
                ie = self._byid[file_id]
1241
1154
            except KeyError:
1242
 
                raise errors.NoSuchId(tree=None, file_id=file_id)
 
1155
                raise BzrError("file_id {%s} not found in inventory" % file_id)
1243
1156
            yield ie
1244
1157
            file_id = ie.parent_id
1245
1158
 
1251
1164
        is equal to the depth of the file in the tree, counting the
1252
1165
        root directory as depth 1.
1253
1166
        """
1254
 
        file_id = osutils.safe_file_id(file_id)
1255
1167
        p = []
1256
1168
        for parent in self._iter_file_id_parents(file_id):
1257
1169
            p.insert(0, parent.file_id)
1266
1178
        >>> print i.id2path('foo-id')
1267
1179
        src/foo.c
1268
1180
        """
1269
 
        file_id = osutils.safe_file_id(file_id)
1270
1181
        # get all names, skipping root
1271
1182
        return '/'.join(reversed(
1272
1183
            [parent.name for parent in 
1310
1221
        return bool(self.path2id(names))
1311
1222
 
1312
1223
    def has_id(self, file_id):
1313
 
        file_id = osutils.safe_file_id(file_id)
1314
1224
        return (file_id in self._byid)
1315
1225
 
1316
1226
    def remove_recursive_id(self, file_id):
1318
1228
        
1319
1229
        :param file_id: A file_id to remove.
1320
1230
        """
1321
 
        file_id = osutils.safe_file_id(file_id)
1322
1231
        to_find_delete = [self._byid[file_id]]
1323
1232
        to_delete = []
1324
1233
        while to_find_delete:
1329
1238
        for file_id in reversed(to_delete):
1330
1239
            ie = self[file_id]
1331
1240
            del self._byid[file_id]
1332
 
        if ie.parent_id is not None:
1333
 
            del self[ie.parent_id].children[ie.name]
1334
 
        else:
1335
 
            self.root = None
 
1241
            if ie.parent_id is not None:
 
1242
                del self[ie.parent_id].children[ie.name]
1336
1243
 
1337
1244
    def rename(self, file_id, new_parent_id, new_name):
1338
1245
        """Move a file within the inventory.
1339
1246
 
1340
1247
        This can change either the name, or the parent, or both.
1341
1248
 
1342
 
        This does not move the working file.
1343
 
        """
1344
 
        file_id = osutils.safe_file_id(file_id)
 
1249
        This does not move the working file."""
1345
1250
        if not is_valid_name(new_name):
1346
1251
            raise BzrError("not an acceptable filename: %r" % new_name)
1347
1252
 
1366
1271
        file_ie.parent_id = new_parent_id
1367
1272
 
1368
1273
    def is_root(self, file_id):
1369
 
        file_id = osutils.safe_file_id(file_id)
1370
1274
        return self.root is not None and file_id == self.root.file_id
1371
1275
 
1372
1276
 
1373
 
entry_factory = {
1374
 
    'directory': InventoryDirectory,
1375
 
    'file': InventoryFile,
1376
 
    'symlink': InventoryLink,
1377
 
    'tree-reference': TreeReference
1378
 
}
1379
 
 
1380
1277
def make_entry(kind, name, parent_id, file_id=None):
1381
1278
    """Create an inventory entry.
1382
1279
 
1386
1283
    :param file_id: the file_id to use. if None, one will be created.
1387
1284
    """
1388
1285
    if file_id is None:
1389
 
        file_id = generate_ids.gen_file_id(name)
1390
 
    else:
1391
 
        file_id = osutils.safe_file_id(file_id)
 
1286
        file_id = bzrlib.workingtree.gen_file_id(name)
1392
1287
 
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.
1397
1288
    norm_name, can_access = osutils.normalized_filename(name)
1398
1289
    if norm_name != name:
1399
1290
        if can_access:
1403
1294
            #       if the error was raised with the full path
1404
1295
            raise errors.InvalidNormalization(name)
1405
1296
 
1406
 
    try:
1407
 
        factory = entry_factory[kind]
1408
 
    except KeyError:
 
1297
    if kind == 'directory':
 
1298
        return InventoryDirectory(file_id, name, parent_id)
 
1299
    elif kind == 'file':
 
1300
        return InventoryFile(file_id, name, parent_id)
 
1301
    elif kind == 'symlink':
 
1302
        return InventoryLink(file_id, name, parent_id)
 
1303
    else:
1409
1304
        raise BzrError("unknown kind %r" % kind)
1410
 
    return factory(file_id, name, parent_id)
1411
1305
 
1412
1306
 
1413
1307
_NAME_RE = None