~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: John Arbash Meinel
  • Date: 2006-11-10 15:38:16 UTC
  • mto: This revision was merged to the branch mainline in revision 2129.
  • Revision ID: john@arbash-meinel.com-20061110153816-46acf76fc86a512b
use try/finally to clean up a nested progress bar during weave fetching

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'))
181
183
        :param entry_vf: The entry versioned file, if its already available.
182
184
        """
183
185
        def get_ancestors(weave, entry):
184
 
            return set(weave.get_ancestry(entry.revision, topo_sorted=False))
 
186
            return set(weave.get_ancestry(entry.revision))
185
187
        # revision:ie mapping for each ie found in previous_inventories.
186
188
        candidates = {}
187
189
        # revision:ie mapping with one revision for each head.
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
 
    def make_entry(self, kind, name, parent_id, file_id=None):
1051
 
        """Simple thunk to bzrlib.inventory.make_entry."""
1052
 
        return make_entry(kind, name, parent_id, file_id)
1053
 
 
1054
980
    def entries(self):
1055
981
        """Return list of (path, ie) for all entries except the root.
1056
982
 
1096
1022
        >>> '456' in inv
1097
1023
        False
1098
1024
        """
1099
 
        file_id = osutils.safe_file_id(file_id)
1100
1025
        return (file_id in self._byid)
1101
1026
 
1102
1027
    def __getitem__(self, file_id):
1108
1033
        >>> inv['123123'].name
1109
1034
        'hello.c'
1110
1035
        """
1111
 
        file_id = osutils.safe_file_id(file_id)
1112
1036
        try:
1113
1037
            return self._byid[file_id]
1114
1038
        except KeyError:
1116
1040
            raise errors.NoSuchId(self, file_id)
1117
1041
 
1118
1042
    def get_file_kind(self, file_id):
1119
 
        file_id = osutils.safe_file_id(file_id)
1120
1043
        return self._byid[file_id].kind
1121
1044
 
1122
1045
    def get_child(self, parent_id, filename):
1123
 
        parent_id = osutils.safe_file_id(parent_id)
1124
1046
        return self[parent_id].children.get(filename)
1125
1047
 
1126
 
    def _add_child(self, entry):
1127
 
        """Add an entry to the inventory, without adding it to its parent"""
1128
 
        if entry.file_id in self._byid:
1129
 
            raise BzrError("inventory already contains entry with id {%s}" %
1130
 
                           entry.file_id)
1131
 
        self._byid[entry.file_id] = entry
1132
 
        for child in getattr(entry, 'children', {}).itervalues():
1133
 
            self._add_child(child)
1134
 
        return entry
1135
 
 
1136
1048
    def add(self, entry):
1137
1049
        """Add entry to inventory.
1138
1050
 
1142
1054
        Returns the new entry object.
1143
1055
        """
1144
1056
        if entry.file_id in self._byid:
1145
 
            raise errors.DuplicateFileId(entry.file_id,
1146
 
                                         self._byid[entry.file_id])
 
1057
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1147
1058
 
1148
1059
        if entry.parent_id is None:
1149
1060
            assert self.root is None and len(self._byid) == 0
1150
 
            self.root = entry
1151
 
        else:
1152
 
            try:
1153
 
                parent = self._byid[entry.parent_id]
1154
 
            except KeyError:
1155
 
                raise BzrError("parent_id {%s} not in inventory" %
1156
 
                               entry.parent_id)
1157
 
 
1158
 
            if entry.name in parent.children:
1159
 
                raise BzrError("%s is already versioned" %
1160
 
                        osutils.pathjoin(self.id2path(parent.file_id),
1161
 
                        entry.name).encode('utf-8'))
1162
 
            parent.children[entry.name] = entry
1163
 
        return self._add_child(entry)
 
1061
            self._set_root(entry)
 
1062
            return entry
 
1063
        try:
 
1064
            parent = self._byid[entry.parent_id]
 
1065
        except KeyError:
 
1066
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
 
1067
 
 
1068
        if entry.name in parent.children:
 
1069
            raise BzrError("%s is already versioned" %
 
1070
                    osutils.pathjoin(self.id2path(parent.file_id), entry.name))
 
1071
 
 
1072
        self._byid[entry.file_id] = entry
 
1073
        parent.children[entry.name] = entry
 
1074
        return entry
1164
1075
 
1165
1076
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
1166
1077
        """Add entry from a path.
1173
1084
 
1174
1085
        if len(parts) == 0:
1175
1086
            if file_id is None:
1176
 
                file_id = generate_ids.gen_root_id()
1177
 
            else:
1178
 
                file_id = osutils.safe_file_id(file_id)
 
1087
                file_id = bzrlib.workingtree.gen_root_id()
1179
1088
            self.root = InventoryDirectory(file_id, '', None)
1180
1089
            self._byid = {self.root.file_id: self.root}
1181
 
            return self.root
 
1090
            return
1182
1091
        else:
1183
1092
            parent_path = parts[:-1]
1184
1093
            parent_id = self.path2id(parent_path)
1199
1108
        >>> '123' in inv
1200
1109
        False
1201
1110
        """
1202
 
        file_id = osutils.safe_file_id(file_id)
