~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: 2006-09-25 18:42:17 UTC
  • mfrom: (2039.1.2 progress-cleanup)
  • Revision ID: pqm@pqm.ubuntu.com-20060925184217-fd144de117df49c3
cleanup progress properly when interrupted during fetch (#54000)

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
# created, but it's not for now.
28
28
ROOT_ID = "TREE_ROOT"
29
29
 
30
 
import os
 
30
 
 
31
import collections
 
32
import os.path
31
33
import re
32
34
import sys
33
 
 
34
 
from bzrlib.lazy_import import lazy_import
35
 
lazy_import(globals(), """
36
 
import collections
37
35
import tarfile
 
36
import types
 
37
from warnings import warn
38
38
 
39
39
import bzrlib
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
 
    )
 
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)
53
45
from bzrlib.trace import mutter
54
46
 
55
47
 
90
82
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
91
83
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
92
84
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
93
 
    >>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
 
85
    >>> shouldbe = {0: '', 1: 'src', 2: pathjoin('src','hello.c')}
94
86
    >>> for ix, j in enumerate(i.iter_entries()):
95
87
    ...   print (j[0] == shouldbe[ix], j[1])
96
88
    ... 
97
 
    (True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
 
89
    (True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
98
90
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
99
91
    (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}
100
96
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
101
97
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
102
98
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
250
246
 
251
247
    def get_tar_item(self, root, dp, now, tree):
252
248
        """Get a tarfile item and a file stream for its content."""
253
 
        item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
 
249
        item = tarfile.TarInfo(pathjoin(root, dp).encode('utf8'))
254
250
        # TODO: would be cool to actually set it to the timestamp of the
255
251
        # revision it was last changed
256
252
        item.mtime = now
285
281
        """
286
282
        assert isinstance(name, basestring), name
287
283
        if '/' in name or '\\' in name:
288
 
            raise errors.InvalidEntryName(name=name)
 
284
            raise InvalidEntryName(name=name)
289
285
        self.executable = False
290
286
        self.revision = None
291
287
        self.text_sha1 = None
292
288
        self.text_size = None
293
289
        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
290
        self.name = name
297
291
        self.text_id = text_id
298
292
        self.parent_id = parent_id
317
311
        
318
312
        This is a template method - implement _put_on_disk in subclasses.
319
313
        """
320
 
        fullpath = osutils.pathjoin(dest, dp)
 
314
        fullpath = pathjoin(dest, dp)
321
315
        self._put_on_disk(fullpath, tree)
322
316
        # mutter("  export {%s} kind %s to %s", self.file_id,
323
317
        #         self.kind, fullpath)
382
376
            return 'added'
383
377
        elif new_entry is None:
384
378
            return 'removed'
385
 
        if old_entry.kind != new_entry.kind:
386
 
            return 'modified'
387
379
        text_modified, meta_modified = new_entry.detect_changes(old_entry)
388
380
        if text_modified or meta_modified:
389
381
            modified = True
522
514
        self.parent_id = None
523
515
        self.name = u''
524
516
        self.revision = None
525
 
        symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
526
 
                               '  Please use InventoryDirectory instead.',
527
 
                               DeprecationWarning, stacklevel=2)
 
517
        warn('RootEntry is deprecated as of bzr 0.10.  Please use '
 
518
             'InventoryDirectory instead.',
 
519
            DeprecationWarning, stacklevel=2)
528
520
 
529
521
    def __eq__(self, other):
530
522
        if not isinstance(other, RootEntry):
653
645
            else:
654
646
                text_diff(to_label, to_text,
655
647
                          from_label, from_text, output_to)
656
 
        except errors.BinaryFile:
 
648
        except BinaryFile:
657
649
            if reverse:
658
650
                label_pair = (to_label, from_label)
659
651
            else:
685
677
 
686
678
    def _put_on_disk(self, fullpath, tree):
687
679
        """See InventoryEntry._put_on_disk."""
688
 
        osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
 
680
        pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
689
681
        if tree.is_executable(self.file_id):
690
682
            os.chmod(fullpath, 0755)
691
683
 
857
849
    ['', u'hello.c']
858
850
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
859
851
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
860
 
    Traceback (most recent call last):
861
 
    BzrError: parent_id {TREE_ROOT} not in inventory
862
 
    >>> inv.add(InventoryFile('123-123', 'hello.c', 'TREE_ROOT-12345678-12345678'))
863
852
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
864
853
    """
865
854
    def __init__(self, root_id=ROOT_ID, revision_id=None):
872
861
        The inventory is created with a default root directory, with
873
862
        an id of None.
874
863
        """
 
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')
875
868
        if root_id is not None:
876
 
            assert root_id.__class__ == str
877
 
            self._set_root(InventoryDirectory(root_id, u'', None))
 
869
            self._set_root(InventoryDirectory(root_id, '', None))
878
870
        else:
879
871
            self.root = None
880
872
            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
881
875
        self.revision_id = revision_id
882
876
 
883
877
    def _set_root(self, ie):
904
898
    def iter_entries(self, from_dir=None):
905
899
        """Return (path, entry) pairs, in order by name."""
906
900
        if from_dir is None:
907
 
            if self.root is None:
908
 
                return
 
901
            assert self.root
909
902
            from_dir = self.root
910
903
            yield '', self.root
911
904
        elif isinstance(from_dir, basestring):
945
938
                # if we finished all children, pop it off the stack
946
939
                stack.pop()
947
940
 
948
 
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
 
941
    def iter_entries_by_dir(self, from_dir=None):
949
942
        """Iterate over the entries in a directory first order.
950
943
 
951
944
        This returns all entries for a directory before returning
955
948
 
956
949
        :return: This yields (path, entry) pairs
957
950
        """
958
 
        if specific_file_ids:
959
 
            specific_file_ids = [osutils.safe_file_id(fid)
960
 
                                 for fid in specific_file_ids]
961
951
        # TODO? Perhaps this should return the from_dir so that the root is
962
952
        # yielded? or maybe an option?
963
953
        if from_dir is None:
964
 
            if self.root is None:
965
 
                return
966
 
            # Optimize a common case
967
 
            if specific_file_ids is not None and len(specific_file_ids) == 1:
968
 
                file_id = list(specific_file_ids)[0]
969
 
                if file_id in self:
970
 
                    yield self.id2path(file_id), self[file_id]
971
 
                return 
 
954
            assert self.root
972
955
            from_dir = self.root
973
 
            if (specific_file_ids is None or 
974
 
                self.root.file_id in specific_file_ids):
975
 
                yield u'', self.root
 
956
            yield '', self.root
976
957
        elif isinstance(from_dir, basestring):
977
958
            from_dir = self._byid[from_dir]
978
 
 
979
 
        if specific_file_ids is not None:
980
 
            parents = set()
981
 
            def add_ancestors(file_id):
982
 
                if file_id not in self:
983
 
                    return
984
 
                parent_id = self[file_id].parent_id
985
 
                if parent_id is None:
986
 
                    return
987
 
                if parent_id not in parents:
988
 
                    parents.add(parent_id)
989
 
                    add_ancestors(parent_id)
990
 
            for file_id in specific_file_ids:
991
 
                add_ancestors(file_id)
992
 
        else:
993
 
            parents = None
994
959
            
995
960
        stack = [(u'', from_dir)]
996
961
        while stack:
1001
966
 
1002
967
                child_relpath = cur_relpath + child_name
1003
968
 
1004
 
                if (specific_file_ids is None or 
1005
 
                    child_ie.file_id in specific_file_ids):
1006
 
                    yield child_relpath, child_ie
 
969
                yield child_relpath, child_ie
1007
970
 
1008
971
                if child_ie.kind == 'directory':
1009
 
                    if parents is None or child_ie.file_id in parents:
1010
 
                        child_dirs.append((child_relpath+'/', child_ie))
 
972
                    child_dirs.append((child_relpath+'/', child_ie))
1011
973
            stack.extend(reversed(child_dirs))
1012
974
 
1013
975
    def entries(self):
1020
982
            kids = dir_ie.children.items()
1021
983
            kids.sort()
1022
984
            for name, ie in kids:
1023
 
                child_path = osutils.pathjoin(dir_path, name)
 
985
                child_path = pathjoin(dir_path, name)
1024
986
                accum.append((child_path, ie))
1025
987
                if ie.kind == 'directory':
1026
988
                    descend(ie, child_path)
1039
1001
            kids.sort()
1040
1002
 
1041
1003
            for name, child_ie in kids:
1042
 
                child_path = osutils.pathjoin(parent_path, name)
 
1004
                child_path = pathjoin(parent_path, name)
1043
1005
                descend(child_ie, child_path)
1044
1006
        descend(self.root, u'')
1045
1007
        return accum
1055
1017
        >>> '456' in inv
1056
1018
        False
1057
1019
        """
1058
 
        file_id = osutils.safe_file_id(file_id)
1059
1020
        return (file_id in self._byid)
1060
1021
 
1061
1022
    def __getitem__(self, file_id):
1067
1028
        >>> inv['123123'].name
1068
1029
        'hello.c'
1069
1030
        """
1070
 
        file_id = osutils.safe_file_id(file_id)
1071
1031
        try:
1072
1032
            return self._byid[file_id]
1073
1033
        except KeyError:
1074
 
            # really we're passing an inventory, not a tree...
1075
 
            raise errors.NoSuchId(self, file_id)
 
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)
1076
1038
 
1077
1039
    def get_file_kind(self, file_id):
1078
 
        file_id = osutils.safe_file_id(file_id)
1079
1040
        return self._byid[file_id].kind
1080
1041
 
1081
1042
    def get_child(self, parent_id, filename):
1082
 
        parent_id = osutils.safe_file_id(parent_id)
1083
1043
        return self[parent_id].children.get(filename)
1084
1044
 
1085
1045
    def add(self, entry):
1091
1051
        Returns the new entry object.
1092
1052
        """
1093
1053
        if entry.file_id in self._byid:
1094
 
            raise errors.DuplicateFileId(entry.file_id,
1095
 
                                         self._byid[entry.file_id])
 
1054
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1096
1055
 
1097
1056
        if entry.parent_id is None:
1098
1057
            assert self.root is None and len(self._byid) == 0
1099
1058
            self._set_root(entry)
1100
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
 
1101
1064
        try:
1102
1065
            parent = self._byid[entry.parent_id]
1103
1066
        except KeyError:
1105
1068
 
1106
1069
        if entry.name in parent.children:
1107
1070
            raise BzrError("%s is already versioned" %
1108
 
                    osutils.pathjoin(self.id2path(parent.file_id), entry.name))
 
1071
                    pathjoin(self.id2path(parent.file_id), entry.name))
1109
1072
 
1110
1073
        self._byid[entry.file_id] = entry
1111
1074
        parent.children[entry.name] = entry
1122
1085
 
1123
1086
        if len(parts) == 0:
1124
1087
            if file_id is None:
1125
 
                file_id = generate_ids.gen_root_id()
1126
 
            else:
1127
 
                file_id = osutils.safe_file_id(file_id)
 
1088
                file_id = bzrlib.workingtree.gen_root_id()
1128
1089
            self.root = InventoryDirectory(file_id, '', None)
1129
1090
            self._byid = {self.root.file_id: self.root}
1130
 
            return self.root
 
1091
            return
1131
1092
        else:
1132
1093
            parent_path = parts[:-1]
1133
1094
            parent_id = self.path2id(parent_path)
1134
1095
            if parent_id is None:
1135
 
                raise errors.NotVersionedError(path=parent_path)
 
1096
                raise NotVersionedError(path=parent_path)
1136
1097
        ie = make_entry(kind, parts[-1], parent_id, file_id)
1137
1098
        return self.add(ie)
1138
1099
 
1148
1109
        >>> '123' in inv
1149
1110
        False
1150
1111
        """
1151
 
        file_id = osutils.safe_file_id(file_id)
1152
1112
        ie = self[file_id]
1153
1113
 
1154
1114
        assert ie.parent_id is None or \
1187
1147
 
1188
1148
    def _iter_file_id_parents(self, file_id):
1189
1149
        """Yield the parents of file_id up to the root."""
1190
 
        file_id = osutils.safe_file_id(file_id)
1191
1150
        while file_id is not None:
1192
1151
            try:
1193
1152
                ie = self._byid[file_id]
1204
1163
        is equal to the depth of the file in the tree, counting the
1205
1164
        root directory as depth 1.
1206
1165
        """
1207
 
        file_id = osutils.safe_file_id(file_id)
1208
1166
        p = []
1209
1167
        for parent in self._iter_file_id_parents(file_id):
1210
1168
            p.insert(0, parent.file_id)
1219
1177
        >>> print i.id2path('foo-id')
1220
1178
        src/foo.c
1221
1179
        """
1222
 
        file_id = osutils.safe_file_id(file_id)
1223
1180
        # get all names, skipping root
1224
1181
        return '/'.join(reversed(
1225
1182
            [parent.name for parent in 
1236
1193
 
1237
1194
        Returns None IFF the path is not found.
1238
1195
        """
1239
 
        if isinstance(name, basestring):
1240
 
            name = osutils.splitpath(name)
 
1196
        if isinstance(name, types.StringTypes):
 
1197
            name = splitpath(name)
1241
1198
 
1242
1199
        # mutter("lookup path %r" % name)
1243
1200
 
1244
1201
        parent = self.root
1245
 
        if parent is None:
1246
 
            return None
1247
1202
        for f in name:
1248
1203
            try:
1249
 
                children = getattr(parent, 'children', None)
1250
 
                if children is None:
1251
 
                    return None
1252
 
                cie = children[f]
 
1204
                cie = parent.children[f]
1253
1205
                assert cie.name == f
1254
1206
                assert cie.parent_id == parent.file_id
1255
1207
                parent = cie
1263
1215
        return bool(self.path2id(names))
1264
1216
 
1265
1217
    def has_id(self, file_id):
1266
 
        file_id = osutils.safe_file_id(file_id)
1267
1218
        return (file_id in self._byid)
1268
1219
 
1269
1220
    def remove_recursive_id(self, file_id):
1271
1222
        
1272
1223
        :param file_id: A file_id to remove.
1273
1224
        """
1274
 
        file_id = osutils.safe_file_id(file_id)
1275
1225
        to_find_delete = [self._byid[file_id]]
1276
1226
        to_delete = []
1277
1227
        while to_find_delete:
1290
1240
 
1291
1241
        This can change either the name, or the parent, or both.
1292
1242
 
1293
 
        This does not move the working file.
1294
 
        """
1295
 
        file_id = osutils.safe_file_id(file_id)
 
1243
        This does not move the working file."""
1296
1244
        if not is_valid_name(new_name):
1297
1245
            raise BzrError("not an acceptable filename: %r" % new_name)
1298
1246
 
1316
1264
        file_ie.name = new_name
1317
1265
        file_ie.parent_id = new_parent_id
1318
1266
 
1319
 
    def is_root(self, file_id):
1320
 
        file_id = osutils.safe_file_id(file_id)
1321
 
        return self.root is not None and file_id == self.root.file_id
1322
 
 
1323
 
 
1324
 
entry_factory = {
1325
 
    'directory':InventoryDirectory,
1326
 
    'file':InventoryFile,
1327
 
    'symlink':InventoryLink,
1328
 
}
1329
1267
 
1330
1268
def make_entry(kind, name, parent_id, file_id=None):
1331
1269
    """Create an inventory entry.
1336
1274
    :param file_id: the file_id to use. if None, one will be created.
1337
1275
    """
1338
1276
    if file_id is None:
1339
 
        file_id = generate_ids.gen_file_id(name)
1340
 
    else:
1341
 
        file_id = osutils.safe_file_id(file_id)
 
1277
        file_id = bzrlib.workingtree.gen_file_id(name)
1342
1278
 
1343
 
    #------- This has been copied to bzrlib.dirstate.DirState.add, please
1344
 
    # keep them synchronised.
1345
 
    # we dont import normalized_filename directly because we want to be
1346
 
    # able to change the implementation at runtime for tests.
1347
1279
    norm_name, can_access = osutils.normalized_filename(name)
1348
1280
    if norm_name != name:
1349
1281
        if can_access:
1353
1285
            #       if the error was raised with the full path
1354
1286
            raise errors.InvalidNormalization(name)
1355
1287
 
1356
 
    try:
1357
 
        factory = entry_factory[kind]
1358
 
    except KeyError:
 
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:
1359
1295
        raise BzrError("unknown kind %r" % kind)
1360
 
    return factory(file_id, name, parent_id)
1361
1296
 
1362
1297
 
1363
1298
_NAME_RE = None