~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

Late bind to PatienceSequenceMatcher to allow plugin to override.

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
ROOT_ID = "TREE_ROOT"
29
29
 
30
30
 
31
 
import collections
32
31
import os.path
33
32
import re
34
33
import sys
35
34
import tarfile
36
35
import types
37
 
from warnings import warn
38
36
 
39
37
import bzrlib
40
 
from bzrlib import errors, osutils
41
38
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
42
39
                            pathjoin, sha_strings)
43
40
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
79
76
    >>> i.path2id('')
80
77
    'TREE_ROOT'
81
78
    >>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
82
 
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
 
79
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
83
80
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
84
 
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
85
 
    >>> shouldbe = {0: '', 1: 'src', 2: pathjoin('src','hello.c')}
 
81
    InventoryFile('2323', 'hello.c', parent_id='123')
 
82
    >>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
86
83
    >>> for ix, j in enumerate(i.iter_entries()):
87
84
    ...   print (j[0] == shouldbe[ix], j[1])
88
85
    ... 
89
 
    (True, InventoryDirectory('TREE_ROOT', '', parent_id=None, revision=None))
90
 
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
91
 
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
 
86
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
 
87
    (True, InventoryFile('2323', 'hello.c', parent_id='123'))
92
88
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
93
89
    Traceback (most recent call last):
94
90
    ...
95
91
    BzrError: inventory already contains entry with id {2323}
96
92
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
97
 
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
 
93
    InventoryFile('2324', 'bye.c', parent_id='123')
98
94
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
99
 
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
 
95
    InventoryDirectory('2325', 'wibble', parent_id='123')
100
96
    >>> i.path2id('src/wibble')
101
97
    '2325'
102
98
    >>> '2325' in i
103
99
    True
104
100
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
105
 
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
 
101
    InventoryFile('2326', 'wibble.c', parent_id='2325')
106
102
    >>> i['2326']
107
 
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
 
103
    InventoryFile('2326', 'wibble.c', parent_id='2325')
108
104
    >>> for path, entry in i.iter_entries():
109
105
    ...     print path
110
106
    ...     assert i.path2id(path)
111
107
    ... 
112
 
    <BLANKLINE>
113
108
    src
114
109
    src/bye.c
115
110
    src/hello.c
127
122
    RENAMED = 'renamed'
128
123
    MODIFIED_AND_RENAMED = 'modified and renamed'
129
124
    
130
 
    __slots__ = []
 
125
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
126
                 'text_id', 'parent_id', 'children', 'executable', 
 
127
                 'revision']
 
128
 
 
129
    def _add_text_to_weave(self, new_lines, parents, weave_store, transaction):
 
130
        versionedfile = weave_store.get_weave_or_empty(self.file_id,
 
131
                                                       transaction)
 
132
        versionedfile.add_lines(self.revision, parents, new_lines)
 
133
        versionedfile.clear_cache()
131
134
 
132
135
    def detect_changes(self, old_entry):
133
136
        """Return a (text_modified, meta_modified) from this to old_entry.
162
165
                            versioned_file_store,
163
166
                            transaction,
164
167
                            entry_vf=None):
165
 
        """Return the revisions and entries that directly precede this.
 
168
        """Return the revisions and entries that directly preceed this.
166
169
 
167
170
        Returned as a map from revision to inventory entry.
168
171
 
296
299
        """Return a short kind indicator useful for appending to names."""
297
300
        raise BzrError('unknown kind %r' % self.kind)
298
301
 
299
 
    known_kinds = ('file', 'directory', 'symlink')
 
302
    known_kinds = ('file', 'directory', 'symlink', 'root_directory')
300
303
 
301
304
    def _put_in_tar(self, item, tree):
302
305
        """populate item for stashing in a tar, and return the content stream.