1203
1111
        ie = self[file_id]
1204
1112
 
1205
1113
        assert ie.parent_id is None or \
1238
1146
 
1239
1147
    def _iter_file_id_parents(self, file_id):
1240
1148
        """Yield the parents of file_id up to the root."""
1241
 
        file_id = osutils.safe_file_id(file_id)
1242
1149
        while file_id is not None:
1243
1150
            try:
1244
1151
                ie = self._byid[file_id]
1245
1152
            except KeyError:
1246
 
                raise errors.NoSuchId(tree=None, file_id=file_id)
 
1153
                raise BzrError("file_id {%s} not found in inventory" % file_id)
1247
1154
            yield ie
1248
1155
            file_id = ie.parent_id
1249
1156
 
1255
1162
        is equal to the depth of the file in the tree, counting the
1256
1163
        root directory as depth 1.
1257
1164
        """
1258
 
        file_id = osutils.safe_file_id(file_id)
1259
1165
        p = []
1260
1166
        for parent in self._iter_file_id_parents(file_id):
1261
1167
            p.insert(0, parent.file_id)
1270
1176
        >>> print i.id2path('foo-id')
1271
1177
        src/foo.c
1272
1178
        """
1273
 
        file_id = osutils.safe_file_id(file_id)
1274
1179
        # get all names, skipping root
1275
1180
        return '/'.join(reversed(
1276
1181
            [parent.name for parent in 
1314
1219
        return bool(self.path2id(names))
1315
1220
 
1316
1221
    def has_id(self, file_id):
1317
 
        file_id = osutils.safe_file_id(file_id)
1318
1222
        return (file_id in self._byid)
1319
1223
 
1320
1224
    def remove_recursive_id(self, file_id):
1322
1226
        
1323
1227
        :param file_id: A file_id to remove.
1324
1228
        """
1325
 
        file_id = osutils.safe_file_id(file_id)
1326
1229
        to_find_delete = [self._byid[file_id]]
1327
1230
        to_delete = []
1328
1231
        while to_find_delete:
1333
1236
        for file_id in reversed(to_delete):
1334
1237
            ie = self[file_id]
1335
1238
            del self._byid[file_id]
1336
 
        if ie.parent_id is not None:
1337
 
            del self[ie.parent_id].children[ie.name]
1338
 
        else:
1339
 
            self.root = None
 
1239
            if ie.parent_id is not None:
 
1240
                del self[ie.parent_id].children[ie.name]
1340
1241
 
1341
1242
    def rename(self, file_id, new_parent_id, new_name):
1342
1243
        """Move a file within the inventory.
1343
1244
 
1344
1245
        This can change either the name, or the parent, or both.
1345
1246
 
1346
 
        This does not move the working file.
1347
 
        """
1348
 
        file_id = osutils.safe_file_id(file_id)
 
1247
        This does not move the working file."""
1349
1248
        if not is_valid_name(new_name):
1350
1249
            raise BzrError("not an acceptable filename: %r" % new_name)
1351
1250
 
1370
1269
        file_ie.parent_id = new_parent_id
1371
1270
 
1372
1271
    def is_root(self, file_id):
1373
 
        file_id = osutils.safe_file_id(file_id)
1374
1272
        return self.root is not None and file_id == self.root.file_id
1375
1273
 
1376
1274
 
1377
 
entry_factory = {
1378
 
    'directory': InventoryDirectory,
1379
 
    'file': InventoryFile,
1380
 
    'symlink': InventoryLink,
1381
 
    'tree-reference': TreeReference
1382
 
}
1383
 
 
1384
1275
def make_entry(kind, name, parent_id, file_id=None):
1385
1276
    """Create an inventory entry.
1386
1277
 
1390
1281
    :param file_id: the file_id to use. if None, one will be created.
1391
1282
    """
1392
1283
    if file_id is None:
1393
 
        file_id = generate_ids.gen_file_id(name)
1394
 
    else:
1395
 
        file_id = osutils.safe_file_id(file_id)
 
1284
        file_id = bzrlib.workingtree.gen_file_id(name)
1396
1285
 
1397
 
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
1398
 
    # keep them synchronised.
1399
 
    # we dont import normalized_filename directly because we want to be
1400
 
    # able to change the implementation at runtime for tests.
1401
1286
    norm_name, can_access = osutils.normalized_filename(name)
1402
1287
    if norm_name != name:
1403
1288
        if can_access:
1407
1292
            #       if the error was raised with the full path
1408
1293
            raise errors.InvalidNormalization(name)
1409
1294
 
1410
 
    try:
1411
 
        factory = entry_factory[kind]
1412
 
    except KeyError:
 
1295
    if kind == 'directory':
 
1296
        return InventoryDirectory(file_id, name, parent_id)
 
1297
    elif kind == 'file':
 
1298
        return InventoryFile(file_id, name, parent_id)
 
1299
    elif kind == 'symlink':
 
1300
        return InventoryLink(file_id, name, parent_id)
 
1301
    else:
1413
1302
        raise BzrError("unknown kind %r" % kind)
1414
 
    return factory(file_id, name, parent_id)
1415
1303
 
1416
1304
 
1417
1305
_NAME_RE = None