~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-05-03 19:49:12 UTC
  • mfrom: (2476.1.1 shared_repo_layouts)
  • Revision ID: pqm@pqm.ubuntu.com-20070503194912-pzlcms91kk2uqfdo
(John Arbash Meinel) Add doc/shared_repository_layouts.txt

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
38
 
38
39
import bzrlib
39
 
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
40
 
                            pathjoin, sha_strings)
41
 
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
42
 
                           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
    )
43
53
from bzrlib.trace import mutter
44
54
 
45
55
 
80
90
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
81
91
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
82
92
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
83
 
    >>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
 
93
    >>> shouldbe = {0: '', 1: 'src', 2: 'src/hello.c'}
84
94
    >>> for ix, j in enumerate(i.iter_entries()):
85
95
    ...   print (j[0] == shouldbe[ix], j[1])
86
96
    ... 
 
97
    (True, InventoryDirectory('TREE_ROOT', u'', parent_id=None, revision=None))
87
98
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
88
99
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
89
 
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
90
 
    Traceback (most recent call last):
91
 
    ...
92
 
    BzrError: inventory already contains entry with id {2323}
93
100
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
94
101
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
95
102
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
106
113
    ...     print path
107
114
    ...     assert i.path2id(path)
108
115
    ... 
 
116
    <BLANKLINE>
109
117
    src
110
118
    src/bye.c
111
119
    src/hello.c
185
193
            if self.file_id in inv:
186
194
                ie = inv[self.file_id]
187
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
188
199
                if ie.revision in candidates:
189
200
                    # same revision value in two different inventories:
190
201
                    # correct possible inconsistencies:
242
253
 
243
254
    def get_tar_item(self, root, dp, now, tree):
244
255
        """Get a tarfile item and a file stream for its content."""
245
 
        item = tarfile.TarInfo(pathjoin(root, dp))
 
256
        item = tarfile.TarInfo(osutils.pathjoin(root, dp).encode('utf8'))
246
257
        # TODO: would be cool to actually set it to the timestamp of the
247
258
        # revision it was last changed
248
259
        item.mtime = now
277
288
        """
278
289
        assert isinstance(name, basestring), name
279
290
        if '/' in name or '\\' in name:
280
 
            raise InvalidEntryName(name=name)
 
291
            raise errors.InvalidEntryName(name=name)
281
292
        self.executable = False
282
293
        self.revision = None
283
294
        self.text_sha1 = None
284
295
        self.text_size = None
285
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)
286
299
        self.name = name
287
300
        self.text_id = text_id
288
301
        self.parent_id = parent_id
289
302
        self.symlink_target = None
 
303
        self.reference_revision = None
290
304
 
291
305
    def kind_character(self):
292
306
        """Return a short kind indicator useful for appending to names."""
293
307
        raise BzrError('unknown kind %r' % self.kind)
294
308
 
295
 
    known_kinds = ('file', 'directory', 'symlink', 'root_directory')
 
309
    known_kinds = ('file', 'directory', 'symlink')
296
310
 
297
311
    def _put_in_tar(self, item, tree):
298
312
        """populate item for stashing in a tar, and return the content stream.
307
321
        
308
322
        This is a template method - implement _put_on_disk in subclasses.
309
323
        """
310
 
        fullpath = pathjoin(dest, dp)
 
324
        fullpath = osutils.pathjoin(dest, dp)
311
325
        self._put_on_disk(fullpath, tree)
312
 
        mutter("  export {%s} kind %s to %s", self.file_id,
313
 
                self.kind, fullpath)
 
326
        # mutter("  export {%s} kind %s to %s", self.file_id,
 
327
        #         self.kind, fullpath)
314
328
 
315
329
    def _put_on_disk(self, fullpath, tree):
316
330
        """Put this entry onto disk at fullpath, from tree tree."""
321
335
 
322
336
    @staticmethod
323
337
    def versionable_kind(kind):
324
 
        return kind in ('file', 'directory', 'symlink')
 
338
        return (kind in ('file', 'directory', 'symlink', 'tree-reference'))
325
339
 
326
340
    def check(self, checker, rev_id, inv, tree):
327
341
        """Check this inventory entry is intact.