313
316
        """
314
317
        fullpath = pathjoin(dest, dp)
315
318
        self._put_on_disk(fullpath, tree)
316
 
        # mutter("  export {%s} kind %s to %s", self.file_id,
317
 
        #         self.kind, fullpath)
 
319
        mutter("  export {%s} kind %s to %s", self.file_id,
 
320
                self.kind, fullpath)
318
321
 
319
322
    def _put_on_disk(self, fullpath, tree):
320
323
        """Put this entry onto disk at fullpath, from tree tree."""
321
324
        raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
322
325
 
323
326
    def sorted_children(self):
324
 
        return sorted(self.children.items())
 
327
        l = self.children.items()
 
328
        l.sort()
 
329
        return l
325
330
 
326
331
    @staticmethod
327
332
    def versionable_kind(kind):
341
346
        :param inv: Inventory from which the entry was loaded.
342
347
        :param tree: RevisionTree for this entry.
343
348
        """
344
 
        if self.parent_id is not None:
 
349
        if self.parent_id != None:
345
350
            if not inv.has_id(self.parent_id):
346
351
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
347
352
                        % (self.parent_id, rev_id))
397
402
        return 'unchanged'
398
403
 
399
404
    def __repr__(self):
400
 
        return ("%s(%r, %r, parent_id=%r, revision=%r)"
 
405
        return ("%s(%r, %r, parent_id=%r)"
401
406
                % (self.__class__.__name__,
402
407
                   self.file_id,
403
408
                   self.name,
404
 
                   self.parent_id,
405
 
                   self.revision))
 
409
                   self.parent_id))
406
410
 
407
411
    def snapshot(self, revision, path, previous_entries,
408
 
                 work_tree, commit_builder):
 
412
                 work_tree, weave_store, transaction):
409
413
        """Make a snapshot of this entry which may or may not have changed.
410
414
        
411
415
        This means that all its fields are populated, that it has its
412
416
        text stored in the text store or weave.
413
417
        """
414
 
        # mutter('new parents of %s are %r', path, previous_entries)
 
418
        mutter('new parents of %s are %r', path, previous_entries)
415
419
        self._read_tree_state(path, work_tree)
416
 
        # TODO: Where should we determine whether to reuse a
417
 
        # previous revision id or create a new revision? 20060606
418
420
        if len(previous_entries) == 1:
419
421
            # cannot be unchanged unless there is only one parent file rev.
420
422
            parent_ie = previous_entries.values()[0]
421
423
            if self._unchanged(parent_ie):
422
 
                # mutter("found unchanged entry")
 
424
                mutter("found unchanged entry")
423
425
                self.revision = parent_ie.revision
424
426
                return "unchanged"
425
427
        return self._snapshot_into_revision(revision, previous_entries, 
426
 
                                            work_tree, commit_builder)
 
428
                                            work_tree, weave_store, transaction)
427
429
 
428
430
    def _snapshot_into_revision(self, revision, previous_entries, work_tree,
429
 
                                commit_builder):
 
431
                                weave_store, transaction):
430
432
        """Record this revision unconditionally into a store.
431
433
 
432
434
        The entry's last-changed revision property (`revision`) is updated to 
436
438
 
437
439
        :returns: String description of the commit (e.g. "merged", "modified"), etc.
438
440
        """
439
 
        # mutter('new revision {%s} for {%s}', revision, self.file_id)
 
441
        mutter('new revision {%s} for {%s}', revision, self.file_id)
440
442
        self.revision = revision
441
 
        self._snapshot_text(previous_entries, work_tree, commit_builder)
 
443
        self._snapshot_text(previous_entries, work_tree, weave_store,
 
444
                            transaction)
442
445
 
443
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder): 
 
446
    def _snapshot_text(self, file_parents, work_tree, weave_store, transaction): 
444
447
        """Record the 'text' of this entry, whatever form that takes.
445
448
        
446
449
        This default implementation simply adds an empty text.
447
450
        """
448
 
        raise NotImplementedError(self._snapshot_text)
 
