~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/inventory.py

 * The internal storage of history, and logical branch identity have now
   been split into Branch, and Repository. The common locking and file 
   management routines are now in bzrlib.lockablefiles. 
   (Aaron Bentley, Robert Collins, Martin Pool)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# (C) 2005 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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
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.
17
21
 
18
22
# TODO: Maybe also keep the full path of the entry, and the children?
19
23
# But those depend on its position within a particular inventory, and
31
35
import types
32
36
 
33
37
import bzrlib
34
 
from bzrlib.errors import BzrError, BzrCheckError
35
 
 
36
38
from bzrlib.osutils import (pumpfile, quotefn, splitpath, joinpath,
37
 
                            appendpath, sha_strings)
 
39
                            pathjoin, sha_strings)
38
40
from bzrlib.trace import mutter
39
 
from bzrlib.errors import NotVersionedError
 
41
from bzrlib.errors import (NotVersionedError, InvalidEntryName,
 
42
                           BzrError, BzrCheckError)
40
43
 
41
44
 
42
45
class InventoryEntry(object):
76
79
    InventoryDirectory('123', 'src', parent_id='TREE_ROOT')
77
80
    >>> i.add(InventoryFile('2323', 'hello.c', parent_id='123'))
78
81
    InventoryFile('2323', 'hello.c', parent_id='123')
79
 
    >>> for j in i.iter_entries():
80
 
    ...   print j
 
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])
81
85
    ... 
82
 
    ('src', InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
83
 
    ('src/hello.c', InventoryFile('2323', 'hello.c', parent_id='123'))
 
86
    (True, InventoryDirectory('123', 'src', parent_id='TREE_ROOT'))
 
87
    (True, InventoryFile('2323', 'hello.c', parent_id='123'))
84
88
    >>> i.add(InventoryFile('2323', 'bye.c', '123'))
85
89
    Traceback (most recent call last):
86
90
    ...
98
102
    >>> i['2326']
99
103
    InventoryFile('2326', 'wibble.c', parent_id='2325')
100
104
    >>> for path, entry in i.iter_entries():
101
 
    ...     print path.replace('\\\\', '/')     # for win32 os.sep
 
105
    ...     print path
102
106
    ...     assert i.path2id(path)
103
107
    ... 
104
108
    src
106
110
    src/hello.c
107
111
    src/wibble
108
112
    src/wibble/wibble.c
109
 
    >>> i.id2path('2326').replace('\\\\', '/')
 
113
    >>> i.id2path('2326')
110
114
    'src/wibble/wibble.c'
111
115
    """
112
116
    
114
118
                 'text_id', 'parent_id', 'children', 'executable', 
115
119
                 'revision']
116
120
 
117
 
    def _add_text_to_weave(self, new_lines, parents, weave_store):
118
 
        weave_store.add_text(self.file_id, self.revision, new_lines, parents)
 
121
    def _add_text_to_weave(self, new_lines, parents, weave_store, transaction):
 
122
        weave_store.add_text(self.file_id, self.revision, new_lines, parents,
 
123
                             transaction)
119
124
 
120
125
    def detect_changes(self, old_entry):
121
126
        """Return a (text_modified, meta_modified) from this to old_entry.
165
170
                ie = inv[self.file_id]
166
171
                assert ie.file_id == self.file_id
167
172
                if ie.revision in heads:
 
173
                    # fixup logic, there was a bug in revision updates.
 
174
                    # with x bit support.
 
175
                    try:
 
176
                        if heads[ie.revision].executable != ie.executable:
 
177
                            heads[ie.revision].executable = False
 
178
                            ie.executable = False
 
179
                    except AttributeError:
 
180
                        pass
168
181
                    assert heads[ie.revision] == ie
169
182
                else:
170
183
                    # may want to add it.
189
202
 
190
203
    def get_tar_item(self, root, dp, now, tree):
191
204
        """Get a tarfile item and a file stream for its content."""
192
 
        item = tarfile.TarInfo(os.path.join(root, dp))
 
205
        item = tarfile.TarInfo(pathjoin(root, dp))
193
206
        # TODO: would be cool to actually set it to the timestamp of the
194
207
        # revision it was last changed
195
208
        item.mtime = now
220
233
        '123'
221
234
        >>> e = InventoryFile('123', 'src/hello.c', ROOT_ID)
222
235
        Traceback (most recent call last):
223
 
        BzrCheckError: InventoryEntry name 'src/hello.c' is invalid
 
236
        InvalidEntryName: Invalid entry name: src/hello.c
224
237
        """
225
238
        assert isinstance(name, basestring), name
226
239
        if '/' in name or '\\' in name:
227
 
            raise BzrCheckError('InventoryEntry name %r is invalid' % name)
228
 
        
 
240
            raise InvalidEntryName(name=name)
229
241
        self.executable = False
230
242
        self.revision = None
231
243
        self.text_sha1 = None
255
267
        
256
268
        This is a template method - implement _put_on_disk in subclasses.
257
269
        """
258
 
        fullpath = appendpath(dest, dp)
 
270
        fullpath = pathjoin(dest, dp)
259
271
        self._put_on_disk(fullpath, tree)
260
 
        mutter("  export {%s} kind %s to %s" % (self.file_id, self.kind, fullpath))
 
272
        mutter("  export {%s} kind %s to %s", self.file_id,
 
273
                self.kind, fullpath)
261
274
 
262
275
    def _put_on_disk(self, fullpath, tree):
263
276
        """Put this entry onto disk at fullpath, from tree tree."""
310
323
                   self.parent_id))