372
386
            return 'added'
373
387
        elif new_entry is None:
374
388
            return 'removed'
 
389
        if old_entry.kind != new_entry.kind:
 
390
            return 'modified'
375
391
        text_modified, meta_modified = new_entry.detect_changes(old_entry)
376
392
        if text_modified or meta_modified:
377
393
            modified = True
407
423
        This means that all its fields are populated, that it has its
408
424
        text stored in the text store or weave.
409
425
        """
410
 
        mutter('new parents of %s are %r', path, previous_entries)
 
426
        # mutter('new parents of %s are %r', path, previous_entries)
411
427
        self._read_tree_state(path, work_tree)
412
428
        # TODO: Where should we determine whether to reuse a
413
429
        # previous revision id or create a new revision? 20060606
415
431
            # cannot be unchanged unless there is only one parent file rev.
416
432
            parent_ie = previous_entries.values()[0]
417
433
            if self._unchanged(parent_ie):
418
 
                mutter("found unchanged entry")
 
434
                # mutter("found unchanged entry")
419
435
                self.revision = parent_ie.revision
420
436
                return "unchanged"
421
437
        return self._snapshot_into_revision(revision, previous_entries, 
432
448
 
433
449
        :returns: String description of the commit (e.g. "merged", "modified"), etc.
434
450
        """
435
 
        mutter('new revision {%s} for {%s}', revision, self.file_id)
 
451
        # mutter('new revision {%s} for {%s}', revision, self.file_id)
436
452
        self.revision = revision
437
453
        self._snapshot_text(previous_entries, work_tree, commit_builder)
438
454
 
457
473
                and (self.kind == other.kind)
458
474
                and (self.revision == other.revision)
459
475
                and (self.executable == other.executable)
 
476
                and (self.reference_revision == other.reference_revision)
460
477
                )
461
478
 
462
479
    def __ne__(self, other):
477
494
        # renamed
478
495
        elif previous_ie.name != self.name:
479
496
            compatible = False
 
497
        elif previous_ie.kind != self.kind:
 
498
            compatible = False
480
499
        return compatible
481
500
 
482
501
    def _read_tree_state(self, path, work_tree):
497
516
class RootEntry(InventoryEntry):
498
517
 
499
518
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
500
 
                 'text_id', 'parent_id', 'children', 'executable', 
501
 
                 'revision', 'symlink_target']
 
519
                 'text_id', 'parent_id', 'children', 'executable',
 
520
                 'revision', 'symlink_target', 'reference_revision']
502
521
 
503
522
    def _check(self, checker, rev_id, tree):
504
523
        """See InventoryEntry._check"""
506
525
    def __init__(self, file_id):
507
526
        self.file_id = file_id
508
527
        self.children = {}
509
 
        self.kind = 'root_directory'
 
528
        self.kind = 'directory'
510
529
        self.parent_id = None
511
530
        self.name = u''
512
531
        self.revision = None
 
532
        symbol_versioning.warn('RootEntry is deprecated as of bzr 0.10.'
 
533
                               '  Please use InventoryDirectory instead.',
 
534
                               DeprecationWarning, stacklevel=2)
513
535
 
514
536
    def __eq__(self, other):
515
537
        if not isinstance(other, RootEntry):
523
545
    """A directory in an inventory."""
524
546
 
525
547
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
526
 
                 'text_id', 'parent_id', 'children', 'executable', 
527
 
                 'revision', 'symlink_target']
 
548
                 'text_id', 'parent_id', 'children', 'executable',
 
549
                 'revision', 'symlink_target', 'reference_revision']