451
        mutter('storing file {%s} in revision {%s}',
 
452
               self.file_id, self.revision)
 
453
        self._add_text_to_weave([], file_parents.keys(), weave_store, transaction)
449
454
 
450
455
    def __eq__(self, other):
451
456
        if not isinstance(other, InventoryEntry):
472
477
    def _unchanged(self, previous_ie):
473
478
        """Has this entry changed relative to previous_ie.
474
479
 
475
 
        This method should be overridden in child classes.
 
480
        This method should be overriden in child classes.
476
481
        """
477
482
        compatible = True
478
483
        # different inv parent
500
505
 
501
506
class RootEntry(InventoryEntry):
502
507
 
503
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
504
 
                 'text_id', 'parent_id', 'children', 'executable', 
505
 
                 'revision', 'symlink_target']
506
 
 
507
508
    def _check(self, checker, rev_id, tree):
508
509
        """See InventoryEntry._check"""
509
510
 
510
511
    def __init__(self, file_id):
511
512
        self.file_id = file_id
512
513
        self.children = {}
513
 
        self.kind = 'directory'
 
514
        self.kind = 'root_directory'
514
515
        self.parent_id = None
515
516
        self.name = u''
516
 
        self.revision = None
517
 
        warn('RootEntry is deprecated as of bzr 0.10.  Please use '
518
 
             'InventoryDirectory instead.',
519
 
            DeprecationWarning, stacklevel=2)
520
517
 
521
518
    def __eq__(self, other):
522
519
        if not isinstance(other, RootEntry):
529
526
class InventoryDirectory(InventoryEntry):
530
527
    """A directory in an inventory."""
531
528
 
532
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
533
 
                 'text_id', 'parent_id', 'children', 'executable', 
534
 
                 'revision', 'symlink_target']
535
 
 
536
529
    def _check(self, checker, rev_id, tree):
537
530
        """See InventoryEntry._check"""
538
 
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
 
531
        if self.text_sha1 != None or self.text_size != None or self.text_id != None:
539
532
            raise BzrCheckError('directory {%s} has text in revision {%s}'
540
533
                                % (self.file_id, rev_id))
541
534
 
568
561
        """See InventoryEntry._put_on_disk."""
569
562
        os.mkdir(fullpath)
570
563
 
571
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
572
 
        """See InventoryEntry._snapshot_text."""
573
 
        commit_builder.modified_directory(self.file_id, file_parents)
574
 
 
575
564
 
576
565
class InventoryFile(InventoryEntry):
577
566
    """A file in an inventory."""
578
567
 
579
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
580
 
                 'text_id', 'parent_id', 'children', 'executable', 
581
 
                 'revision', 'symlink_target']
582
 
 
583
568
    def _check(self, checker, tree_revision_id, tree):
584
569
        """See InventoryEntry._check"""
585
570
        t = (self.file_id, self.revision)
624
609
 
625
610
    def detect_changes(self, old_entry):
626
611
        """See InventoryEntry.detect_changes."""
627
 
        assert self.text_sha1 is not None
628
 
        assert old_entry.text_sha1 is not None
 
612
        assert self.text_sha1 != None
 
613
        assert old_entry.text_sha1 != None
629
614
        text_modified = (self.text_sha1 != old_entry.text_sha1)
630
615
        meta_modified = (self.executable != old_entry.executable)
631
616
        return text_modified, meta_modified
683
668
 
684
669
    def _read_tree_state(self, path, work_tree):
685
670
        """See InventoryEntry._read_tree_state."""
686
 
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
687
 
        # FIXME: 20050930 probe for the text size when getting sha1
688
 
        # in _read_tree_state
689
 
        self.executable = work_tree.is_executable(self.file_id, path=path)
690
 
 
691
 
    def __repr__(self):
692
 
        return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
