~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bundle/bundle_data.py

(jelmer) Support upgrading between the 2a and development-colo formats.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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
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
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Read in a bundle stream, and process it into a BundleReader object."""
18
18
 
25
25
    osutils,
26
26
    timestamp,
27
27
    )
28
 
import bzrlib.errors
29
28
from bzrlib.bundle import apply_bundle
30
 
from bzrlib.errors import (TestamentMismatch, BzrError, 
31
 
                           MalformedHeader, MalformedPatches, NotABundle)
32
 
from bzrlib.inventory import (Inventory, InventoryEntry,
33
 
                              InventoryDirectory, InventoryFile,
34
 
                              InventoryLink)
35
 
from bzrlib.osutils import sha_file, sha_string, pathjoin
 
29
from bzrlib.errors import (
 
30
    TestamentMismatch,
 
31
    BzrError,
 
32
    )
 
33
from bzrlib.inventory import (
 
34
    Inventory,
 
35
    InventoryDirectory,
 
36
    InventoryFile,
 
37
    InventoryLink,
 
38
    )
 
39
from bzrlib.osutils import sha_string, pathjoin
36
40
from bzrlib.revision import Revision, NULL_REVISION
37
41
from bzrlib.testament import StrictTestament
38
42
from bzrlib.trace import mutter, warning
39
 
import bzrlib.transport
40
43
from bzrlib.tree import Tree
41
 
import bzrlib.urlutils
42
44
from bzrlib.xml5 import serializer_v5
43
45
 
44
46
 
78
80
            for property in self.properties:
79
81
                key_end = property.find(': ')
80
82
                if key_end == -1:
81
 
                    assert property.endswith(':')
 
83
                    if not property.endswith(':'):
 
84
                        raise ValueError(property)
82
85
                    key = str(property[:-1])
83
86
                    value = ''
84
87
                else:
158
161
    def get_base(self, revision):
159
162
        revision_info = self.get_revision_info(revision.revision_id)
160
163
        if revision_info.base_id is not None:
161
 
            if revision_info.base_id == NULL_REVISION:
162
 
                return None
163
 
            else:
164
 
                return revision_info.base_id
 
164
            return revision_info.base_id
165
165
        if len(revision.parent_ids) == 0:
166
166
            # There is no base listed, and
167
167
            # the lowest revision doesn't have a parent
168
168
            # so this is probably against the empty tree
169
 
            # and thus base truly is None
170
 
            return None
 
169
            # and thus base truly is NULL_REVISION
 
170
            return NULL_REVISION
171
171
        else:
172
172
            return revision.parent_ids[-1]
173
173
 
196
196
    def revision_tree(self, repository, revision_id, base=None):
197
197
        revision = self.get_revision(revision_id)
198
198
        base = self.get_base(revision)
199
 
        assert base != revision_id
 
199
        if base == revision_id:
 
200
            raise AssertionError()
200
201
        if not self._validated_revisions_against_repo:
201
202
            self._validate_references_from_repository(repository)
202
203
        revision_info = self.get_revision_info(revision_id)
203
204
        inventory_revision_id = revision_id
204
 
        bundle_tree = BundleTree(repository.revision_tree(base), 
 
205
        bundle_tree = BundleTree(repository.revision_tree(base),
205
206
                                  inventory_revision_id)
206
207
        self._update_tree(bundle_tree, revision_id)
207
208
 
208
209
        inv = bundle_tree.inventory
209
210
        self._validate_inventory(inv, revision_id)
210
 
        self._validate_revision(inv, revision_id)
 
211
        self._validate_revision(bundle_tree, revision_id)
211
212
 
212
213
        return bundle_tree
213
214
 
240
241
        for rev_info in self.revisions:
241
242
            checked[rev_info.revision_id] = True
242
243
            add_sha(rev_to_sha, rev_info.revision_id, rev_info.sha1)
243
 
                
 
244
 
244
245
        for (rev, rev_info) in zip(self.real_revisions, self.revisions):
245
246
            add_sha(inv_to_sha, rev_info.revision_id, rev_info.inventory_sha1)
246
247
 
248
249
        missing = {}
249
250
        for revision_id, sha1 in rev_to_sha.iteritems():
250
251
            if repository.has_revision(revision_id):
251
 
                testament = StrictTestament.from_revision(repository, 
 
252
                testament = StrictTestament.from_revision(repository,
252
253
                                                          revision_id)
253
254
                local_sha1 = self._testament_sha1_from_revision(repository,
254
255
                                                                revision_id)
255
256
                if sha1 != local_sha1:
256
 
                    raise BzrError('sha1 mismatch. For revision id {%s}' 
 
257
                    raise BzrError('sha1 mismatch. For revision id {%s}'
257
258
                            'local: %s, bundle: %s' % (revision_id, local_sha1, sha1))
258
259
                else:
259
260
                    count += 1
260
261
            elif revision_id not in checked:
261
262
                missing[revision_id] = sha1
262
263
 
263
 
        for inv_id, sha1 in inv_to_sha.iteritems():
264
 
            if repository.has_revision(inv_id):
265
 
                # Note: branch.get_inventory_sha1() just returns the value that
266
 
                # is stored in the revision text, and that value may be out
267
 
                # of date. This is bogus, because that means we aren't
268
 
                # validating the actual text, just that we wrote and read the
269
 
                # string. But for now, what the hell.
270
 
                local_sha1 = repository.get_inventory_sha1(inv_id)
271
 
                if sha1 != local_sha1:
272
 
                    raise BzrError('sha1 mismatch. For inventory id {%s}' 
273
 
                                   'local: %s, bundle: %s' % 
274
 
                                   (inv_id, local_sha1, sha1))
275
 
                else:
276
 
                    count += 1
277
 
 
278
264
        if len(missing) > 0:
279
265
            # I don't know if this is an error yet
280
266
            warning('Not all revision hashes could be validated.'
286
272
        """At this point we should have generated the BundleTree,
