~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

Merged bzr.dev and updated NEWS with a better description of changes

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004-2006 by Canonical Ltd
 
1
# Copyright (C) 2004, 2005, 2006, 2007 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
19
19
import sys
20
20
import tempfile
21
21
 
22
 
from bzrlib import inventory
23
 
from bzrlib.builtins import merge
 
22
from bzrlib import (
 
23
    bzrdir,
 
24
    errors,
 
25
    inventory,
 
26
    repository,
 
27
    revision as _mod_revision,
 
28
    treebuilder,
 
29
    )
24
30
from bzrlib.bzrdir import BzrDir
25
31
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
26
32
from bzrlib.bundle.bundle_data import BundleTree
27
 
from bzrlib.bundle.serializer import write_bundle, read_bundle
 
33
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
 
34
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
 
35
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
 
36
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
28
37
from bzrlib.branch import Branch
29
38
from bzrlib.diff import internal_diff
30
 
from bzrlib.errors import BzrError, TestamentMismatch, NotABundle, BadBundle
 
39
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
 
40
                           NoSuchFile,)
31
41
from bzrlib.merge import Merge3Merger
 
42
from bzrlib.repofmt import knitrepo
32
43
from bzrlib.osutils import has_symlinks, sha_file
33
44
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
34
 
                          TestCase, TestSkipped)
 
45
                          TestCase, TestSkipped, test_commit)
35
46
from bzrlib.transform import TreeTransform
36
47
from bzrlib.workingtree import WorkingTree
37
48
 
300
311
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
301
312
 
302
313
 
303
 
class BundleTester(TestCaseWithTransport):
 
314
class BundleTester1(TestCaseWithTransport):
 
315
 
 
316
    def test_mismatched_bundle(self):
 
317
        format = bzrdir.BzrDirMetaFormat1()
 
318
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
319
        serializer = BundleSerializerV08('0.8')
 
320
        b = self.make_branch('.', format=format)
 
321
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
 
322
                          b.repository, [], {}, StringIO())
 
323
 
 
324
    def test_matched_bundle(self):
 
325
        """Don't raise IncompatibleBundleFormat for knit2 and bundle0.9"""
 
326
        format = bzrdir.BzrDirMetaFormat1()
 
327
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
328
        serializer = BundleSerializerV09('0.9')
 
329
        b = self.make_branch('.', format=format)
 
330
        serializer.write(b.repository, [], {}, StringIO())
 
331
 
 
332
    def test_mismatched_model(self):
 
333
        """Try copying a bundle from knit2 to knit1"""
 
334
        format = bzrdir.BzrDirMetaFormat1()
 
335
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
336
        source = self.make_branch_and_tree('source', format=format)
 
337
        source.commit('one', rev_id='one-id')
 
338
        source.commit('two', rev_id='two-id')
 
339
        text = StringIO()
 
340
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
 
341
                     format='0.9')
 
342
        text.seek(0)
 
343
 
 
344
        format = bzrdir.BzrDirMetaFormat1()
 
345
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
346
        target = self.make_branch('target', format=format)
 
347
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
348
                          target.repository, read_bundle(text))
 
349
 
 
350
 
 
351
class BundleTester(object):
 
352
 
 
353
    def bzrdir_format(self):
 
354
        format = bzrdir.BzrDirMetaFormat1()
 
355
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
356
        return format
 
357
 
 
358
    def make_branch_and_tree(self, path, format=None):
 
359
        if format is None:
 
360
            format = self.bzrdir_format()
 
361
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
362
 
 
363
    def make_branch(self, path, format=None):
 
364
        if format is None:
 
365
            format = self.bzrdir_format()
 
366
        return TestCaseWithTransport.make_branch(self, path, format)
304
367
 
305
368
    def create_bundle_text(self, base_rev_id, rev_id):
306
369
        bundle_txt = StringIO()
307
370
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
308
 
                               bundle_txt)
 
371
                               bundle_txt, format=self.format)
309
372
        bundle_txt.seek(0)
310
373
        self.assertEqual(bundle_txt.readline(), 
311
 
                         '# Bazaar revision bundle v0.8\n')
 
374
                         '# Bazaar revision bundle v%s\n' % self.format)
312
375
        self.assertEqual(bundle_txt.readline(), '#\n')
313
376
 
314
377
        rev = self.b1.repository.get_revision(rev_id)
315
378
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
316
379
                         u'# message:\n')
317
 
 
318
 
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
319
380
        bundle_txt.seek(0)
320
381
        return bundle_txt, rev_ids
321
382
 
390
451
        else:
391
452
            if not os.path.exists(checkout_dir):
392
453
                os.mkdir(checkout_dir)
393
 
        tree = BzrDir.create_standalone_workingtree(checkout_dir)
 
454
        tree = self.make_branch_and_tree(checkout_dir)
394
455
        s = StringIO()
395
 
        ancestors = write_bundle(self.b1.repository, rev_id, None, s)
 
456
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
 
457
                                 format=self.format)
396
458
        s.seek(0)