528
550
 
529
551
    def _check(self, checker, rev_id, tree):
530
552
        """See InventoryEntry._check"""
570
592
    """A file in an inventory."""
571
593
 
572
594
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
573
 
                 'text_id', 'parent_id', 'children', 'executable', 
574
 
                 'revision', 'symlink_target']
 
595
                 'text_id', 'parent_id', 'children', 'executable',
 
596
                 'revision', 'symlink_target', 'reference_revision']
575
597
 
576
598
    def _check(self, checker, tree_revision_id, tree):
577
599
        """See InventoryEntry._check"""
638
660
            else:
639
661
                text_diff(to_label, to_text,
640
662
                          from_label, from_text, output_to)
641
 
        except BinaryFile:
 
663
        except errors.BinaryFile:
642
664
            if reverse:
643
665
                label_pair = (to_label, from_label)
644
666
            else:
670
692
 
671
693
    def _put_on_disk(self, fullpath, tree):
672
694
        """See InventoryEntry._put_on_disk."""
673
 
        pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
 
695
        osutils.pumpfile(tree.get_file(self.file_id), file(fullpath, 'wb'))
674
696
        if tree.is_executable(self.file_id):
675
697
            os.chmod(fullpath, 0755)
676
698
 
692
714
 
693
715
    def _forget_tree_state(self):
694
716
        self.text_sha1 = None
695
 
        self.executable = None
696
717
 
697
718
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
698
719
        """See InventoryEntry._snapshot_text."""
719
740
    """A file in an inventory."""
720
741
 
721
742
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
722
 
                 'text_id', 'parent_id', 'children', 'executable', 
723
 
                 'revision', 'symlink_target']
 
743
                 'text_id', 'parent_id', 'children', 'executable',
 
744
                 'revision', 'symlink_target', 'reference_revision']
724
745
 
725
746
    def _check(self, checker, rev_id, tree):
726
747
        """See InventoryEntry._check"""
807
828
            self.file_id, file_parents, self.symlink_target)
808
829
 
809
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
 
810
858
class Inventory(object):
811
859
    """Inventory of versioned files in a tree.
812
860
 
840
888
    May also look up by name:
841
889
 
842
890
    >>> [x[0] for x in inv.iter_entries()]
843
 
    [u'hello.c']
 
891
    ['', u'hello.c']
844
892
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
845
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'))
846
897
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
847
898
    """
848
899
    def __init__(self, root_id=ROOT_ID, revision_id=None):
855
906
        The inventory is created with a default root directory, with
856
907
        an id of None.
857
908
        """
858
 
        # We are letting Branch.create() create a unique inventory
859
 
        # root id. Rather than generating a random one here.
860
 
        #if root_id is None:
861
 
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
862
 
        self.root = RootEntry(root_id)
863
 
        # FIXME: this isn't ever used, changing it to self.revision may break
864
 
        # things. TODO make everything use self.revision_id
 
909
        if root_id is not None:
 
910
            assert root_id.__class__ == str
 
911
            self._set_root(InventoryDirectory(root_id, u'', None))
 
912
        else:
 
913
            self.root = None
 
914
            self._byid = {}
865
915
        self.revision_id = revision_id
 
916
 
 
917
    def _set_root(self, ie):
 
918
        self.root = ie
866
919
        self._byid = {self.root.file_id: self.root}
867
920
 
868
921
    def copy(self):
869
922
        # TODO: jam 20051218 Should copy also copy the revision_id?
870
 
        other = Inventory(self.root.file_id)
 
923
        entries = self.iter_entries()
 
924
        other = Inventory(entries.next()[1].file_id)
871
925
        # copy recursively so we know directories will be added before
872
926
        # their children.  There are more efficient ways than this...
873
 
        for path, entry in self.iter_entries():
874
 
            if entry == self.root:
875
 
                continue
 
927
        for path, entry in entries():
876
928
            other.add(entry.copy())
877
929
        return other
878
930
 
886
938
    def iter_entries(self, from_dir=None):
887
939
        """Return (path, entry) pairs, in order by name."""
888
940
        if from_dir is None:
889
 
            assert self.root
 
941
            if self.root is None:
 
942
                return
890
943
            from_dir = self.root
 
944
            yield '', self.root
891
945
        elif isinstance(from_dir, basestring):
892
946
            from_dir = self._byid[from_dir]
893
947
            
925
979
                # if we finished all children, pop it off the stack
926
980
                stack.pop()
927
981
 
928
 
    def iter_entries_by_dir(self, from_dir=None):
 
982
    def iter_entries_by_dir(self, from_dir=None, specific_file_ids=None):
929
983
        """Iterate over the entries in a directory first order.
