/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

  • Committer: Matthieu Moy
  • Date: 2006-07-08 19:32:30 UTC
  • mfrom: (1845 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1857.
  • Revision ID: Matthieu.Moy@imag.fr-20060708193230-3eb72d871471bd5b
merge

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
31
32
import os.path
32
33
import re
33
34
import sys
76
77
    >>> i.path2id('')
77
78
    'TREE_ROOT'
78
79
    >>> i.add(InventoryDirectory('123', 'src', ROOT_ID))
79
 
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
 
80
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None)
80
81
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
81
 
    InventoryFile('2323', 'hello.c', parent_id='123')
 
82
    InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None)
82
83
    >>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
83
84
    >>> for ix, j in enumerate(i.iter_entries()):
84
85
    ...   print (j[0] == shouldbe[ix], j[1])
85
86
    ... 
86
 
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
87
 
    (True, InventoryFile('2323', 'hello.c', parent_id='123'))
 
87
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT', revision=None))
 
88
    (True, InventoryFile('2323', 'hello.c', parent_id='123', sha1=None, len=None))
88
89
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
89
90
    Traceback (most recent call last):
90
91
    ...
91
92
    BzrError: inventory already contains entry with id {2323}
92
93
    >>> i.add(InventoryFile('2324', 'bye.c', '123'))
93
 
    InventoryFile('2324', 'bye.c', parent_id='123')
 
94
    InventoryFile('2324', 'bye.c', parent_id='123', sha1=None, len=None)
94
95
    >>> i.add(InventoryDirectory('2325', 'wibble', '123'))
95
 
    InventoryDirectory('2325', 'wibble', parent_id='123')
 
96
    InventoryDirectory('2325', 'wibble', parent_id='123', revision=None)
96
97
    >>> i.path2id('src/wibble')
97
98
    '2325'
98
99
    >>> '2325' in i
99
100
    True
100
101
    >>> i.add(InventoryFile('2326', 'wibble.c', '2325'))
101
 
    InventoryFile('2326', 'wibble.c', parent_id='2325')
 
102
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
102
103
    >>> i['2326']
103
 
    InventoryFile('2326', 'wibble.c', parent_id='2325')
 
104
    InventoryFile('2326', 'wibble.c', parent_id='2325', sha1=None, len=None)
104
105
    >>> for path, entry in i.iter_entries():
105
106
    ...     print path
106
107
    ...     assert i.path2id(path)
122
123
    RENAMED = 'renamed'
123
124
    MODIFIED_AND_RENAMED = 'modified and renamed'
124
125
    
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()
 
126
    __slots__ = []
134
127
 
135
128
    def detect_changes(self, old_entry):
136
129
        """Return a (text_modified, meta_modified) from this to old_entry.
165
158
                            versioned_file_store,
166
159
                            transaction,
167
160
                            entry_vf=None):
168
 
        """Return the revisions and entries that directly preceed this.
 
161
        """Return the revisions and entries that directly precede this.
169
162
 
170
163
        Returned as a map from revision to inventory entry.
171
164
 
324
317
        raise BzrError("don't know how to export {%s} of kind %r" % (self.file_id, self.kind))
325
318
 
326
319
    def sorted_children(self):
327
 
        l = self.children.items()
328
 
        l.sort()
329
 
        return l
 
320
        return sorted(self.children.items())
330
321
 
331
322
    @staticmethod
332
323
    def versionable_kind(kind):
346
337
        :param inv: Inventory from which the entry was loaded.
347
338
        :param tree: RevisionTree for this entry.
348
339
        """
349
 
        if self.parent_id != None:
 
340
        if self.parent_id is not None:
350
341
            if not inv.has_id(self.parent_id):
351
342
                raise BzrCheckError('missing parent {%s} in inventory for revision {%s}'
352
343
                        % (self.parent_id, rev_id))
402
393
        return 'unchanged'
403
394
 
404
395
    def __repr__(self):
405
 
        return ("%s(%r, %r, parent_id=%r)"
 
396
        return ("%s(%r, %r, parent_id=%r, revision=%r)"
406
397
                % (self.__class__.__name__,
407
398
                   self.file_id,
408
399
                   self.name,
409
 
                   self.parent_id))
 
400
                   self.parent_id,
 
401
                   self.revision))
410
402
 
411
403
    def snapshot(self, revision, path, previous_entries,
412
 
                 work_tree, weave_store, transaction):
 
404
                 work_tree, commit_builder):
413
405
        """Make a snapshot of this entry which may or may not have changed.
414
406
        
415
407
        This means that all its fields are populated, that it has its
417
409
        """