397
459
        assert isinstance(s.getvalue(), str), (
398
460
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
411
473
            for inventory_id in old:
412
474
                try:
413
475
                    old_file = old.get_file(inventory_id)
414
 
                except:
 
476
                except NoSuchFile:
415
477
                    continue
416
478
                if old_file is None:
417
479
                    continue
418
480
                self.assertEqual(old_file.read(),
419
481
                                 new.get_file(inventory_id).read())
420
 
        if rev_id is not None:
 
482
        if not _mod_revision.is_null(rev_id):
421
483
            rh = self.b1.revision_history()
422
484
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
423
485
            tree.update()
424
486
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
425
487
            self.assertFalse(delta.has_changed(),
426
 
                             'Working tree has modifications')
 
488
                             'Working tree has modifications: %s' % delta)
427
489
        return tree
428
490
 
429
491
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
431
493
        sure everything matches the builtin branch.
432
494
        """
433
495
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
 
496
        original_parents = to_tree.get_parent_ids()
434
497
        repository = to_tree.branch.repository
 
498
        original_parents = to_tree.get_parent_ids()
435
499
        self.assertIs(repository.has_revision(base_rev_id), True)
436
500
        for rev in info.real_revisions:
437
501
            self.assert_(not repository.has_revision(rev.revision_id),
447
511
        self.assert_(to_tree.branch.repository.has_revision(info.target))
448
512
        # Do we also want to verify that all the texts have been added?
449
513
 
450
 
        self.assert_(info.target in to_tree.pending_merges())
451
 
 
 
514
        self.assertEqual(original_parents + [info.target],
 
515
            to_tree.get_parent_ids())
452
516
 
453
517
        rev = info.real_revisions[-1]
454
518
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
482
546
        self.tree1.add('one')
483
547
        self.tree1.commit('add one', rev_id='a@cset-0-1')
484
548
 
485
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
486
 
        # FIXME: The current write_bundle api no longer supports
487
 
        #        setting a custom summary message
488
 
        #        We should re-introduce the ability, and update
489
 
        #        the tests to make sure it works.
490
 
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
491
 
        #         message='With a specialized message')
 
549
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
492
550
 
493
551
        # Make sure we can handle files with spaces, tabs, other
494
552
        # bogus characters
507
565
        tt = TreeTransform(self.tree1)
508
566
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
509
567
        tt.apply()
 
568
        # have to fix length of file-id so that we can predictably rewrite
 
569
        # a (length-prefixed) record containing it later.
 
570
        self.tree1.add('with space.txt', 'withspace-id')
510
571
        self.tree1.add([
511
 
                'with space.txt'
512
 
                , 'dir'
 
572
                  'dir'
513
573
                , 'dir/filein subdir.c'
514
574
                , 'dir/WithCaps.txt'
515
575
                , 'dir/ pre space'
524
584
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
525
585
 
526
586
        # Check a rollup bundle 
527
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
 
587
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
528
588
 
529
589
        # Now delete entries
530
590
        self.tree1.remove(
539
599
        self.tree1.commit('removed', rev_id='a@cset-0-3')
540
600
        
541
601
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
542
 
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
543
 
                          'a@cset-0-2', 'a@cset-0-3')
 
602
        self.assertRaises((TestamentMismatch,
 
603
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
604
            'a@cset-0-2', 'a@cset-0-3')
544
605
        # Check a rollup bundle 
545
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
 
606
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
546
607
 
547
608
        # Now move the directory
548
609
        self.tree1.rename_one('dir', 'sub/dir')
550
611
 
551
612
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
552
613
        # Check a rollup bundle 
553
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
 
614
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
554
615
 
555
616
        # Modified files
556
617
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
557
 
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
 
618
        open('b1/sub/dir/ pre space', 'ab').write(
 
619
             '\r\nAdding some\r\nDOS format lines\r\n')
558
620
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
559
621
        self.tree1.rename_one('sub/dir/ pre space', 
560
622
                              'sub/ start space')
568
630
                          verbose=False)
569
631
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
570
632
        other = self.get_checkout('a@cset-0-5')
 
633
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
 
634
            'a@cset-0-5')
 
635
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
636
        self.assertEqualDiff(tree1_inv, tree2_inv)
571
637
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
572
638
        other.commit('rename file', rev_id='a@cset-0-6b')
573
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
 
639
        self.tree1.merge_from_branch(other.branch)
574
640
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
575
641
                          verbose=False)
576
642
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
578
644
    def test_symlink_bundle(self):
579
645
        if not has_symlinks():
580
646
            raise TestSkipped("No symlink support")
581
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
647
        self.tree1 = self.make_branch_and_tree('b1')
582
648
        self.b1 = self.tree1.branch
583
649
        tt = TreeTransform(self.tree1)
584
650
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
585
651
        tt.apply()
586
652
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
587
 
        self.get_valid_bundle(None, 'l@cset-0-1')
 
653
        self.get_valid_bundle('null:', 'l@cset-0-1')
588
654
        tt = TreeTransform(self.tree1)
589
655
        trans_id = tt.trans_id_tree_file_id('link-1')
590
656
        tt.adjust_path('link2', tt.root, trans_id)
608
674
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
609
675
 
610
676
    def test_binary_bundle(self):
611
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
677
        self.tree1 = self.make_branch_and_tree('b1')
612
678
        self.b1 = self.tree1.branch
613
679
        tt = TreeTransform(self.tree1)
614
680
        
615
681
        # Add
616
682
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
617
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
 
683
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
 
684
            'binary-2')
618
685
        tt.apply()
619
686
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
620
 
        self.get_valid_bundle(None, 'b@cset-0-1')
 
687
        self.get_valid_bundle('null:', 'b@cset-0-1')
621
688
 
622
689
        # Delete
623
690
        tt = TreeTransform(self.tree1)
647
714
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
648
715
 
649
716
        # Rollup
650
 
        self.get_valid_bundle(None, 'b@cset-0-4')
 
717
        self.get_valid_bundle('null:', 'b@cset-0-4')
651
718
 
652
719
    def test_last_modified(self):
653
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
720
        self.tree1 = self.make_branch_and_tree('b1')
654
721
        self.b1 = self.tree1.branch
655
722
        tt = TreeTransform(self.tree1)
656
723
        tt.new_file('file', tt.root, 'file', 'file')
671
738
        tt.create_file('file2', trans_id)
672
739
        tt.apply()
673
740
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
674
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
 
741
        self.tree1.merge_from_branch(other.branch)
675
742
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
676
743
                          verbose=False)
677
744
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
678
745
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
679
746
 
680
747
    def test_hide_history(self):
681
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
748
        self.tree1 = self.make_branch_and_tree('b1')
682
749
        self.b1 = self.tree1.branch
683
750
 
684
751
        open('b1/one', 'wb').write('one\n')
690
757
        self.tree1.commit('modify', rev_id='a@cset-0-3')
691
758
        bundle_file = StringIO()
692
759
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
 
760
                               'a@cset-0-1', bundle_file, format=self.format)
 
761
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
 
762
        self.assertContainsRe(self.get_raw(bundle_file), 'one')
 
763
        self.assertContainsRe(self.get_raw(bundle_file), 'three')
 
764
 
 
765
    def test_bundle_same_basis(self):
 
766
        """Ensure using the basis as the target doesn't cause an error"""
 
