~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

- constraints on revprops
- tests for this

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
# FIXME: This refactoring of the workingtree code doesn't seem to keep 
18
 
# the WorkingTree's copy of the inventory in sync with the branch.  The
19
 
# branch modifies its working inventory when it does a commit to make
20
 
# missing files permanently removed.
21
17
 
22
18
# TODO: Maybe also keep the full path of the entry, and the children?
23
19
# But those depend on its position within a particular inventory, and
35
31
import types
36
32
 
37
33
import bzrlib
 
34
from bzrlib.errors import BzrError, BzrCheckError
 
35
 
38
36
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
39
 
                            pathjoin, sha_strings)
 
37
                            appendpath, sha_strings)
40
38
from bzrlib.trace import mutter
41
 
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
42
 
                           BzrError, BzrCheckError)
 
39
from bzrlib.errors import NotVersionedError
43
40
 
44
41
 
45
42
class InventoryEntry(object):
79
76
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
80
77
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
81
78
    InventoryFile('2323', 'hello.c', parent_id='123')
82
 
    >>> shouldbe = {0: 'src', 1: pathjoin('src','hello.c')}
83
 
    >>> for ix, j in enumerate(i.iter_entries()):
84
 
    ...   print (j[0] == shouldbe[ix], j[1])
 
79
    >>> for j in i.iter_entries():
 
80
    ...   print j
85
81
    ... 
86
 
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
87
 
    (True, InventoryFile('2323', 'hello.c', parent_id='123'))
 
