~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-07-06 03:15:29 UTC
  • mfrom: (1711.2.78 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060706031529-e189d8c3f42076be
(jam) allow plugins to include benchmarks

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