767
        self.tree1 = self.make_branch_and_tree('b1')
 
768
        self.tree1.commit('add file', rev_id='a@cset-0-1')
 
769
        bundle_file = StringIO()
 
770
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-1',
693
771
                               'a@cset-0-1', bundle_file)
694
 
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
695
 
        self.assertContainsRe(bundle_file.getvalue(), 'one')
696
 
        self.assertContainsRe(bundle_file.getvalue(), 'three')
 
772
 
 
773
    @staticmethod
 
774
    def get_raw(bundle_file):
 
775
        return bundle_file.getvalue()
697
776
 
698
777
    def test_unicode_bundle(self):
699
778
        # Handle international characters
711
790
            u'William Dod\xe9\n').encode('utf-8'))
712
791
        f.close()
713
792
 
714
 
        self.tree1.add([u'with Dod\xe9'])
715
 
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
 
793
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
 
794
        self.tree1.commit(u'i18n commit from William Dod\xe9',
716
795
                          rev_id='i18n-1', committer=u'William Dod\xe9')
717
796
 
 
797
        if sys.platform == 'darwin':
 
798
            # On Mac the '\xe9' gets changed to 'e\u0301'
 
799
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
 
800
                             sorted(os.listdir(u'b1')))
 
801
            delta = self.tree1.changes_from(self.tree1.basis_tree())
 
802
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
 
803
                             delta.removed)
 
804
            self.knownFailure("Mac OSX doesn't preserve unicode"
 
805
                              " combining characters.")
 
806
 
718
807
        # Add
719
 
        bundle = self.get_valid_bundle(None, 'i18n-1')
 
808
        bundle = self.get_valid_bundle('null:', 'i18n-1')
720
809
 
721
810
        # Modified
722
811
        f = open(u'b1/with Dod\xe9', 'wb')
740
829
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
741
830
 
742
831
        # Rollup
743
 
        bundle = self.get_valid_bundle(None, 'i18n-4')
 
832
        bundle = self.get_valid_bundle('null:', 'i18n-4')
744
833
 
745
834
 
746
835
    def test_whitespace_bundle(self):
758
847
        # Added
759
848
        self.tree1.commit('funky whitespace', rev_id='white-1')
760
849
 
761
 
        bundle = self.get_valid_bundle(None, 'white-1')
 
850
        bundle = self.get_valid_bundle('null:', 'white-1')
762
851
 
763
852
        # Modified
764
853
        open('b1/trailing space ', 'ab').write('add some text\n')
779
868
        bundle = self.get_valid_bundle('white-3', 'white-4')
780
869
        
781
870
        # Now test a complet roll-up
782
 
        bundle = self.get_valid_bundle(None, 'white-4')
 
871
        bundle = self.get_valid_bundle('null:', 'white-4')
783
872
 
784
873
    def test_alt_timezone_bundle(self):
785
 
        self.tree1 = self.make_branch_and_tree('b1')
 
874
        self.tree1 = self.make_branch_and_memory_tree('b1')
786
875
        self.b1 = self.tree1.branch
 
876
        builder = treebuilder.TreeBuilder()