418
410
        mutter('new parents of %s are %r', path, previous_entries)
419
411
        self._read_tree_state(path, work_tree)
 
412
        # TODO: Where should we determine whether to reuse a
 
413
        # previous revision id or create a new revision? 20060606
420
414
        if len(previous_entries) == 1:
421
415
            # cannot be unchanged unless there is only one parent file rev.
422
416
            parent_ie = previous_entries.values()[0]
425
419
                self.revision = parent_ie.revision
426
420
                return "unchanged"
427
421
        return self._snapshot_into_revision(revision, previous_entries, 
428
 
                                            work_tree, weave_store, transaction)
 
422
                                            work_tree, commit_builder)
429
423
 
430
424
    def _snapshot_into_revision(self, revision, previous_entries, work_tree,
431
 
                                weave_store, transaction):
 
425
                                commit_builder):
432
426
        """Record this revision unconditionally into a store.
433
427
 
434
428
        The entry's last-changed revision property (`revision`) is updated to 
440
434
        """
441
435
        mutter('new revision {%s} for {%s}', revision, self.file_id)
442
436
        self.revision = revision
443
 
        self._snapshot_text(previous_entries, work_tree, weave_store,
444
 
                            transaction)
 
437
        self._snapshot_text(previous_entries, work_tree, commit_builder)
445
438
 
446
 
    def _snapshot_text(self, file_parents, work_tree, weave_store, transaction): 
 
439
    def _snapshot_text(self, file_parents, work_tree, commit_builder): 
447
440
        """Record the 'text' of this entry, whatever form that takes.
448
441
        
449
442
        This default implementation simply adds an empty text.
450
443
        """
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)
 
444
        raise NotImplementedError(self._snapshot_text)
454
445
 
455
446
    def __eq__(self, other):
456
447
        if not isinstance(other, InventoryEntry):
477
468
    def _unchanged(self, previous_ie):
478
469
        """Has this entry changed relative to previous_ie.
479
470
 
480
 
        This method should be overriden in child classes.
 
471
        This method should be overridden in child classes.
481
472
        """
482
473
        compatible = True
483
474
        # different inv parent
505
496
 
506
497
class RootEntry(InventoryEntry):
507
498
 
 
499
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
500
                 'text_id', 'parent_id', 'children', 'executable', 
 
501
                 'revision', 'symlink_target']
 
502
 
508
503
    def _check(self, checker, rev_id, tree):
509
504
        """See InventoryEntry._check"""
510
505
 
514
509
        self.kind = 'root_directory'
515
510
        self.parent_id = None
516
511
        self.name = u''
 
512
        self.revision = None
517
513
 
518
514
    def __eq__(self, other):
519
515
        if not isinstance(other, RootEntry):
526
522
class InventoryDirectory(InventoryEntry):
527
523
    """A directory in an inventory."""
528
524
 
 
525
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
526
                 'text_id', 'parent_id', 'children', 'executable', 
 
527
                 'revision', 'symlink_target']
 
528
 
529
529
    def _check(self, checker, rev_id, tree):
530
530
        """See InventoryEntry._check"""
531
 
        if self.text_sha1 != None or self.text_size != None or self.text_id != None:
 
531
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
532
532
            raise BzrCheckError('directory {%s} has text in revision {%s}'
533
533
                                % (self.file_id, rev_id))
534
534
 
561
561
        """See InventoryEntry._put_on_disk."""
562
562
        os.mkdir(fullpath)
563
563
 
 
564
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
565
        """See InventoryEntry._snapshot_text."""
 
566
        commit_builder.modified_directory(self.file_id, file_parents)
 
567
 
564
568
 
565
569
class InventoryFile(InventoryEntry):
566
570
    """A file in an inventory."""
567
571
 
 
572
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
573
                 'text_id', 'parent_id', 'children', 'executable', 
 
574
                 'revision', 'symlink_target']
 
575
 
568
576
    def _check(self, checker, tree_revision_id, tree):
569
577
        """See InventoryEntry._check"""
570
578
        t = (self.file_id, self.revision)
609
617
 
610
618
    def detect_changes(self, old_entry):
611
619
        """See InventoryEntry.detect_changes."""
612
 
        assert self.text_sha1 != None
613
 
        assert old_entry.text_sha1 != None
 
620
        assert self.text_sha1 is not None
 
621
        assert old_entry.text_sha1 is not None
614
622
        text_modified = (self.text_sha1 != old_entry.text_sha1)
615
623
        meta_modified = (self.executable != old_entry.executable)
616
624
        return text_modified, meta_modified
668
676
 