930
984
 
931
985
        This returns all entries for a directory before returning
935
989
 
936
990
        :return: This yields (path, entry) pairs
937
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)
938
995
        # TODO? Perhaps this should return the from_dir so that the root is
939
996
        # yielded? or maybe an option?
940
997
        if from_dir is None:
941
 
            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 
942
1006
            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
943
1010
        elif isinstance(from_dir, basestring):
944
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
945
1031
            
946
1032
        stack = [(u'', from_dir)]
947
1033
        while stack:
952
1038
 
953
1039
                child_relpath = cur_relpath + child_name
954
1040
 
955
 
                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
956
1044
 
957
1045
                if child_ie.kind == 'directory':
958
 
                    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))
959
1048
            stack.extend(reversed(child_dirs))
960
1049
 
961
1050
    def entries(self):
968
1057
            kids = dir_ie.children.items()
969
1058
            kids.sort()
970
1059
            for name, ie in kids:
971
 
                child_path = pathjoin(dir_path, name)
 
1060
                child_path = osutils.pathjoin(dir_path, name)
972
1061
                accum.append((child_path, ie))
973
1062
                if ie.kind == 'directory':
974
1063
                    descend(ie, child_path)
987
1076
            kids.sort()
988
1077
 
989
1078
            for name, child_ie in kids:
990
 
                child_path = pathjoin(parent_path, name)
 
1079
                child_path = osutils.pathjoin(parent_path, name)
991
1080
                descend(child_ie, child_path)
992
1081
        descend(self.root, u'')
993
1082
        return accum
1003
1092
        >>> '456' in inv
1004
1093
        False
1005
1094
        """
1006
 
        return file_id in self._byid
 
1095
        file_id = osutils.safe_file_id(file_id)
 
1096
        return (file_id in self._byid)
1007
1097
 
1008
1098
    def __getitem__(self, file_id):
1009
1099
        """Return the entry for given file_id.
1014
1104
        >>> inv['123123'].name
1015
1105
        'hello.c'
1016
1106
        """
 
1107
        file_id = osutils.safe_file_id(file_id)
1017
1108
        try:
1018
1109
            return self._byid[file_id]
1019
1110
        except KeyError:
1020
 
            if file_id is None:
1021
 
                raise BzrError("can't look up file_id None")
1022
 
            else:
1023
 
                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)
1024
1113
 
1025
1114
    def get_file_kind(self, file_id):
 
1115
        file_id = osutils.safe_file_id(file_id)
1026
1116
        return self._byid[file_id].kind
1027
1117
 
1028
1118
    def get_child(self, parent_id, filename):
 
1119
        parent_id = osutils.safe_file_id(parent_id)
1029
1120
        return self[parent_id].children.get(filename)
1030
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
 
1031
1132
    def add(self, entry):
1032
1133
        """Add entry to inventory.
1033
1134
 
1037
1138
        Returns the new entry object.