787
877
 
788
 
        self.build_tree(['b1/newfile'])
789
 
        self.tree1.add(['newfile'])
 
878
        self.tree1.lock_write()
 
879
        builder.start_tree(self.tree1)
 
880
        builder.build(['newfile'])
 
881
        builder.finish_tree()
790
882
 
791
883
        # Asia/Colombo offset = 5 hours 30 minutes
792
884
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
793
885
                          timezone=19800, timestamp=1152544886.0)
794
886
 
795
 
        bundle = self.get_valid_bundle(None, 'tz-1')
 
887
        bundle = self.get_valid_bundle('null:', 'tz-1')
796
888
        
797
889
        rev = bundle.revisions[0]
798
890
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
799
891
        self.assertEqual(19800, rev.timezone)
800
892
        self.assertEqual(1152544886.0, rev.timestamp)
801
 
 
802
 
 
803
 
class MungedBundleTester(TestCaseWithTransport):
 
893
        self.tree1.unlock()
 
894
 
 
895
    def test_bundle_root_id(self):
 
896
        self.tree1 = self.make_branch_and_tree('b1')
 
897
        self.b1 = self.tree1.branch
 
898
        self.tree1.commit('message', rev_id='revid1')
 
899
        bundle = self.get_valid_bundle('null:', 'revid1')
 
900
        tree = self.get_bundle_tree(bundle, 'revid1')
 
901
        self.assertEqual('revid1', tree.inventory.root.revision)
 
902
 
 
903
    def test_install_revisions(self):
 
904
        self.tree1 = self.make_branch_and_tree('b1')
 
905
        self.b1 = self.tree1.branch
 
906
        self.tree1.commit('message', rev_id='rev2a')
 
907
        bundle = self.get_valid_bundle('null:', 'rev2a')
 
908
        branch2 = self.make_branch('b2')
 
909
        self.assertFalse(branch2.repository.has_revision('rev2a'))
 
910
        target_revision = bundle.install_revisions(branch2.repository)
 
911
        self.assertTrue(branch2.repository.has_revision('rev2a'))
 
912
        self.assertEqual('rev2a', target_revision)
 
913
 
 
914
    def test_bundle_empty_property(self):
 
915
        """Test serializing revision properties with an empty value."""
 
916
        tree = self.make_branch_and_memory_tree('tree')
 
917
        tree.lock_write()
 
918
        self.addCleanup(tree.unlock)
 
919
        tree.add([''], ['TREE_ROOT'])
 
920
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
921
        self.b1 = tree.branch
 
922
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
923
        bundle = read_bundle(bundle_sio)
 
924
        revision_info = bundle.revisions[0]
 
925
        self.assertEqual('rev1', revision_info.revision_id)
 
926
        rev = revision_info.as_revision()
 
927
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
928
                         rev.properties)
 
929
 
 
930
    def test_bundle_sorted_properties(self):
 
931
        """For stability the writer should write properties in sorted order."""
 
932
        tree = self.make_branch_and_memory_tree('tree')
 
933
        tree.lock_write()
 
934
        self.addCleanup(tree.unlock)
 
935
 
 
936
        tree.add([''], ['TREE_ROOT'])
 
937
        tree.commit('One', rev_id='rev1',
 
938
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
939
        self.b1 = tree.branch
 
940
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
941
        bundle = read_bundle(bundle_sio)
 
942
        revision_info = bundle.revisions[0]
 
943
        self.assertEqual('rev1', revision_info.revision_id)
 
944
        rev = revision_info.as_revision()
 
945
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
 
946
                          'd':'1'}, rev.properties)
 
947
 
 
948
    def test_bundle_unicode_properties(self):
 
949
        """We should be able to round trip a non-ascii property."""
 
950
        tree = self.make_branch_and_memory_tree('tree')
 
951
        tree.lock_write()
 
952
        self.addCleanup(tree.unlock)
 
953
 
 
954
        tree.add([''], ['TREE_ROOT'])
 
955
        # Revisions themselves do not require anything about revision property
 
956
        # keys, other than that they are a basestring, and do not contain
 
957
        # whitespace.
 
958
        # However, Testaments assert than they are str(), and thus should not
 
959
        # be Unicode.
 
960
        tree.commit('One', rev_id='rev1',
 
961
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
962
        self.b1 = tree.branch
 
963
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
964
        bundle = read_bundle(bundle_sio)
 
965
        revision_info = bundle.revisions[0]
 
966
        self.assertEqual('rev1', revision_info.revision_id)
 
967
        rev = revision_info.as_revision()
 
968
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
 
969
                          'alpha':u'\u03b1'}, rev.properties)
 
970
 
 
971
    def test_bundle_with_ghosts(self):
 
972
        tree = self.make_branch_and_tree('tree')
 
973
        self.b1 = tree.branch
 
974
        self.build_tree_contents([('tree/file', 'content1')])
 
975
        tree.add(['file'])
 
976
        tree.commit('rev1')
 
977
        self.build_tree_contents([('tree/file', 'content2')])
 
978
        tree.add_parent_tree_id('ghost')
 
979
        tree.commit('rev2', rev_id='rev2')
 