693
 
                % (self.__class__.__name__,
694
 
                   self.file_id,
695
 
                   self.name,
696
 
                   self.parent_id,
697
 
                   self.text_sha1,
698
 
                   self.text_size))
 
671
        self.text_sha1 = work_tree.get_file_sha1(self.file_id)
 
672
        self.executable = work_tree.is_executable(self.file_id)
699
673
 
700
674
    def _forget_tree_state(self):
701
675
        self.text_sha1 = None
 
676
        self.executable = None
702
677
 
703
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
678
    def _snapshot_text(self, file_parents, work_tree, versionedfile_store, transaction):
704
679
        """See InventoryEntry._snapshot_text."""
705
 
        def get_content_byte_lines():
706
 
            return work_tree.get_file(self.file_id).readlines()
707
 
        self.text_sha1, self.text_size = commit_builder.modified_file_text(
708
 
            self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
 
680
        mutter('storing text of file {%s} in revision {%s} into %r',
 
681
               self.file_id, self.revision, versionedfile_store)
 
682
        # special case to avoid diffing on renames or 
 
683
        # reparenting
 
684
        if (len(file_parents) == 1
 
685
            and self.text_sha1 == file_parents.values()[0].text_sha1
 
686
            and self.text_size == file_parents.values()[0].text_size):
 
687
            previous_ie = file_parents.values()[0]
 
688
            versionedfile = versionedfile_store.get_weave(self.file_id, transaction)
 
689
            versionedfile.clone_text(self.revision, previous_ie.revision, file_parents.keys())
 
690
        else:
 
691
            new_lines = work_tree.get_file(self.file_id).readlines()
 
692
            self._add_text_to_weave(new_lines, file_parents.keys(), versionedfile_store,
 
693
                                    transaction)
 
694
            self.text_sha1 = sha_strings(new_lines)
 
695
            self.text_size = sum(map(len, new_lines))
 
696
 
709
697
 
710
698
    def _unchanged(self, previous_ie):
711
699
        """See InventoryEntry._unchanged."""
724
712
class InventoryLink(InventoryEntry):
725
713
    """A file in an inventory."""
726
714
 
727
 
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
728
 
                 'text_id', 'parent_id', 'children', 'executable', 
729
 
                 'revision', 'symlink_target']
 
715
    __slots__ = ['symlink_target']
730
716
 
731
717
    def _check(self, checker, rev_id, tree):
732
718
        """See InventoryEntry._check"""
733
 
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
 
719
        if self.text_sha1 != None or self.text_size != None or self.text_id != None:
734
720
            raise BzrCheckError('symlink {%s} has text in revision {%s}'
735
721
                    % (self.file_id, rev_id))
736
 
        if self.symlink_target is None:
 
722
        if self.symlink_target == None:
737
723
            raise BzrCheckError('symlink {%s} has no target in revision {%s}'
738
724
                    % (self.file_id, rev_id))
739
725
 
807
793
            compatible = False
808
794
        return compatible
809
795
 
810
 
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
811
 
        """See InventoryEntry._snapshot_text."""
812
 
        commit_builder.modified_link(
813
 
            self.file_id, file_parents, self.symlink_target)
814
 
 
815
796
 
816
797
class Inventory(object):
817
798
    """Inventory of versioned files in a tree.
832
813
 
833
814
    >>> inv = Inventory()
834
815
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
835
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
 
816
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT')
836
817
    >>> inv['123-123'].name
837
818
    'hello.c'
838
819
 
846
827
    May also look up by name:
847
828
 
848
829
    >>> [x[0] for x in inv.iter_entries()]
849
 
    ['', u'hello.c']
 
830
    ['hello.c']
850
831
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
851
832
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
852
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
 
833
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
853
834
    """
854
835
    def __init__(self, root_id=ROOT_ID, revision_id=None):