82
    ('src', InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
 
83
    ('src/hello.c', InventoryFile('2323', 'hello.c', parent_id='123'))
88
84
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
89
85
    Traceback (most recent call last):
90
86
    ...
102
98
    >>> i['2326']
103
99
    InventoryFile('2326', 'wibble.c', parent_id='2325')
104
100
    >>> for path, entry in i.iter_entries():
105
 
    ...     print path
 
101
    ...     print path.replace('\\\\', '/')     # for win32 os.sep
106
102
    ...     assert i.path2id(path)
107
103
    ... 
108
104
    src
110
106
    src/hello.c
111
107
    src/wibble
112
108
    src/wibble/wibble.c
113
 
    >>> i.id2path('2326')
 
109
    >>> i.id2path('2326').replace('\\\\', '/')
114
110
    'src/wibble/wibble.c'
115
111
    """
116
112
    
137
133
        text_diff will be used for textual difference calculation.
138
134
        This is a template method, override _diff in child classes.
139
135
        """
140
 
        self._read_tree_state(tree.id2path(self.file_id), tree)
 
136
        self._read_tree_state(tree)
141
137
        if to_entry:
142
138
            # cannot diff from one kind to another - you must do a removal
143
139
            # and an addif they do not match.
144
140
            assert self.kind == to_entry.kind
145
 
            to_entry._read_tree_state(to_tree.id2path(to_entry.file_id),
146
 
                                      to_tree)
 
141
            to_entry._read_tree_state(to_tree)
147
142
        self._diff(text_diff, from_label, tree, to_label, to_entry, to_tree,
148
143
                   output_to, reverse)
149
144
 
202
197
 
203
198
    def get_tar_item(self, root, dp, now, tree):
204
199
        """Get a tarfile item and a file stream for its content."""
205
 
        item = tarfile.TarInfo(pathjoin(root, dp))
 
200
        item = tarfile.TarInfo(os.path.join(root, dp))
206
201
        # TODO: would be cool to actually set it to the timestamp of the
207
202
        # revision it was last changed
208
203
        item.mtime = now
233
228
        '123'
234
229
        >>> e = InventoryFile('123', 'src/hello.c', ROOT_ID)
235
230
        Traceback (most recent call last):
236
 
        InvalidEntryName: Invalid entry name: src/hello.c
 
231
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
237
232
        """
238
233
        assert isinstance(name, basestring), name
239
234
        if '/' in name or '\\' in name:
240
 
            raise InvalidEntryName(name=name)
 
235
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
 
236
        
241
237
        self.executable = False
242
238
        self.revision = None
243
239
        self.text_sha1 = None
267
263
        
268
264
        This is a template method - implement _put_on_disk in subclasses.
269
265
        """
270
 
        fullpath = pathjoin(dest, dp)
 
266
        fullpath = appendpath(dest, dp)
271
267
        self._put_on_disk(fullpath, tree)
272
 
        mutter("  export {%s} kind %s to %s", self.file_id,
273
 
                self.kind, fullpath)
 
268
        mutter("  export {%s} kind %s to %s" % (self.file_id, self.kind, fullpath))
274
269
 
275
270
    def _put_on_disk(self, fullpath, tree):
276
271
        """Put this entry onto disk at fullpath, from tree tree."""
330
325
        text stored in the text store or weave.
331
326
        """
332
327
        mutter('new parents of %s are %r', path, previous_entries)
333
 
        self._read_tree_state(path, work_tree)
 
328
        self._read_tree_state(work_tree)
334
329
        if len(previous_entries) == 1:
335
330
            # cannot be unchanged unless there is only one parent file rev.
336
331
            parent_ie = previous_entries.values()[0]
396
391
            compatible = False
397
392
        return compatible
398
393
 
399
 
    def _read_tree_state(self, path, work_tree):
 
394
    def _read_tree_state(self, work_tree):
400
395
        """Populate fields in the inventory entry from the given tree.
401
396
        
402
397
        Note that this should be modified to be a noop on virtual trees
407
402
        # first requested, or preload them if they're already known
408
403
        pass            # nothing to do by default
409
404
 
410
 
    def _forget_tree_state(self):
411
 
        pass
412
 
 
413
405
 
414
406
class RootEntry(InventoryEntry):
415
407
 
421
413
        self.children = {}
422
414
        self.kind = 'root_directory'
423
415
        self.parent_id = None
424
 
        self.name = u''
 
416
        self.name = ''
425
417
 
426
418
    def __eq__(self, other):
427
419
        if not isinstance(other, RootEntry):
485
477
            else:
486
478
                checker.repeated_text_cnt += 1
487
479
                return
488
 
 
489
 
        if self.file_id not in checker.checked_weaves:
490
 
            mutter('check weave {%s}', self.file_id)
491
 
            w = tree.get_weave(self.file_id)
492
 
            # Not passing a progress bar, because it creates a new
493
 
            # progress, which overwrites the current progress,
494
 
            # and doesn't look nice
495
 
            w.check()
496
 
            checker.checked_weaves[self.file_id] = True
497
 
        else:
498
 
            w = tree.get_weave_prelude(self.file_id)
499
 
 
500
480
        mutter('check version {%s} of {%s}', rev_id, self.file_id)
 
481
        file_lines = tree.get_file_lines(self.file_id)
501
482
        checker.checked_text_cnt += 1 
502
 
        # We can't check the length, because Weave doesn't store that
503
 
        # information, and the whole point of looking at the weave's
504
 
        # sha1sum is that we don't have to extract the text.
505
 
        if self.text_sha1 != w.get_sha1(self.revision):
506
 
            raise BzrCheckError('text {%s} version {%s} wrong sha1' 
507
 
                                % (self.file_id, self.revision))
 
483
        if self.text_size != sum(map(len, file_lines)):
 
484
            raise BzrCheckError('text {%s} wrong size' % self.text_id)
 
485
        if self.text_sha1 != sha_strings(file_lines):
 
486
            raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
508
487
        checker.checked_texts[t] = self.text_sha1
509
488
 
510
489
    def copy(self):
568
547
        if tree.is_executable(self.file_id):
569
548
            os.chmod(fullpath, 0755)
570
549
 
571
 
    def _read_tree_state(self, path, work_tree):
 
550
    def _read_tree_state(self, work_tree):
572
551
        """See InventoryEntry._read_tree_state."""
573
552
        self.text_sha1 = work_tree.get_file_sha1(self.file_id)
574
553
        self.executable = work_tree.is_executable(self.file_id)
575
554
 
576
 
    def _forget_tree_state(self):
577
 
        self.text_sha1 = None
578
 
        self.executable = None
579
 
 
580
555
    def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
581
556
        """See InventoryEntry._snapshot_text."""
582
557
        mutter('storing file {%s} in revision {%s}',
668
643
 
669
644
    def _put_in_tar(self, item, tree):
670
645
        """See InventoryEntry._put_in_tar."""
671
 
        item.type = tarfile.SYMTYPE
 
646
        iterm.type = tarfile.SYMTYPE
672
647
        fileobj = None
673
648
        item.size = 0
674
649
        item.mode = 0755
682
657
        except OSError,e:
683
658
            raise BzrError("Failed to create symlink %r -> %r, error: %s" % (fullpath, self.symlink_target, e))
684
659
 
685
 
    def _read_tree_state(self, path, work_tree):
 
660
    def _read_tree_state(self, work_tree):
686
661
        """See InventoryEntry._read_tree_state."""
687
662
        self.symlink_target = work_tree.get_symlink_target(self.file_id)
688
663
 
689
 
    def _forget_tree_state(self):
690
 
        self.symlink_target = None
691
 
 
692
664
    def _unchanged(self, previous_ie):
693
665
        """See InventoryEntry._unchanged."""
694
666
        compatible = super(InventoryLink, self)._unchanged(previous_ie)
745
717
        The inventory is created with a default root directory, with
746
718
        an id of None.
747
719
        """
748
 
        # We are letting Branch.create() create a unique inventory
 
720
        # We are letting Branch.initialize() create a unique inventory
749
721
        # root id. Rather than generating a random one here.
750
722
        #if root_id is None:
751
723
        #    root_id = bzrlib.branch.gen_file_id('TREE_ROOT')
787
759
            yield name, ie
788
760
            if ie.kind == 'directory':
789
761
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
790
 
                    yield pathjoin(name, cn), cie
 
762
                    yield os.path.join(name, cn), cie
791
763
 
792
764
 
793
765
    def entries(self):
800
772
            kids = dir_ie.children.items()
801
773
            kids.sort()
802
774
            for name, ie in kids:
803
 
                child_path = pathjoin(dir_path, name)
 
775
                child_path = os.path.join(dir_path, name)
804
776
                accum.append((child_path, ie))
805
777
                if ie.kind == 'directory':
806
778
                    descend(ie, child_path)
807
779
 
808
 
        descend(self.root, u'')
 
780
        descend(self.root, '')
809
781
        return accum
810
782
 
811
783
 
820
792
            kids.sort()
821
793
 
822
794
            for name, child_ie in kids:
823
 
                child_path = pathjoin(parent_path, name)
 
795
                child_path = os.path.join(parent_path, name)
824
796
                descend(child_ie, child_path)
825
 
        descend(self.root, u'')
 
797
        descend(self.root, '')
826
798
        return accum
827
799
        
828
800
 
887
859
 
888
860
        if parent.children.has_key(entry.name):
889
861
            raise BzrError("%s is already versioned" %
890
 
                    pathjoin(self.id2path(parent.file_id), entry.name))
 
862
                    appendpath(self.id2path(parent.file_id), entry.name))
891
863
 
892
864
        self._byid[entry.file_id] = entry
893
865
        parent.children[entry.name] = entry
900
872
        The immediate parent must already be versioned.
901
873
 
902
874
        Returns the new entry object."""
903
 
        from bzrlib.workingtree import gen_file_id
 
875
        from bzrlib.branch import gen_file_id
904
876
        
905
877
        parts = bzrlib.osutils.splitpath(relpath)
 
878
        if len(parts) == 0:
 
879
            raise BzrError("cannot re-add root of inventory")
906
880
 
907
881
        if file_id == None:
908
882
            file_id = gen_file_id(relpath)
909
883
 
910
 
        if len(parts) == 0:
911
 
            self.root = RootEntry(file_id)
912
 
            self._byid = {self.root.file_id: self.root}
913
 
            return
914
 
        else:
915
 
            parent_path = parts[:-1]
916
 
            parent_id = self.path2id(parent_path)
917
 
            if parent_id == None:
918
 
                raise NotVersionedError(path=parent_path)
 
884
        parent_path = parts[:-1]
 
885
        parent_id = self.path2id(parent_path)
 
886
        if parent_id == None:
 
887
            raise NotVersionedError(parent_path)
 
888
 
919
889
        if kind == 'directory':
920
890
            ie = InventoryDirectory(file_id, parts[-1], parent_id)
921
891
        elif kind == 'file':
941
911
        """
942
912
        ie = self[file_id]
943
913
 
944
 
        assert ie.parent_id is None or \
945
 
            self[ie.parent_id].children[ie.name] == ie
 
914
        assert self[ie.parent_id].children[ie.name] == ie
946
915
        
 
916
        # TODO: Test deleting all children; maybe hoist to a separate
 
917
        # deltree method?
 
918
        if ie.kind == 'directory':
 
919
            for cie in ie.children.values():
 
920
                del self[cie.file_id]
 
921
            del ie.children
 
922
 
947
923
        del self._byid[file_id]
948
 
        if ie.parent_id is not None:
949
 
            del self[ie.parent_id].children[ie.name]
 
924
        del self[ie.parent_id].children[ie.name]
950
925
 
951
926
 
952
927
    def __eq__(self, other):
1008
983
        >>> i = Inventory()
1009
984
        >>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
1010
985
        >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
1011
 
        >>> print i.id2path('foo-id')
 
986
        >>> print i.id2path('foo-id').replace(os.sep, '/')
1012
987
        src/foo.c
1013
988
        """
1014
989
        # get all names, skipping root
1015
990
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
1016
 
        if p:
1017
 
            return pathjoin(*p)
1018
 
        else:
1019
 
            return ''
 
991
        return os.sep.join(p)
1020
992
            
1021
993
 
1022
994