980
        bundle = self.get_valid_bundle('null:', 'rev2')
 
981
 
 
982
    def make_simple_tree(self, format=None):
 
983
        tree = self.make_branch_and_tree('b1', format=format)
 
984
        self.b1 = tree.branch
 
985
        self.build_tree(['b1/file'])
 
986
        tree.add('file')
 
987
        return tree
 
988
 
 
989
    def test_across_serializers(self):
 
990
        tree = self.make_simple_tree('knit')
 
991
        tree.commit('hello', rev_id='rev1')
 
992
        tree.commit('hello', rev_id='rev2')
 
993
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
994
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
995
        bundle.install_revisions(repo)
 
996
        inv_text = repo.get_inventory_xml('rev2')
 
997
        self.assertNotContainsRe(inv_text, 'format="5"')
 
998
        self.assertContainsRe(inv_text, 'format="7"')
 
999
 
 
1000
    def test_across_models(self):
 
1001
        tree = self.make_simple_tree('knit')
 
1002
        tree.commit('hello', rev_id='rev1')
 
1003
        tree.commit('hello', rev_id='rev2')
 
1004
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1005
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1006
        bundle.install_revisions(repo)
 
1007
        inv = repo.get_inventory('rev2')
 
1008
        self.assertEqual('rev2', inv.root.revision)
 
1009
        root_vf = repo.weave_store.get_weave(inv.root.file_id,
 
1010
                                             repo.get_transaction())
 
1011
        self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
 
1012
 
 
1013
    def test_across_models_incompatible(self):
 
1014
        tree = self.make_simple_tree('dirstate-with-subtree')
 
1015
        tree.commit('hello', rev_id='rev1')
 
1016
        tree.commit('hello', rev_id='rev2')
 
1017
        try:
 
1018
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1019
        except errors.IncompatibleBundleFormat:
 
1020
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1021
        repo = self.make_repository('repo', format='knit')
 
1022
        bundle.install_revisions(repo)
 
1023
 
 
1024
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1025
        self.assertRaises(errors.IncompatibleRevision,
 
1026
                          bundle.install_revisions, repo)
 
1027
 
 
1028
    def test_get_merge_request(self):
 
1029
        tree = self.make_simple_tree()
 
1030
        tree.commit('hello', rev_id='rev1')
 
1031
        tree.commit('hello', rev_id='rev2')
 
1032
        bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1033
        result = bundle.get_merge_request(tree.branch.repository)
 
1034
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
 
1035
 
 
1036
    def test_with_subtree(self):
 
1037
        tree = self.make_branch_and_tree('tree',
 
1038
                                         format='dirstate-with-subtree')
 
1039
        self.b1 = tree.branch
 
1040
        subtree = self.make_branch_and_tree('tree/subtree',
 
1041
                                            format='dirstate-with-subtree')
 
1042
        tree.add('subtree')
 
1043
        tree.commit('hello', rev_id='rev1')
 
1044
        try:
 
1045
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1046
        except errors.IncompatibleBundleFormat:
 
1047
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1048
        if isinstance(bundle, v09.BundleInfo09):
 
1049
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
 
1050
        repo = self.make_repository('repo', format='knit')
 
1051
        self.assertRaises(errors.IncompatibleRevision,
 
1052
                          bundle.install_revisions, repo)
 
1053
        repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
 
1054
        bundle.install_revisions(repo2)
 
1055
 
 
1056
    def test_revision_id_with_slash(self):
 
1057
        self.tree1 = self.make_branch_and_tree('tree')
 
1058
        self.b1 = self.tree1.branch
 
1059
        try:
 
1060
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
 
1061
        except ValueError:
 
1062
            raise TestSkipped("Repository doesn't support revision ids with"
 
1063
                              " slashes")
 
1064
        bundle = self.get_valid_bundle('null:', 'rev/id')
 
1065
 
 
1066
 
 
1067
class V08BundleTester(BundleTester, TestCaseWithTransport):
 
1068
 
 
1069
    format = '0.8'
 
1070
 
 
1071
    def test_bundle_empty_property(self):
 
1072
        """Test serializing revision properties with an empty value."""
 
1073
        tree = self.make_branch_and_memory_tree('tree')
 
1074
        tree.lock_write()
 
1075
        self.addCleanup(tree.unlock)
 
1076
        tree.add([''], ['TREE_ROOT'])
 
1077
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1078
        self.b1 = tree.branch
 
1079
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1080
        self.assertContainsRe(bundle_sio.getvalue(),
 
1081
                              '# properties:\n'
 
1082
                              '#   branch-nick: tree\n'
 
1083
                              '#   empty: \n'
 
1084
                              '#   one: two\n'
 
1085
                             )
 
1086
        bundle = read_bundle(bundle_sio)
 
1087
        revision_info = bundle.revisions[0]
 
1088
        self.assertEqual('rev1', revision_info.revision_id)
 
1089
        rev = revision_info.as_revision()
 
1090
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1091
                         rev.properties)
 
1092
 
 
1093
    def get_bundle_tree(self, bundle, revision_id):
 
1094
        repository = self.make_repository('repo')
 
1095
        return bundle.revision_tree(repository, 'revid1')
 