287
273
        so build up an inventory, and make sure the hashes match.
288
274
        """
289
 
 
290
 
        assert inv is not None
291
 
 
292
275
        # Now we should have a complete inventory entry.
293
276
        s = serializer_v5.write_inventory_to_string(inv)
294
277
        sha1 = sha_string(s)
295
278
        # Target revision is the last entry in the real_revisions list
296
279
        rev = self.get_revision(revision_id)
297
 
        assert rev.revision_id == revision_id
 
280
        if rev.revision_id != revision_id:
 
281
            raise AssertionError()
298
282
        if sha1 != rev.inventory_sha1:
299
 
            open(',,bogus-inv', 'wb').write(s)
 
283
            f = open(',,bogus-inv', 'wb')
 
284
            try:
 
285
                f.write(s)
 
286
            finally:
 
287
                f.close()
300
288
            warning('Inventory sha hash mismatch for revision %s. %s'
301
289
                    ' != %s' % (revision_id, sha1, rev.inventory_sha1))
302
290
 
303
 
    def _validate_revision(self, inventory, revision_id):
 
291
    def _validate_revision(self, tree, revision_id):
304
292
        """Make sure all revision entries match their checksum."""
305
293
 
306
 
        # This is a mapping from each revision id to it's sha hash
 
294
        # This is a mapping from each revision id to its sha hash
307
295
        rev_to_sha1 = {}
308
 
        
 
296
 
309
297
        rev = self.get_revision(revision_id)
310
298
        rev_info = self.get_revision_info(revision_id)
311
 
        assert rev.revision_id == rev_info.revision_id
312
 
        assert rev.revision_id == revision_id
313
 
        sha1 = self._testament_sha1(rev, inventory)
 
299
        if not (rev.revision_id == rev_info.revision_id):
 
300
            raise AssertionError()
 
301
        if not (rev.revision_id == revision_id):
 
302
            raise AssertionError()
 
303
        sha1 = self._testament_sha1(rev, tree)
314
304
        if sha1 != rev_info.sha1:
315
305
            raise TestamentMismatch(rev.revision_id, rev_info.sha1, sha1)
316
306
        if rev.revision_id in rev_to_sha1:
343
333
                try:
344
334
                    name, value = info_item.split(':', 1)
345
335
                except ValueError:
346
 
                    raise 'Value %r has no colon' % info_item
 
336
                    raise ValueError('Value %r has no colon' % info_item)
347
337
                if name == 'last-changed':
348
338
                    last_changed = value
349
339
                elif name == 'executable':
350
 
                    assert value in ('yes', 'no'), value
351
340
                    val = (value == 'yes')
352
341
                    bundle_tree.note_executable(new_path, val)
353
342
                elif name == 'target':
357
346
            return last_changed, encoding
358
347
 
359
348
        def do_patch(path, lines, encoding):
360
 
            if encoding is not None:
361
 
                assert encoding == 'base64'
 
349
            if encoding == 'base64':
362
350
                patch = base64.decodestring(''.join(lines))
363
 
            else:
 
351
            elif encoding is None:
364
352
                patch =  ''.join(lines)
 
353
            else:
 
354
                raise ValueError(encoding)
365
355
            bundle_tree.note_patch(path, patch)
366
356
 
367
357
        def renamed(kind, extra, lines):
427
417
            revision = get_rev_id(last_modified, path, kind)
428
418
            if lines:
429
419
                do_patch(path, lines, encoding)
430
 
            
 
420
 
431
421
        valid_actions = {
432
422
            'renamed':renamed,
433
423
            'removed':removed,
474
464
 
475
465
 
476
466
class BundleTree(Tree):
 
467
 
477
468
    def __init__(self, base_tree, revision_id):
478
469
        self.base_tree = base_tree
479
470
        self._renamed = {} # Mapping from old_path => new_path
495
486
 
496
487
    def note_rename(self, old_path, new_path):
497
488
        """A file/directory has been renamed from old_path => new_path"""
498
 
        assert new_path not in self._renamed
499
 
        assert old_path not in self._renamed_r
 
489
        if new_path in self._renamed:
 
490
            raise AssertionError(new_path)
 
491
        if old_path in self._renamed_r:
 
492
            raise AssertionError(old_path)
500
493
        self._renamed[new_path] = old_path
501
494
        self._renamed_r[old_path] = new_path
502
495
 
532
525
 
533
526
    def old_path(self, new_path):
534
527
        """Get the old_path (path in the base_tree) for the file at new_path"""
535
 
        assert new_path[:1] not in ('\\', '/')
 
528
        if new_path[:1] in ('\\', '/'):
 
529
            raise ValueError(new_path)
536
530
        old_path = self._renamed.get(new_path)
537
531
        if old_path is not None:
538
532
            return old_path
552
546
        #renamed_r
553
547
        if old_path in self._renamed_r:
554
548
            return None
555
 
        return old_path 
 
549
        return old_path
556
550
 
557
551
    def new_path(self, old_path):
558
552
        """Get the new_path (path in the target_tree) for the file at old_path