1038
1139
        """
1039
1140
        if entry.file_id in self._byid:
1040
 
            raise BzrError("inventory already contains entry with id {%s}" % entry.file_id)
1041
 
 
1042
 
        if entry.parent_id == ROOT_ID or entry.parent_id is None:
1043
 
            entry.parent_id = self.root.file_id
1044
 
 
1045
 
        try:
1046
 
            parent = self._byid[entry.parent_id]
1047
 
        except KeyError:
1048
 
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
1049
 
 
1050
 
        if entry.name in parent.children:
1051
 
            raise BzrError("%s is already versioned" %
1052
 
                    pathjoin(self.id2path(parent.file_id), entry.name))
1053
 
 
1054
 
        self._byid[entry.file_id] = entry
1055
 
        parent.children[entry.name] = entry
1056
 
        return entry
 
1141
            raise errors.DuplicateFileId(entry.file_id,
 
1142
                                         self._byid[entry.file_id])
 
1143
 
 
1144
        if entry.parent_id is None:
 
1145
            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)
1057
1160
 
1058
1161
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
1059
1162
        """Add entry from a path.
1062
1165
 
1063
1166
        Returns the new entry object."""
1064
1167
        
1065
 
        parts = bzrlib.osutils.splitpath(relpath)
 
1168
        parts = osutils.splitpath(relpath)
1066
1169
 
1067
1170
        if len(parts) == 0:
1068
1171
            if file_id is None:
1069
 
                file_id = bzrlib.workingtree.gen_root_id()
1070
 
            self.root = RootEntry(file_id)
 
1172
                file_id = generate_ids.gen_root_id()
 
1173
            else:
 
1174
                file_id = osutils.safe_file_id(file_id)
 
1175
            self.root = InventoryDirectory(file_id, '', None)
1071
1176
            self._byid = {self.root.file_id: self.root}
1072
 
            return
 
1177
            return self.root
1073
1178
        else:
1074
1179
            parent_path = parts[:-1]
1075
1180
            parent_id = self.path2id(parent_path)
1076
1181
            if parent_id is None:
1077
 
                raise NotVersionedError(path=parent_path)
 
1182
                raise errors.NotVersionedError(path=parent_path)
1078
1183
        ie = make_entry(kind, parts[-1], parent_id, file_id)
1079
1184
        return self.add(ie)
1080
1185
 
1090
1195
        >>> '123' in inv
1091
1196
        False
1092
1197
        """
 
1198
        file_id = osutils.safe_file_id(file_id)
1093
1199
        ie = self[file_id]
1094
1200
 
1095
1201
        assert ie.parent_id is None or \
1128
1234
 
1129
1235
    def _iter_file_id_parents(self, file_id):
1130
1236
        """Yield the parents of file_id up to the root."""
 
1237
        file_id = osutils.safe_file_id(file_id)
1131
1238
        while file_id is not None:
1132
1239
            try:
1133
1240
                ie = self._byid[file_id]
1134
1241
            except KeyError:
1135
 
                raise BzrError("file_id {%s} not found in inventory" % file_id)
 
1242
                raise errors.NoSuchId(tree=None, file_id=file_id)
1136
1243
            yield ie
1137
1244
            file_id = ie.parent_id
1138
1245
 
1144
1251
        is equal to the depth of the file in the tree, counting the
1145
1252
        root directory as depth 1.
1146
1253
        """
 
1254
        file_id = osutils.safe_file_id(file_id)
1147
1255
        p = []
1148
1256
        for parent in self._iter_file_id_parents(file_id):
1149
1257
            p.insert(0, parent.file_id)
1158
1266
        >>> print i.id2path('foo-id')
1159
1267
        src/foo.c
1160
1268
        """
 
1269
        file_id = osutils.safe_file_id(file_id)
1161
1270
        # get all names, skipping root