855
836
        """Create or read an inventory.
865
846
        # root id. Rather than generating a random one here.
866
847
        #if root_id is None:
867
848
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
868
 
        self.root = InventoryDirectory(root_id, '', None)
869
 
        # FIXME: this isn't ever used, changing it to self.revision may break
870
 
        # things. TODO make everything use self.revision_id
 
849
        self.root = RootEntry(root_id)
871
850
        self.revision_id = revision_id
872
851
        self._byid = {self.root.file_id: self.root}
873
852
 
 
853
 
874
854
    def copy(self):
875
855
        # TODO: jam 20051218 Should copy also copy the revision_id?
876
 
        entries = self.iter_entries()
877
 
        other = Inventory(entries.next()[1].file_id)
 
856
        other = Inventory(self.root.file_id)
878
857
        # copy recursively so we know directories will be added before
879
858
        # their children.  There are more efficient ways than this...
880
 
        for path, entry in entries():
 
859
        for path, entry in self.iter_entries():
 
860
            if entry == self.root:
 
861
                continue
881
862
            other.add(entry.copy())
882
863
        return other
883
864
 
 
865
 
884
866
    def __iter__(self):
885
867
        return iter(self._byid)
886
868
 
 
869
 
887
870
    def __len__(self):
888
871
        """Returns number of entries."""
889
872
        return len(self._byid)
890
873
 
 
874
 
891
875
    def iter_entries(self, from_dir=None):
892
876
        """Return (path, entry) pairs, in order by name."""
893
 
        if from_dir is None:
894
 
            assert self.root
895
 
            from_dir = self.root
896
 
            yield '', self.root
897
 
        elif isinstance(from_dir, basestring):
898
 
            from_dir = self._byid[from_dir]
899
 
            
900
 
        # unrolling the recursive called changed the time from
901
 
        # 440ms/663ms (inline/total) to 116ms/116ms
902
 
        children = from_dir.children.items()
903
 
        children.sort()
904
 
        children = collections.deque(children)
905
 
        stack = [(u'', children)]
906
 
        while stack:
907
 
            from_dir_relpath, children = stack[-1]
908
 
 
909
 
            while children:
910
 
                name, ie = children.popleft()
911
 
 
912
 
                # we know that from_dir_relpath never ends in a slash
913
 
                # and 'f' doesn't begin with one, we can do a string op, rather
914
 
                # than the checks of pathjoin(), though this means that all paths
915
 
                # start with a slash
916
 
                path = from_dir_relpath + '/' + name
917
 
 
918
 
                yield path[1:], ie
919
 
 
920
 
                if ie.kind != 'directory':
921
 
                    continue
922
 
 
923
 
                # But do this child first
924
 
                new_children = ie.children.items()
925
 
                new_children.sort()
926
 
                new_children = collections.deque(new_children)
927
 
                stack.append((path, new_children))
928
 
                # Break out of inner loop, so that we start outer loop with child
929
 
                break
930
 
            else:
931
 
                # if we finished all children, pop it off the stack
932
 
                stack.pop()
933
 
 
934
 
    def iter_entries_by_dir(self, from_dir=None):
935
 
        """Iterate over the entries in a directory first order.
936
 
 
937
 
        This returns all entries for a directory before returning
938
 
        the entries for children of a directory. This is not
939
 
        lexicographically sorted order, and is a hybrid between
940
 
        depth-first and breadth-first.
941
 
 
942
 
        :return: This yields (path, entry) pairs