1096
 
 
1097
    def test_bundle_empty_property_alt(self):
 
1098
        """Test serializing revision properties with an empty value.
 
1099
 
 
1100
        Older readers had a bug when reading an empty property.
 
1101
        They assumed that all keys ended in ': \n'. However they would write an
 
1102
        empty value as ':\n'. This tests make sure that all newer bzr versions
 
1103
        can handle th second form.
 
1104
        """
 
1105
        tree = self.make_branch_and_memory_tree('tree')
 
1106
        tree.lock_write()
 
1107
        self.addCleanup(tree.unlock)
 
1108
        tree.add([''], ['TREE_ROOT'])
 
1109
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1110
        self.b1 = tree.branch
 
1111
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1112
        txt = bundle_sio.getvalue()
 
1113
        loc = txt.find('#   empty: ') + len('#   empty:')
 
1114
        # Create a new bundle, which strips the trailing space after empty
 
1115
        bundle_sio = StringIO(txt[:loc] + txt[loc+1:])
 
1116
 
 
1117
        self.assertContainsRe(bundle_sio.getvalue(),
 
1118
                              '# properties:\n'
 
1119
                              '#   branch-nick: tree\n'
 
1120
                              '#   empty:\n'
 
1121
                              '#   one: two\n'
 
1122
                             )
 
1123
        bundle = read_bundle(bundle_sio)
 
1124
        revision_info = bundle.revisions[0]
 
1125
        self.assertEqual('rev1', revision_info.revision_id)
 
1126
        rev = revision_info.as_revision()
 
1127
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1128
                         rev.properties)
 
1129
 
 
1130
    def test_bundle_sorted_properties(self):
 
1131
        """For stability the writer should write properties in sorted order."""
 
1132
        tree = self.make_branch_and_memory_tree('tree')
 
1133
        tree.lock_write()
 
1134
        self.addCleanup(tree.unlock)
 
1135
 
 
1136
        tree.add([''], ['TREE_ROOT'])
 
1137
        tree.commit('One', rev_id='rev1',
 
1138
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
1139
        self.b1 = tree.branch
 
1140
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1141
        self.assertContainsRe(bundle_sio.getvalue(),
 
1142
                              '# properties:\n'
 
1143
                              '#   a: 4\n'
 
1144
                              '#   b: 3\n'
 
1145
                              '#   branch-nick: tree\n'
 
1146
                              '#   c: 2\n'
 
1147
                              '#   d: 1\n'
 
1148
                             )
 
1149
        bundle = read_bundle(bundle_sio)
 
1150
        revision_info = bundle.revisions[0]
 
1151
        self.assertEqual('rev1', revision_info.revision_id)
 
1152
        rev = revision_info.as_revision()
 
1153
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
 
1154
                          'd':'1'}, rev.properties)
 
1155
 
 
1156
    def test_bundle_unicode_properties(self):
 
1157
        """We should be able to round trip a non-ascii property."""
 
1158
        tree = self.make_branch_and_memory_tree('tree')
 
1159
        tree.lock_write()
 
1160
        self.addCleanup(tree.unlock)
 
1161
 
 
1162
        tree.add([''], ['TREE_ROOT'])
 
1163
        # Revisions themselves do not require anything about revision property
 
1164
        # keys, other than that they are a basestring, and do not contain
 
1165
        # whitespace.
 
1166
        # However, Testaments assert than they are str(), and thus should not
 
1167
        # be Unicode.
 
1168
        tree.commit('One', rev_id='rev1',
 
1169
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
1170
        self.b1 = tree.branch
 
1171
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1172
        self.assertContainsRe(bundle_sio.getvalue(),
 
1173
                              '# properties:\n'
 
1174
                              '#   alpha: \xce\xb1\n'
 
1175
                              '#   branch-nick: tree\n'
 
1176
                              '#   omega: \xce\xa9\n'
 
1177
                             )
 
1178
        bundle = read_bundle(bundle_sio)
 
1179
        revision_info = bundle.revisions[0]
 
1180
        self.assertEqual('rev1', revision_info.revision_id)
 
1181
        rev = revision_info.as_revision()
 
1182
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
 
1183
                          'alpha':u'\u03b1'}, rev.properties)
 
1184
 
 
1185
 
 
1186
class V09BundleKnit2Tester(V08BundleTester):
 
1187
 
 
1188
    format = '0.9'
 
1189
 
 
1190
    def bzrdir_format(self):
 
1191
        format = bzrdir.BzrDirMetaFormat1()
 
1192
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
1193
        return format
 
1194
 
 
1195
 
 
1196
class V09BundleKnit1Tester(V08BundleTester):
 
1197
 
 
1198
    format = '0.9'
 
1199
 
 
1200
    def bzrdir_format(self):
 
1201
        format = bzrdir.BzrDirMetaFormat1()
 
1202
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
1203
        return format
 
1204
 
 
1205
 
 
1206
class V4BundleTester(BundleTester, TestCaseWithTransport):
 
1207
 
 
1208
    format = '4'
 
1209
 
 
1210
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
 
1211
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1212
        Make sure that the text generated is valid, and that it
 
1213
        can be applied against the base, and generate the same information.
 