311
324
 
312
325
    def snapshot(self, revision, path, previous_entries,
313
 
                 work_tree, weave_store):
 
326
                 work_tree, weave_store, transaction):
314
327
        """Make a snapshot of this entry which may or may not have changed.
315
328
        
316
329
        This means that all its fields are populated, that it has its
326
339
                self.revision = parent_ie.revision
327
340
                return "unchanged"
328
341
        return self.snapshot_revision(revision, previous_entries, 
329
 
                                      work_tree, weave_store)
 
342
                                      work_tree, weave_store, transaction)
330
343
 
331
344
    def snapshot_revision(self, revision, previous_entries, work_tree,
332
 
                          weave_store):
 
345
                          weave_store, transaction):
333
346
        """Record this revision unconditionally."""
334
347
        mutter('new revision for {%s}', self.file_id)
335
348
        self.revision = revision
336
349
        change = self._get_snapshot_change(previous_entries)
337
 
        self._snapshot_text(previous_entries, work_tree, weave_store)
 
350
        self._snapshot_text(previous_entries, work_tree, weave_store,
 
351
                            transaction)
338
352
        return change
339
353
 
340
 
    def _snapshot_text(self, file_parents, work_tree, weave_store): 
 
354
    def _snapshot_text(self, file_parents, work_tree, weave_store, transaction): 
341
355
        """Record the 'text' of this entry, whatever form that takes.
342
356
        
343
357
        This default implementation simply adds an empty text.
344
358
        """
345
359
        mutter('storing file {%s} in revision {%s}',
346
360
               self.file_id, self.revision)
347
 
        self._add_text_to_weave([], file_parents, weave_store)
 
361
        self._add_text_to_weave([], file_parents, weave_store, transaction)
348
362
 
349
363
    def __eq__(self, other):
350
364
        if not isinstance(other, InventoryEntry):
388
402
        Note that this should be modified to be a noop on virtual trees
389
403
        as all entries created there are prepopulated.
390
404
        """
 
405
        # TODO: Rather than running this manually, we should check the 
 
406
        # working sha1 and other expensive properties when they're
 
407
        # first requested, or preload them if they're already known
 
408
        pass            # nothing to do by default
391
409
 
392
410
 
393
411
class RootEntry(InventoryEntry):
400
418
        self.children = {}
401
419
        self.kind = 'root_directory'
402
420
        self.parent_id = None
403
 
        self.name = ''
 
421
        self.name = u''
404
422
 
405
423
    def __eq__(self, other):
406
424
        if not isinstance(other, RootEntry):
464
482
            else:
465
483
                checker.repeated_text_cnt += 1
466
484
                return
 
485
 
 
486
        if self.file_id not in checker.checked_weaves:
 
487
            mutter('check weave {%s}', self.file_id)
 
488
            w = tree.get_weave(self.file_id)
 
489
            # Not passing a progress bar, because it creates a new
 
490
            # progress, which overwrites the current progress,
 
491
            # and doesn't look nice
 
492
            w.check()
 
493
            checker.checked_weaves[self.file_id] = True
 
494
        else:
 
495
            w = tree.get_weave_prelude(self.file_id)
 
496
 
467
497
        mutter('check version {%s} of {%s}', rev_id, self.file_id)
468
 
        file_lines = tree.get_file_lines(self.file_id)
469
498
        checker.checked_text_cnt += 1 
470
 
        if self.text_size != sum(map(len, file_lines)):
471
 
            raise BzrCheckError('text {%s} wrong size' % self.text_id)
472
 
        if self.text_sha1 != sha_strings(file_lines):
473
 
            raise BzrCheckError('text {%s} wrong sha1' % self.text_id)
 
499
        # We can't check the length, because Weave doesn't store that
 
500
        # information, and the whole point of looking at the weave's
 
501
        # sha1sum is that we don't have to extract the text.
 
502
        if self.text_sha1 != w.get_sha1(self.revision):
 
503
            raise BzrCheckError('text {%s} version {%s} wrong sha1' 
 
504
                                % (self.file_id, self.revision))
474
505
        checker.checked_texts[t] = self.text_sha1
475
506
 
476
507
    def copy(self):