943
 
        """
944
 
        # TODO? Perhaps this should return the from_dir so that the root is
945
 
        # yielded? or maybe an option?
946
 
        if from_dir is None:
947
 
            assert self.root
948
 
            from_dir = self.root
949
 
            yield '', self.root
950
 
        elif isinstance(from_dir, basestring):
951
 
            from_dir = self._byid[from_dir]
952
 
            
953
 
        stack = [(u'', from_dir)]
954
 
        while stack:
955
 
            cur_relpath, cur_dir = stack.pop()
956
 
 
957
 
            child_dirs = []
958
 
            for child_name, child_ie in sorted(cur_dir.children.iteritems()):
959
 
 
960
 
                child_relpath = cur_relpath + child_name
961
 
 
962
 
                yield child_relpath, child_ie
963
 
 
964
 
                if child_ie.kind == 'directory':
965
 
                    child_dirs.append((child_relpath+'/', child_ie))
966
 
            stack.extend(reversed(child_dirs))
 
877
        if from_dir == None:
 
878
            assert self.root
 
879
            from_dir = self.root
 
880
        elif isinstance(from_dir, basestring):
 
881
            from_dir = self._byid[from_dir]
 
882
            
 
883
        kids = from_dir.children.items()
 
884
        kids.sort()
 
885
        for name, ie in kids:
 
886
            yield name, ie
 
887
            if ie.kind == 'directory':
 
888
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
 
889
                    yield pathjoin(name, cn), cie
 
890
 
967
891
 
968
892
    def entries(self):
969
893
        """Return list of (path, ie) for all entries except the root.
983
907
        descend(self.root, u'')
984
908
        return accum
985
909
 
 
910
 
986
911
    def directories(self):
987
912
        """Return (path, entry) pairs for all directories, including the root.
988
913
        """
999
924
        descend(self.root, u'')
1000
925
        return accum
1001
926
        
 
927
 
 
928
 
1002
929
    def __contains__(self, file_id):
1003
930
        """True if this entry contains a file with given id.
1004
931
 
1005
932
        >>> inv = Inventory()
1006
933
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1007
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
 
934
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
1008
935
        >>> '123' in inv
1009
936
        True
1010
937
        >>> '456' in inv
1012
939
        """
1013
940
        return file_id in self._byid
1014
941
 
 
942
 
1015
943
    def __getitem__(self, file_id):
1016
944
        """Return the entry for given file_id.
1017
945
 
1018
946
        >>> inv = Inventory()
1019
947
        >>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
1020
 
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
 
948
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
1021
949
        >>> inv['123123'].name
1022
950
        'hello.c'
1023
951
        """
1024
952
        try:
1025
953
            return self._byid[file_id]
1026
954
        except KeyError:
1027
 
            if file_id is None:
 
955
            if file_id == None:
1028
956
                raise BzrError("can't look up file_id None")
1029
957
            else:
1030
958
                raise BzrError("file_id {%s} not in inventory" % file_id)
1031
959
 
 
960
 
1032
961
    def get_file_kind(self, file_id):
1033
962
        return self._byid[file_id].kind
1034
963
 
1035
964
    def get_child(self, parent_id, filename):
1036
965
        return self[parent_id].children.get(filename)
1037
966
 
 
967
 
1038
968
    def add(self, entry):
1039
969
        """Add entry to inventory.
1040
970
 
1054
984
        except KeyError:
1055
985
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
1056
986
 
1057
 
        if entry.name in parent.children:
 
987
        if parent.children.has_key(entry.name):
1058
988
            raise BzrError("%s is already versioned" %
1059
989
                    pathjoin(self.id2path(parent.file_id), entry.name))
1060
990
 
1062
992
        parent.children[entry.name] = entry
1063
993
        return entry
1064
994
 
 
995
 
1065
996
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
1066
997
        """Add entry from a path.
1067
998
 
1069
1000
 