669
677
    def _read_tree_state(self, path, work_tree):
670
678
        """See InventoryEntry._read_tree_state."""
671
 
        self.text_sha1 = work_tree.get_file_sha1(self.file_id)
672
 
        self.executable = work_tree.is_executable(self.file_id)
 
679
        self.text_sha1 = work_tree.get_file_sha1(self.file_id, path=path)
 
680
        # FIXME: 20050930 probe for the text size when getting sha1
 
681
        # in _read_tree_state
 
682
        self.executable = work_tree.is_executable(self.file_id, path=path)
 
683
 
 
684
    def __repr__(self):
 
685
        return ("%s(%r, %r, parent_id=%r, sha1=%r, len=%s)"
 
686
                % (self.__class__.__name__,
 
687
                   self.file_id,
 
688
                   self.name,
 
689
                   self.parent_id,
 
690
                   self.text_sha1,
 
691
                   self.text_size))
673
692
 
674
693
    def _forget_tree_state(self):
675
694
        self.text_sha1 = None
676
 
        self.executable = None
677
695
 
678
 
    def _snapshot_text(self, file_parents, work_tree, versionedfile_store, transaction):
 
696
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
679
697
        """See InventoryEntry._snapshot_text."""
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
 
 
 
698
        def get_content_byte_lines():
 
699
            return work_tree.get_file(self.file_id).readlines()
 
700
        self.text_sha1, self.text_size = commit_builder.modified_file_text(
 
701
            self.file_id, file_parents, get_content_byte_lines, self.text_sha1, self.text_size)
697
702
 
698
703
    def _unchanged(self, previous_ie):
699
704
        """See InventoryEntry._unchanged."""
712
717
class InventoryLink(InventoryEntry):
713
718
    """A file in an inventory."""
714
719
 
715
 
    __slots__ = ['symlink_target']
 
720
    __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
 
721
                 'text_id', 'parent_id', 'children', 'executable', 
 
722
                 'revision', 'symlink_target']
716
723
 
717
724
    def _check(self, checker, rev_id, tree):
718
725
        """See InventoryEntry._check"""
719
 
        if self.text_sha1 != None or self.text_size != None or self.text_id != None:
 
726
        if self.text_sha1 is not None or self.text_size is not None or self.text_id is not None:
720
727
            raise BzrCheckError('symlink {%s} has text in revision {%s}'
721
728
                    % (self.file_id, rev_id))
722
 
        if self.symlink_target == None:
 
729
        if self.symlink_target is None:
723
730
            raise BzrCheckError('symlink {%s} has no target in revision {%s}'
724
731
                    % (self.file_id, rev_id))
725
732
 
793
800
            compatible = False
794
801
        return compatible
795
802
 
 
803
    def _snapshot_text(self, file_parents, work_tree, commit_builder):
 
804
        """See InventoryEntry._snapshot_text."""
 
805
        commit_builder.modified_link(
 
806
            self.file_id, file_parents, self.symlink_target)
 
807
 
796
808
 
797
809
class Inventory(object):
798
810
    """Inventory of versioned files in a tree.
813
825
 
814
826
    >>> inv = Inventory()
815
827
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
816
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT')
 
828
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
817
829
    >>> inv['123-123'].name
818
830
    'hello.c'
819
831
 
827
839
    May also look up by name:
828
840
 
829
841
    >>> [x[0] for x in inv.iter_entries()]
830
 
    ['hello.c']
 
842
    [u'hello.c']
831
843
    >>> inv = Inventory('TREE_ROOT-12345678-12345678')
832
844
    >>> inv.add(InventoryFile('123-123', 'hello.c', ROOT_ID))
833
 
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678')
 
845
    InventoryFile('123-123', 'hello.c', parent_id='TREE_ROOT-12345678-12345678', sha1=None, len=None)
834
846
    """
835
847
    def __init__(self, root_id=ROOT_ID, revision_id=None):