539
570
        self.text_sha1 = work_tree.get_file_sha1(self.file_id)
540
571
        self.executable = work_tree.is_executable(self.file_id)
541
572
 
542
 
    def _snapshot_text(self, file_parents, work_tree, weave_store): 
 
573
    def _snapshot_text(self, file_parents, work_tree, weave_store, transaction):
543
574
        """See InventoryEntry._snapshot_text."""
544
575
        mutter('storing file {%s} in revision {%s}',
545
576
               self.file_id, self.revision)
551
582
            previous_ie = file_parents.values()[0]
552
583
            weave_store.add_identical_text(
553
584
                self.file_id, previous_ie.revision, 
554
 
                self.revision, file_parents)
 
585
                self.revision, file_parents, transaction)
555
586
        else:
556
587
            new_lines = work_tree.get_file(self.file_id).readlines()
557
 
            self._add_text_to_weave(new_lines, file_parents, weave_store)
 
588
            self._add_text_to_weave(new_lines, file_parents, weave_store,
 
589
                                    transaction)
558
590
            self.text_sha1 = sha_strings(new_lines)
559
591
            self.text_size = sum(map(len, new_lines))
560
592
 
568
600
            # FIXME: 20050930 probe for the text size when getting sha1
569
601
            # in _read_tree_state
570
602
            self.text_size = previous_ie.text_size
 
603
        if self.executable != previous_ie.executable:
 
604
            compatible = False
571
605
        return compatible
572
606
 
573
607
 
627
661
 
628
662
    def _put_in_tar(self, item, tree):
629
663
        """See InventoryEntry._put_in_tar."""
630
 
        iterm.type = tarfile.SYMTYPE
 
664
        item.type = tarfile.SYMTYPE
631
665
        fileobj = None
632
666
        item.size = 0
633
667
        item.mode = 0755
743
777
            yield name, ie
744
778
            if ie.kind == 'directory':
745
779
                for cn, cie in self.iter_entries(from_dir=ie.file_id):
746
 
                    yield os.path.join(name, cn), cie
 
780
                    yield pathjoin(name, cn), cie
747
781
 
748
782
 
749
783
    def entries(self):
756
790
            kids = dir_ie.children.items()
757
791
            kids.sort()
758
792
            for name, ie in kids:
759
 
                child_path = os.path.join(dir_path, name)
 
793
                child_path = pathjoin(dir_path, name)
760
794
                accum.append((child_path, ie))
761
795
                if ie.kind == 'directory':
762
796
                    descend(ie, child_path)
763
797
 
764
 
        descend(self.root, '')
 
798
        descend(self.root, u'')
765
799
        return accum
766
800
 
767
801
 
776
810
            kids.sort()
777
811
 
778
812
            for name, child_ie in kids:
779
 
                child_path = os.path.join(parent_path, name)
 
813
                child_path = pathjoin(parent_path, name)
780
814
                descend(child_ie, child_path)
781
 
        descend(self.root, '')
 
815
        descend(self.root, u'')
782
816
        return accum
783
817
        
784
818
 
843
877
 
844
878
        if parent.children.has_key(entry.name):
845
879
            raise BzrError("%s is already versioned" %
846
 
                    appendpath(self.id2path(parent.file_id), entry.name))
 
880
                    pathjoin(self.id2path(parent.file_id), entry.name))
847
881
 
848
882
        self._byid[entry.file_id] = entry
849
883
        parent.children[entry.name] = entry
856
890
        The immediate parent must already be versioned.
857
891
 
858
892
        Returns the new entry object."""
859
 
        from bzrlib.branch import gen_file_id
 
893
        from bzrlib.workingtree import gen_file_id
860
894
        
861
895
        parts = bzrlib.osutils.splitpath(relpath)
862
896
        if len(parts) == 0:
868
902
        parent_path = parts[:-1]
869
903
        parent_id = self.path2id(parent_path)
870
904
        if parent_id == None:
871
 
            raise NotVersionedError(parent_path)
872
 
 
 
905
            raise NotVersionedError(path=parent_path)
873
906
        if kind == 'directory':
874
907
            ie = InventoryDirectory(file_id, parts[-1], parent_id)
875
908
        elif kind == 'file':
967
1000
        >>> i = Inventory()
968
1001
        >>> e = i.add(InventoryDirectory('src-id', 'src', ROOT_ID))
969
1002
        >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
970
 
        >>> print i.id2path('foo-id').replace(os.sep, '/')
 
1003
        >>> print i.id2path('foo-id')
971
1004
        src/foo.c
972
1005
        """
973
1006
        # get all names, skipping root
974
1007
        p = [self._byid[fid].name for fid in self.get_idpath(file_id)[1:]]
975
 
        return os.sep.join(p)
 
1008
        if p:
 
1009
            return pathjoin(*p)
 
1010
        else:
 
1011
            return ''
976
1012
            
977
1013
 
978
1014