1070
1001
        Returns the new entry object."""
1071
1002
        
1072
 
        parts = osutils.splitpath(relpath)
 
1003
        parts = bzrlib.osutils.splitpath(relpath)
1073
1004
 
1074
1005
        if len(parts) == 0:
1075
1006
            if file_id is None:
1076
1007
                file_id = bzrlib.workingtree.gen_root_id()
1077
 
            self.root = InventoryDirectory(file_id, '', None)
 
1008
            self.root = RootEntry(file_id)
1078
1009
            self._byid = {self.root.file_id: self.root}
1079
1010
            return
1080
1011
        else:
1081
1012
            parent_path = parts[:-1]
1082
1013
            parent_id = self.path2id(parent_path)
1083
 
            if parent_id is None:
 
1014
            if parent_id == None:
1084
1015
                raise NotVersionedError(path=parent_path)
1085
1016
        ie = make_entry(kind, parts[-1], parent_id, file_id)
1086
1017
        return self.add(ie)
1090
1021
 
1091
1022
        >>> inv = Inventory()
1092
1023
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1093
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
 
1024
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
1094
1025
        >>> '123' in inv
1095
1026
        True
1096
1027
        >>> del inv['123']
1106
1037
        if ie.parent_id is not None:
1107
1038
            del self[ie.parent_id].children[ie.name]
1108
1039
 
 
1040
 
1109
1041
    def __eq__(self, other):
1110
1042
        """Compare two sets by comparing their contents.
1111
1043
 
1114
1046
        >>> i1 == i2
1115
1047
        True
1116
1048
        >>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1117
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
 
1049
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1118
1050
        >>> i1 == i2
1119
1051
        False
1120
1052
        >>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1121
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
 
1053
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
1122
1054
        >>> i1 == i2
1123
1055
        True
1124
1056
        """
1125
1057
        if not isinstance(other, Inventory):
1126
1058
            return NotImplemented
1127
1059
 
 
1060
        if len(self._byid) != len(other._byid):
 
1061
            # shortcut: obviously not the same
 
1062
            return False
 
1063
 
1128
1064
        return self._byid == other._byid
1129
1065
 
 
1066
 
1130
1067
    def __ne__(self, other):
1131
1068
        return not self.__eq__(other)
1132
1069
 
 
1070
 
1133
1071
    def __hash__(self):
1134
1072
        raise ValueError('not hashable')
1135
1073
 
1136
1074
    def _iter_file_id_parents(self, file_id):
1137
1075
        """Yield the parents of file_id up to the root."""
1138
 
        while file_id is not None:
 
1076
        while file_id != None:
1139
1077
            try:
1140
1078
                ie = self._byid[file_id]
1141
1079
            except KeyError:
1199
1137
 
1200
1138
        return parent.file_id
1201
1139
 
 
1140
 
1202
1141
    def has_filename(self, names):
1203
1142
        return bool(self.path2id(names))
1204
1143
 
 
1144
 
1205
1145
    def has_id(self, file_id):
1206
1146
        return self._byid.has_key(file_id)
1207
1147
 
 
1148
 
1208
1149
    def rename(self, file_id, new_parent_id, new_name):
1209
1150
        """Move a file within the inventory.
1210
1151
 
1245
1186
    """
1246
1187
    if file_id is None:
1247
1188
        file_id = bzrlib.workingtree.gen_file_id(name)
1248
 
 
1249
 
    norm_name, can_access = osutils.normalized_filename(name)
1250
 
    if norm_name != name:
1251
 
        if can_access:
1252
 
            name = norm_name
1253
 
        else:
1254
 
            # TODO: jam 20060701 This would probably be more useful
1255
 
            #       if the error was raised with the full path
1256
 
            raise errors.InvalidNormalization(name)
1257
 
 
1258
1189
    if kind == 'directory':
1259
1190
        return InventoryDirectory(file_id, name, parent_id)
1260
1191
    elif kind == 'file':
1265
1196
        raise BzrError("unknown kind %r" % kind)
1266
1197
 
1267
1198
 
 
1199
 
1268
1200
_NAME_RE = None
1269
1201
 
1270
1202
def is_valid_name(name):
1271
1203
    global _NAME_RE
1272
 
    if _NAME_RE is None:
 
1204
    if _NAME_RE == None:
1273
1205
        _NAME_RE = re.compile(r'^[^/\\]+$')
1274
1206
        
1275
1207
    return bool(_NAME_RE.match(name))