1214
        
 
1215
        :return: The in-memory bundle 
 
1216
        """
 
1217
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1218
 
 
1219
        # This should also validate the generated bundle 
 
1220
        bundle = read_bundle(bundle_txt)
 
1221
        repository = self.b1.repository
 
1222
        for bundle_rev in bundle.real_revisions:
 
1223
            # These really should have already been checked when we read the
 
1224
            # bundle, since it computes the sha1 hash for the revision, which
 
1225
            # only will match if everything is okay, but lets be explicit about
 
1226
            # it
 
1227
            branch_rev = repository.get_revision(bundle_rev.revision_id)
 
1228
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
 
1229
                      'timestamp', 'timezone', 'message', 'committer', 
 
1230
                      'parent_ids', 'properties'):
 
1231
                self.assertEqual(getattr(branch_rev, a), 
 
1232
                                 getattr(bundle_rev, a))
 
1233
            self.assertEqual(len(branch_rev.parent_ids), 
 
1234
                             len(bundle_rev.parent_ids))
 
1235
        self.assertEqual(set(rev_ids),
 
1236
                         set([r.revision_id for r in bundle.real_revisions]))
 
1237
        self.valid_apply_bundle(base_rev_id, bundle,
 
1238
                                   checkout_dir=checkout_dir)
 
1239
 
 
1240
        return bundle
 
1241
 
 
1242
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
1243
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1244
        Munge the text so that it's invalid.
 
1245
 
 
1246
        :return: The in-memory bundle
 
1247
        """
 
1248
        from bzrlib.bundle import serializer
 
1249
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1250
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1251
        new_text = new_text.replace('<file file_id="exe-1"',
 
1252
                                    '<file executable="y" file_id="exe-1"')
 
1253
        new_text = new_text.replace('B372', 'B387')
 
1254
        bundle_txt = StringIO()
 
1255
        bundle_txt.write(serializer._get_bundle_header('4'))
 
1256
        bundle_txt.write('\n')
 
1257
        bundle_txt.write(new_text.encode('bz2'))
 
1258
        bundle_txt.seek(0)
 
1259
        bundle = read_bundle(bundle_txt)
 
1260
        self.valid_apply_bundle(base_rev_id, bundle)
 
1261
        return bundle
 
1262
 
 
1263
    def create_bundle_text(self, base_rev_id, rev_id):
 
1264
        bundle_txt = StringIO()
 
1265
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
1266
                               bundle_txt, format=self.format)
 
1267
        bundle_txt.seek(0)
 
1268
        self.assertEqual(bundle_txt.readline(), 
 
1269
                         '# Bazaar revision bundle v%s\n' % self.format)
 
1270
        self.assertEqual(bundle_txt.readline(), '#\n')
 
1271
        rev = self.b1.repository.get_revision(rev_id)
 
1272
        bundle_txt.seek(0)
 
1273
        return bundle_txt, rev_ids
 
1274
 
 
1275
    def get_bundle_tree(self, bundle, revision_id):
 
1276
        repository = self.make_repository('repo')
 
1277
        bundle.install_revisions(repository)
 
1278
        return repository.revision_tree(revision_id)
 
1279
 
 
1280
    def test_creation(self):
 
1281
        tree = self.make_branch_and_tree('tree')
 
1282
        self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
 
1283
        tree.add('file', 'fileid-2')
 
1284
        tree.commit('added file', rev_id='rev1')
 
1285
        self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
 
1286
        tree.commit('changed file', rev_id='rev2')
 
1287
        s = StringIO()
 
1288
        serializer = BundleSerializerV4('1.0')
 
1289
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
 
1290
        s.seek(0)
 
1291
        tree2 = self.make_branch_and_tree('target')
 
1292
        target_repo = tree2.branch.repository
 
1293
        install_bundle(target_repo, serializer.read(s))
 
1294
        vf = target_repo.weave_store.get_weave('fileid-2',
 
1295
            target_repo.get_transaction())
 
1296
        self.assertEqual('contents1\nstatic\n', vf.get_text('rev1'))
 
1297
        self.assertEqual('contents2\nstatic\n', vf.get_text('rev2'))
 
1298
        rtree = target_repo.revision_tree('rev2')
 
1299
        inventory_vf = target_repo.get_inventory_weave()
 
1300
        self.assertEqual(['rev1'], inventory_vf.get_parents('rev2'))
 
1301
        self.assertEqual('changed file',
 
1302
                         target_repo.get_revision('rev2').message)
 
1303
 
 
1304
    @staticmethod
 
1305
    def get_raw(bundle_file):
 
1306
        bundle_file.seek(0)
 
1307
        line = bundle_file.readline()
 
1308
        line = bundle_file.readline()
 
1309
        lines = bundle_file.readlines()
 
1310
        return ''.join(lines).decode('bz2')
 
1311
 
 
1312
    def test_copy_signatures(self):
 
1313
        tree_a = self.make_branch_and_tree('tree_a')
 
1314
        import bzrlib.gpg
 
1315
        import bzrlib.commit as commit
 
1316
        oldstrategy = bzrlib.gpg.GPGStrategy
 
1317
        branch = tree_a.branch
 