559
553
        in the base tree.
560
554
        """
561
 
        assert old_path[:1] not in ('\\', '/')
 
555
        if old_path[:1] in ('\\', '/'):
 
556
            raise ValueError(old_path)
562
557
        new_path = self._renamed_r.get(old_path)
563
558
        if new_path is not None:
564
559
            return new_path
577
572
        #renamed_r
578
573
        if new_path in self._renamed:
579
574
            return None
580
 
        return new_path 
 
575
        return new_path
581
576
 
582
577
    def path2id(self, path):
583
578
        """Return the id of the file present at path in the target tree."""
617
612
                return None
618
613
        new_path = self.id2path(file_id)
619
614
        return self.base_tree.path2id(new_path)
620
 
        
 
615
 
621
616
    def get_file(self, file_id):
622
617
        """Return a file-like object containing the new contents of the
623
618
        file given by file_id.
634
629
            patch_original = None
635
630
        file_patch = self.patches.get(self.id2path(file_id))
636
631
        if file_patch is None:
637
 
            if (patch_original is None and 
 
632
            if (patch_original is None and
638
633
                self.get_kind(file_id) == 'directory'):
639
634
                return StringIO()
640
 
            assert patch_original is not None, "None: %s" % file_id
 
635
            if patch_original is None:
 
636
                raise AssertionError("None: %s" % file_id)
641
637
            return patch_original
642
638
 
643
 
        assert not file_patch.startswith('\\'), \
644
 
            'Malformed patch for %s, %r' % (file_id, file_patch)
 
639
        if file_patch.startswith('\\'):
 
640
            raise ValueError(
 
641
                'Malformed patch for %s, %r' % (file_id, file_patch))
645
642
        return patched_file(file_patch, patch_original)
646
643
 
647
 
    def get_symlink_target(self, file_id):
648
 
        new_path = self.id2path(file_id)
 
644
    def get_symlink_target(self, file_id, path=None):
 
645
        if path is None:
 
646
            path = self.id2path(file_id)
649
647
        try:
650
 
            return self._targets[new_path]
 
648
            return self._targets[path]
651
649
        except KeyError:
652
650
            return self.base_tree.get_symlink_target(file_id)
653
651
 
667
665
        path = self.id2path(file_id)
668
666
        if path in self._last_changed:
669
667
            return self._last_changed[path]
670
 
        return self.base_tree.inventory[file_id].revision
 
668
        return self.base_tree.get_file_revision(file_id)
671
669
 
672
670
    def get_size_and_sha1(self, file_id):
673
671
        """Return the size and sha1 hash of the given file id.
694
692
        This need to be called before ever accessing self.inventory
695
693
        """
696
694
        from os.path import dirname, basename
697
 
 
698
 
        assert self.base_tree is not None
699
695
        base_inv = self.base_tree.inventory
700
696
        inv = Inventory(None, self.revision_id)
701
697
 
720
716
                ie.executable = self.is_executable(file_id)
721
717
            elif kind == 'symlink':
722
718
                ie = InventoryLink(file_id, name, parent_id)
723
 
                ie.symlink_target = self.get_symlink_target(file_id)
 
719
                ie.symlink_target = self.get_symlink_target(file_id, path)
724
720
            ie.revision = revision_id
725
721
 
726
 
            if kind in ('directory', 'symlink'):
727
 
                ie.text_size, ie.text_sha1 = None, None
728
 
            else:
 
722
            if kind == 'file':
729
723
                ie.text_size, ie.text_sha1 = self.get_size_and_sha1(file_id)
730
 
            if (ie.text_size is None) and (kind == 'file'):
731
 
                raise BzrError('Got a text_size of None for file_id %r' % file_id)
 
724
                if ie.text_size is None:
 
725
                    raise BzrError(
 
726
                        'Got a text_size of None for file_id %r' % file_id)
732
727
            inv.add(ie)
733
728
 
734
729
        sorted_entries = self.sorted_path_id()
748
743
        for path, entry in self.inventory.iter_entries():
749
744
            yield entry.file_id
750
745
 
 
746
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
747
        # The only files returned by this are those from the version
 
748
        inv = self.inventory
 
749
        if from_dir is None:
 
750
            from_dir_id = None
 
751
        else:
 
752
            from_dir_id = inv.path2id(from_dir)
 
753
            if from_dir_id is None:
 
754
                # Directory not versioned
 
755
                return
 
756
        entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
 
757
        if inv.root is not None and not include_root and from_dir is None:
 
758
            # skip the root for compatability with the current apis.
 
759
            entries.next()
 
760
        for path, entry in entries:
 
761
            yield path, 'V', entry.kind, entry.file_id, entry
 
762
 
751
763
    def sorted_path_id(self):
752
764
        paths = []
753
765
        for result in self._new_id.iteritems():
754
766
            paths.append(result)
755
 
        for id in self.base_tree:
 
767
        for id in self.base_tree.all_file_ids():
756
768
            path = self.id2path(id)
757
769
            if path is None:
758
770
                continue