836
848
        """Create or read an inventory.
847
859
        #if root_id is None:
848
860
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
849
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
850
864
        self.revision_id = revision_id
851
865
        self._byid = {self.root.file_id: self.root}
852
866
 
853
 
 
854
867
    def copy(self):
855
868
        # TODO: jam 20051218 Should copy also copy the revision_id?
856
869
        other = Inventory(self.root.file_id)
862
875
            other.add(entry.copy())
863
876
        return other
864
877
 
865
 
 
866
878
    def __iter__(self):
867
879
        return iter(self._byid)
868
880
 
869
 
 
870
881
    def __len__(self):
871
882
        """Returns number of entries."""
872
883
        return len(self._byid)
873
884
 
874
 
 
875
885
    def iter_entries(self, from_dir=None):
876
886
        """Return (path, entry) pairs, in order by name."""
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
 
 
 
887
        if from_dir is None:
 
888
            assert self.root
 
889
            from_dir = self.root
 
890
        elif isinstance(from_dir, basestring):
 
891
            from_dir = self._byid[from_dir]
 
892
            
 
893
        # unrolling the recursive called changed the time from
 
894
        # 440ms/663ms (inline/total) to 116ms/116ms
 
895
        children = from_dir.children.items()
 
896
        children.sort()
 
897
        children = collections.deque(children)
 
898
        stack = [(u'', children)]
 
899
        while stack:
 
900
            from_dir_relpath, children = stack[-1]
 
901
 
 
902
            while children:
 
903
                name, ie = children.popleft()
 
904
 
 
905
                # we know that from_dir_relpath never ends in a slash
 
906
                # and 'f' doesn't begin with one, we can do a string op, rather
 
907
                # than the checks of pathjoin(), though this means that all paths
 
908
                # start with a slash
 
909
                path = from_dir_relpath + '/' + name
 
910
 
 
911
                yield path[1:], ie
 
912
 
 
913
                if ie.kind != 'directory':
 
914
                    continue
 
915
 
 
916
                # But do this child first
 
917
                new_children = ie.children.items()
 
918
                new_children.sort()
 
919
                new_children = collections.deque(new_children)
 
920
                stack.append((path, new_children))
 
921
                # Break out of inner loop, so that we start outer loop with child
 
922
                break
 
923
            else:
 
924
                # if we finished all children, pop it off the stack
 
925
                stack.pop()
 
926
 
 
927
    def iter_entries_by_dir(self, from_dir=None):
 
928
        """Iterate over the entries in a directory first order.
 
929
 
 
930
        This returns all entries for a directory before returning
 
931
        the entries for children of a directory. This is not
 
932
        lexicographically sorted order, and is a hybrid between
 
933
        depth-first and breadth-first.
 
934
 
 
935
        :return: This yields (path, entry) pairs
 
936
        """
 
937
        # TODO? Perhaps this should return the from_dir so that the root is
 
938
        # yielded? or maybe an option?
 
939
        if from_dir is None:
 
940
            assert self.root
 
941
            from_dir = self.root
 
942
        elif isinstance(from_dir, basestring):
 
943
            from_dir = self._byid[from_dir]
 
944
            
 
945
        stack = [(u'', from_dir)]
 
946
        while stack:
 
947
            cur_relpath, cur_dir = stack.pop()
 
948
 
 
949
            child_dirs = []
 
950
            for child_name, child_ie in sorted(cur_dir.children.iteritems()):
 
951
 
 
952
                child_relpath = cur_relpath + child_name
 
953
 
 
954
                yield child_relpath, child_ie
 
955
 
 
956
                if child_ie.kind == 'directory':
 
957
                    child_dirs.append((child_relpath+'/', child_ie))
 
958
            stack.extend(reversed(child_dirs))
891
959
 
892
960
    def entries(self):
893
961
        """Return list of (path, ie) for all entries except the root.
907
975
        descend(self.root, u'')
908
976
        return accum
909
977
 
910
 
 
911
978
    def directories(self):
912
979
        """Return (path, entry) pairs for all directories, including the root.
913
980
        """
924
991
        descend(self.root, u'')
925
992
        return accum
926
993
        
927
 
 
928
 
 
929
994
    def __contains__(self, file_id):
930
995
        """True if this entry contains a file with given id.
931
996
 
932
997
        >>> inv = Inventory()
933
998
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
934
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
 
999
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
935
1000
        >>> '123' in inv
936
1001
        True
937
1002
        >>> '456' in inv
939
1004
        """
940
1005
        return file_id in self._byid
941
1006
 
942
 
 
943
1007
    def __getitem__(self, file_id):
944
1008
        """Return the entry for given file_id.
945
1009
 
946
1010
        >>> inv = Inventory()
947
1011
        >>> inv.add(InventoryFile('123123', 'hello.c', ROOT_ID))
948
 
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT')
 
1012
        InventoryFile('123123', 'hello.c', parent_id='TREE_ROOT', sha1=None, len=None)
949
1013
        >>> inv['123123'].name
950
1014
        'hello.c'
951
1015
        """
952
1016
        try:
953
1017
            return self._byid[file_id]
954
1018
        except KeyError:
955
 
            if file_id == None:
 
1019
            if file_id is None:
956
1020
                raise BzrError("can't look up file_id None")
957
1021
            else:
958
1022
                raise BzrError("file_id {%s} not in inventory" % file_id)
959
1023
 
960
 
 
961
1024
    def get_file_kind(self, file_id):
962
1025
        return self._byid[file_id].kind
963
1026
 
964
1027
    def get_child(self, parent_id, filename):
965
1028
        return self[parent_id].children.get(filename)
966
1029
 
967
 
 
968
1030
    def add(self, entry):
969
1031
        """Add entry to inventory.
970
1032
 
984
1046
        except KeyError:
985
1047
            raise BzrError("parent_id {%s} not in inventory" % entry.parent_id)
986
1048
 
987
 
        if parent.children.has_key(entry.name):
 
1049
        if entry.name in parent.children:
988
1050
            raise BzrError("%s is already versioned" %
989
1051
                    pathjoin(self.id2path(parent.file_id), entry.name))
990
1052
 
992
1054
        parent.children[entry.name] = entry
993
1055
        return entry
994
1056
 
995
 
 
996
1057
    def add_path(self, relpath, kind, file_id=None, parent_id=None):
997
1058
        """Add entry from a path.
998
1059
 
1011
1072
        else:
1012
1073
            parent_path = parts[:-1]
1013
1074
            parent_id = self.path2id(parent_path)
1014
 
            if parent_id == None:
 
1075
            if parent_id is None:
1015
1076
                raise NotVersionedError(path=parent_path)
1016
1077
        ie = make_entry(kind, parts[-1], parent_id, file_id)
1017
1078
        return self.add(ie)
1021
1082
 
1022
1083
        >>> inv = Inventory()
1023
1084
        >>> inv.add(InventoryFile('123', 'foo.c', ROOT_ID))
1024
 
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT')
 
1085
        InventoryFile('123', 'foo.c', parent_id='TREE_ROOT', sha1=None, len=None)
1025
1086
        >>> '123' in inv
1026
1087
        True
1027
1088
        >>> del inv['123']
1037
1098
        if ie.parent_id is not None:
1038
1099
            del self[ie.parent_id].children[ie.name]
1039
1100
 
1040
 
 
1041
1101
    def __eq__(self, other):
1042
1102
        """Compare two sets by comparing their contents.
1043
1103
 
1046
1106
        >>> i1 == i2
1047
1107
        True
1048
1108
        >>> i1.add(InventoryFile('123', 'foo', ROOT_ID))
1049
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
 
1109
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1050
1110
        >>> i1 == i2
1051
1111
        False
1052
1112
        >>> i2.add(InventoryFile('123', 'foo', ROOT_ID))
1053
 
        InventoryFile('123', 'foo', parent_id='TREE_ROOT')
 
1113
        InventoryFile('123', 'foo', parent_id='TREE_ROOT', sha1=None, len=None)
1054
1114
        >>> i1 == i2
1055
1115
        True
1056
1116
        """
1057
1117
        if not isinstance(other, Inventory):
1058
1118
            return NotImplemented
1059
1119
 
1060
 
        if len(self._byid) != len(other._byid):
1061
 
            # shortcut: obviously not the same
1062
 
            return False
1063
 
 
1064
1120
        return self._byid == other._byid
1065
1121
 
1066
 
 
1067
1122
    def __ne__(self, other):
1068
1123
        return not self.__eq__(other)
1069
1124
 
1070
 
 
1071
1125
    def __hash__(self):
1072
1126
        raise ValueError('not hashable')
1073
1127
 
1074
1128
    def _iter_file_id_parents(self, file_id):
1075
1129
        """Yield the parents of file_id up to the root."""
1076
 
        while file_id != None:
 
1130
        while file_id is not None:
1077
1131
            try:
1078
1132
                ie = self._byid[file_id]
1079
1133
            except KeyError:
1137
1191
 
1138
1192
        return parent.file_id
1139
1193
 
1140
 
 
1141
1194
    def has_filename(self, names):
1142
1195
        return bool(self.path2id(names))
1143
1196
 
1144
 
 
1145
1197
    def has_id(self, file_id):
1146
1198
        return self._byid.has_key(file_id)
1147
1199
 
1148
 
 
1149
1200
    def rename(self, file_id, new_parent_id, new_name):
1150
1201
        """Move a file within the inventory.
1151
1202
 
1201
1252
 
1202
1253
def is_valid_name(name):
1203
1254
    global _NAME_RE
1204
 
    if _NAME_RE == None:
 
1255
    if _NAME_RE is None:
1205
1256
        _NAME_RE = re.compile(r'^[^/\\]+$')
1206
1257
        
1207
1258
    return bool(_NAME_RE.match(name))