1318
        repo_a = branch.repository
 
1319
        tree_a.commit("base", allow_pointless=True, rev_id='A')
 
1320
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1321
        try:
 
1322
            from bzrlib.testament import Testament
 
1323
            # monkey patch gpg signing mechanism
 
1324
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
 
1325
            new_config = test_commit.MustSignConfig(branch)
 
1326
            commit.Commit(config=new_config).commit(message="base",
 
1327
                                                    allow_pointless=True,
 
1328
                                                    rev_id='B',
 
1329
                                                    working_tree=tree_a)
 
1330
            def sign(text):
 
1331
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
 
1332
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
 
1333
        finally:
 
1334
            bzrlib.gpg.GPGStrategy = oldstrategy
 
1335
        tree_b = self.make_branch_and_tree('tree_b')
 
1336
        repo_b = tree_b.branch.repository
 
1337
        s = StringIO()
 
1338
        serializer = BundleSerializerV4('4')
 
1339
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
 
1340
        s.seek(0)
 
1341
        install_bundle(repo_b, serializer.read(s))
 
1342
        self.assertTrue(repo_b.has_signature_for_revision_id('B'))
 
1343
        self.assertEqual(repo_b.get_signature_text('B'),
 
1344
                         repo_a.get_signature_text('B'))
 
1345
        s.seek(0)
 
1346
        # ensure repeat installs are harmless
 
1347
        install_bundle(repo_b, serializer.read(s))
 
1348
 
 
1349
 
 
1350
class V4WeaveBundleTester(V4BundleTester):
 
1351
 
 
1352
    def bzrdir_format(self):
 
1353
        return 'metaweave'
 
1354
 
 
1355
 
 
1356
class MungedBundleTester(object):
804
1357
 
805
1358
    def build_test_bundle(self):
806
1359
        wt = self.make_branch_and_tree('b1')
815
1368
 
816
1369
        bundle_txt = StringIO()
817
1370
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
818
 
                               'a@cset-0-1', bundle_txt)
819
 
        self.assertEqual(['a@cset-0-2'], rev_ids)
 
1371
                               'a@cset-0-1', bundle_txt, self.format)
 
1372
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
820
1373
        bundle_txt.seek(0, 0)
821
1374
        return bundle_txt
822
1375
 
851
1404
        bundle = read_bundle(bundle_txt)
852
1405
        self.check_valid(bundle)
853
1406
 
 
1407
 
 
1408
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
 
1409
 
 
1410
    format = '0.9'
 
1411
 
854
1412
    def test_missing_trailing_whitespace(self):
855
1413
        bundle_txt = self.build_test_bundle()
856
1414
 
884
1442
        bundle = read_bundle(bundle_txt)
885
1443
        self.check_valid(bundle)
886
1444
 
 
1445
 
 
1446
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
 
1447
 
 
1448
    format = '4'
 
1449
 
 
1450
 
 
1451
class TestBundleWriterReader(TestCase):
 
1452
 
 
1453
    def test_roundtrip_record(self):
 
1454
        fileobj = StringIO()
 
1455
        writer = v4.BundleWriter(fileobj)
 
1456
        writer.begin()
 
1457
        writer.add_info_record(foo='bar')
 
1458
        writer._add_record("Record body", {'parents': ['1', '3'],
 
1459
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1460
        writer.end()
 
1461
        fileobj.seek(0)
 
1462
        record_iter = v4.BundleReader(fileobj).iter_records()
 
1463
        record = record_iter.next()
 
1464
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1465
            'info', None, None), record)
 
1466
        record = record_iter.next()
 
1467
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
 
1468
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
 
1469
                          record)
 
1470
 
 
1471
    def test_encode_name(self):
 
1472
        self.assertEqual('revision/rev1',
 
1473
            v4.BundleWriter.encode_name('revision', 'rev1'))
 
1474
        self.assertEqual('file/rev//1/file-id-1',
 
1475
            v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
 
1476
        self.assertEqual('info',
 
1477
            v4.BundleWriter.encode_name('info', None, None))
 
1478
 
 
1479
    def test_decode_name(self):
 
1480
        self.assertEqual(('revision', 'rev1', None),
 
1481
            v4.BundleReader.decode_name('revision/rev1'))
 
1482
        self.assertEqual(('file', 'rev/1', 'file-id-1'),
 
1483
            v4.BundleReader.decode_name('file/rev//1/file-id-1'))
 
1484
        self.assertEqual(('info', None, None),
 
1485
                         v4.BundleReader.decode_name('info'))
 
1486
 
 
1487
    def test_too_many_names(self):
 
1488
        fileobj = StringIO()
 
1489
        writer = v4.BundleWriter(fileobj)
 
1490
        writer.begin()
 
1491
        writer.add_info_record(foo='bar')
 
1492
        writer._container.add_bytes_record('blah', ['two', 'names'])
 
1493
        writer.end()
 
1494
        fileobj.seek(0)
 
1495
        record_iter = v4.BundleReader(fileobj).iter_records()
 
1496
        record = record_iter.next()
 
1497
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1498
            'info', None, None), record)
 
1499
        self.assertRaises(BadBundle, record_iter.next)