1162
1271
        return '/'.join(reversed(
1163
1272
            [parent.name for parent in 
1174
1283
 
1175
1284
        Returns None IFF the path is not found.
1176
1285
        """
1177
 
        if isinstance(name, types.StringTypes):
1178
 
            name = splitpath(name)
 
1286
        if isinstance(name, basestring):
 
1287
            name = osutils.splitpath(name)
1179
1288
 
1180
1289
        # mutter("lookup path %r" % name)
1181
1290
 
1182
1291
        parent = self.root
 
1292
        if parent is None:
 
1293
            return None
1183
1294
        for f in name:
1184
1295
            try:
1185
 
                cie = parent.children[f]
 
1296
                children = getattr(parent, 'children', None)
 
1297
                if children is None:
 
1298
                    return None
 
1299
                cie = children[f]
1186
1300
                assert cie.name == f
1187
1301
                assert cie.parent_id == parent.file_id
1188
1302
                parent = cie
1196
1310
        return bool(self.path2id(names))
1197
1311
 
1198
1312
    def has_id(self, file_id):
1199
 
        return self._byid.has_key(file_id)
 
1313
        file_id = osutils.safe_file_id(file_id)
 
1314
        return (file_id in self._byid)
 
1315
 
 
1316
    def remove_recursive_id(self, file_id):
 
1317
        """Remove file_id, and children, from the inventory.
 
1318
        
 
1319
        :param file_id: A file_id to remove.
 
1320
        """
 
1321
        file_id = osutils.safe_file_id(file_id)
 
1322
        to_find_delete = [self._byid[file_id]]
 
1323
        to_delete = []
 
1324
        while to_find_delete:
 
1325
            ie = to_find_delete.pop()
 
1326
            to_delete.append(ie.file_id)
 
1327
            if ie.kind == 'directory':
 
1328
                to_find_delete.extend(ie.children.values())
 
1329
        for file_id in reversed(to_delete):
 
1330
            ie = self[file_id]
 
1331
            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
1200
1336
 
1201
1337
    def rename(self, file_id, new_parent_id, new_name):
1202
1338
        """Move a file within the inventory.
1203
1339
 
1204
1340
        This can change either the name, or the parent, or both.
1205
1341
 
1206
 
        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)
1207
1345
        if not is_valid_name(new_name):
1208
1346
            raise BzrError("not an acceptable filename: %r" % new_name)
1209
1347
 
1227
1365
        file_ie.name = new_name
1228
1366
        file_ie.parent_id = new_parent_id
1229
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
}
1230
1379
 
1231
1380
def make_entry(kind, name, parent_id, file_id=None):
1232
1381
    """Create an inventory entry.
1237
1386
    :param file_id: the file_id to use. if None, one will be created.
1238
1387
    """
1239
1388
    if file_id is None:
1240
 
        file_id = bzrlib.workingtree.gen_file_id(name)
1241
 
    if kind == 'directory':
1242
 
        return InventoryDirectory(file_id, name, parent_id)
1243
 
    elif kind == 'file':
1244
 
        return InventoryFile(file_id, name, parent_id)
1245
 
    elif kind == 'symlink':
1246
 
        return InventoryLink(file_id, name, parent_id)
 
1389
        file_id = generate_ids.gen_file_id(name)
1247
1390
    else:
 
1391
        file_id = osutils.safe_file_id(file_id)
 
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.
 
1397
    norm_name, can_access = osutils.normalized_filename(name)
 
1398
    if norm_name != name:
 
1399
        if can_access:
 
1400
            name = norm_name
 
1401
        else:
 
1402
            # TODO: jam 20060701 This would probably be more useful
 
1403
            #       if the error was raised with the full path
 
1404
            raise errors.InvalidNormalization(name)
 
1405
 
 
1406
    try:
 
1407
        factory = entry_factory[kind]
 
1408
    except KeyError:
1248
1409
        raise BzrError("unknown kind %r" % kind)
1249
 
 
 
1410
    return factory(file_id, name, parent_id)
1250
1411
 
1251
1412
 
1252
1413
_NAME_RE = None