/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

First attempt to merge .dev and resolve the conflicts (but tests are 
failing)

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
 
31
from bzrlib.bundle import read_mergeable_from_url
25
32
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
26
33
from bzrlib.bundle.bundle_data import BundleTree
27
 
from bzrlib.bundle.serializer import write_bundle, read_bundle
 
34
from bzrlib.directory_service import directories
 
35
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
 
36
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
 
37
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
 
38
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
28
39
from bzrlib.branch import Branch
29
40
from bzrlib.diff import internal_diff
30
41
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
31
42
                           NoSuchFile,)
32
43
from bzrlib.merge import Merge3Merger
33
 
from bzrlib.osutils import has_symlinks, sha_file
34
 
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
35
 
                          TestCase, TestSkipped)
 
44
from bzrlib.repofmt import knitrepo
 
45
from bzrlib.osutils import sha_file, sha_string
 
46
from bzrlib.tests import (
 
47
    SymlinkFeature,
 
48
    TestCase,
 
49
    TestCaseInTempDir,
 
50
    TestCaseWithTransport,
 
51
    TestSkipped,
 
52
    test_read_bundle,
 
53
    test_commit,
 
54
    )
36
55
from bzrlib.transform import TreeTransform
37
 
from bzrlib.workingtree import WorkingTree
38
56
 
39
57
 
40
58
class MockTree(object):
151
169
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
152
170
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
153
171
 
154
 
        assert btree.path2id("grandparent2") is None
155
 
        assert btree.path2id("grandparent2/parent") is None
156
 
        assert btree.path2id("grandparent2/parent/file") is None
 
172
        self.assertTrue(btree.path2id("grandparent2") is None)
 
173
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
174
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
157
175
 
158
176
        btree.note_rename("grandparent", "grandparent2")
159
 
        assert btree.old_path("grandparent") is None
160
 
        assert btree.old_path("grandparent/parent") is None
161
 
        assert btree.old_path("grandparent/parent/file") is None
 
177
        self.assertTrue(btree.old_path("grandparent") is None)
 
178
        self.assertTrue(btree.old_path("grandparent/parent") is None)
 
179
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
162
180
 
163
181
        self.assertEqual(btree.id2path("a"), "grandparent2")
164
182
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
168
186
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
169
187
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
170
188
 
171
 
        assert btree.path2id("grandparent") is None
172
 
        assert btree.path2id("grandparent/parent") is None
173
 
        assert btree.path2id("grandparent/parent/file") is None
 
189
        self.assertTrue(btree.path2id("grandparent") is None)
 
190
        self.assertTrue(btree.path2id("grandparent/parent") is None)
 
191
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
174
192
 
175
193
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
176
194
        self.assertEqual(btree.id2path("a"), "grandparent2")
181
199
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
182
200
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
183
201
 
184
 
        assert btree.path2id("grandparent2/parent") is None
185
 
        assert btree.path2id("grandparent2/parent/file") is None
 
202
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
203
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
186
204
 
187
205
        btree.note_rename("grandparent/parent/file", 
188
206
                          "grandparent2/parent2/file2")
194
212
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
195
213
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
196
214
 
197
 
        assert btree.path2id("grandparent2/parent2/file") is None
 
215
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
198
216
 
199
217
    def test_moves(self):
200
218
        """Ensure that file moves have the proper effect on children"""
203
221
                          "grandparent/alt_parent/file")
204
222
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
205
223
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
206
 
        assert btree.path2id("grandparent/parent/file") is None
 
224
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
207
225
 
208
226
    def unified_diff(self, old, new):
209
227
        out = StringIO()
215
233
        btree = self.make_tree_1()[0]
216
234
        btree.note_rename("grandparent/parent/file", 
217
235
                          "grandparent/alt_parent/file")
218
 
        assert btree.id2path("e") is None
219
 
        assert btree.path2id("grandparent/parent/file") is None
 
236
        self.assertTrue(btree.id2path("e") is None)
 
237
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
220
238
        btree.note_id("e", "grandparent/parent/file")
221
239
        return btree
222
240
 
280
298
        btree = self.make_tree_1()[0]
281
299
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
282
300
        btree.note_deletion("grandparent/parent/file")
283
 
        assert btree.id2path("c") is None
284
 
        assert btree.path2id("grandparent/parent/file") is None
 
301
        self.assertTrue(btree.id2path("c") is None)
 
302
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
285
303
 
286
304
    def sorted_ids(self, tree):
287
305
        ids = list(tree)
301
319
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
302
320
 
303
321
 
304
 
class BundleTester(TestCaseWithTransport):
 
322
class BundleTester1(TestCaseWithTransport):
 
323
 
 
324
    def test_mismatched_bundle(self):
 
325
        format = bzrdir.BzrDirMetaFormat1()
 
326
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
327
        serializer = BundleSerializerV08('0.8')
 
328
        b = self.make_branch('.', format=format)
 
329
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
 
330
                          b.repository, [], {}, StringIO())
 
331
 
 
332
    def test_matched_bundle(self):
 
333
        """Don't raise IncompatibleBundleFormat for knit2 and bundle0.9"""
 
334
        format = bzrdir.BzrDirMetaFormat1()
 
335
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
336
        serializer = BundleSerializerV09('0.9')
 
337
        b = self.make_branch('.', format=format)
 
338
        serializer.write(b.repository, [], {}, StringIO())
 
339
 
 
340
    def test_mismatched_model(self):
 
341
        """Try copying a bundle from knit2 to knit1"""
 
342
        format = bzrdir.BzrDirMetaFormat1()
 
343
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
344
        source = self.make_branch_and_tree('source', format=format)
 
345
        source.commit('one', rev_id='one-id')
 
346
        source.commit('two', rev_id='two-id')
 
347
        text = StringIO()
 
348
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
 
349
                     format='0.9')
 
350
        text.seek(0)
 
351
 
 
352
        format = bzrdir.BzrDirMetaFormat1()
 
353
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
354
        target = self.make_branch('target', format=format)
 
355
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
356
                          target.repository, read_bundle(text))
 
357
 
 
358
 
 
359
class BundleTester(object):
 
360
 
 
361
    def bzrdir_format(self):
 
362
        format = bzrdir.BzrDirMetaFormat1()
 
363
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
364
        return format
 
365
 
 
366
    def make_branch_and_tree(self, path, format=None):
 
367
        if format is None:
 
368
            format = self.bzrdir_format()
 
369
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
370
 
 
371
    def make_branch(self, path, format=None):
 
372
        if format is None:
 
373
            format = self.bzrdir_format()
 
374
        return TestCaseWithTransport.make_branch(self, path, format)
305
375
 
306
376
    def create_bundle_text(self, base_rev_id, rev_id):
307
377
        bundle_txt = StringIO()
308
378
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
309
 
                               bundle_txt)
 
379
                               bundle_txt, format=self.format)
310
380
        bundle_txt.seek(0)
311
381
        self.assertEqual(bundle_txt.readline(), 
312
 
                         '# Bazaar revision bundle v0.8\n')
 
382
                         '# Bazaar revision bundle v%s\n' % self.format)
313
383
        self.assertEqual(bundle_txt.readline(), '#\n')
314
384
 
315
385
        rev = self.b1.repository.get_revision(rev_id)
316
386
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
317
387
                         u'# message:\n')
318
 
 
319
 
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
320
388
        bundle_txt.seek(0)
321
389
        return bundle_txt, rev_ids
322
390
 
391
459
        else:
392
460
            if not os.path.exists(checkout_dir):
393
461
                os.mkdir(checkout_dir)
394
 
        tree = BzrDir.create_standalone_workingtree(checkout_dir)
 
462
        tree = self.make_branch_and_tree(checkout_dir)
395
463
        s = StringIO()
396
 
        ancestors = write_bundle(self.b1.repository, rev_id, None, s)
 
464
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
 
465
                                 format=self.format)
397
466
        s.seek(0)
398
 
        assert isinstance(s.getvalue(), str), (
399
 
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
 
467
        self.assertIsInstance(s.getvalue(), str)
400
468
        install_bundle(tree.branch.repository, read_bundle(s))
401
469
        for ancestor in ancestors:
402
470
            old = self.b1.repository.revision_tree(ancestor)
403
471
            new = tree.branch.repository.revision_tree(ancestor)
404
 
 
405
 
            # Check that there aren't any inventory level changes
406
 
            delta = new.changes_from(old)
407
 
            self.assertFalse(delta.has_changed(),
408
 
                             'Revision %s not copied correctly.'
409
 
                             % (ancestor,))
410
 
 
411
 
            # Now check that the file contents are all correct
412
 
            for inventory_id in old:
413
 
                try:
414
 
                    old_file = old.get_file(inventory_id)
415
 
                except NoSuchFile:
416
 
                    continue
417
 
                if old_file is None:
418
 
                    continue
419
 
                self.assertEqual(old_file.read(),
420
 
                                 new.get_file(inventory_id).read())
421
 
        if rev_id is not None:
 
472
            old.lock_read()
 
473
            new.lock_read()
 
474
            try:
 
475
                # Check that there aren't any inventory level changes
 
476
                delta = new.changes_from(old)
 
477
                self.assertFalse(delta.has_changed(),
 
478
                                 'Revision %s not copied correctly.'
 
479
                                 % (ancestor,))
 
480
 
 
481
                # Now check that the file contents are all correct
 
482
                for inventory_id in old:
 
483
                    try:
 
484
                        old_file = old.get_file(inventory_id)
 
485
                    except NoSuchFile:
 
486
                        continue
 
487
                    if old_file is None:
 
488
                        continue
 
489
                    self.assertEqual(old_file.read(),
 
490
                                     new.get_file(inventory_id).read())
 
491
            finally:
 
492
                new.unlock()
 
493
                old.unlock()
 
494
        if not _mod_revision.is_null(rev_id):
422
495
            rh = self.b1.revision_history()
423
496
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
424
497
            tree.update()
425
498
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
426
499
            self.assertFalse(delta.has_changed(),
427
 
                             'Working tree has modifications')
 
500
                             'Working tree has modifications: %s' % delta)
428
501
        return tree
429
502
 
430
503
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
432
505
        sure everything matches the builtin branch.
433
506
        """
434
507
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
 
508
        to_tree.lock_write()
 
509
        try:
 
510
            self._valid_apply_bundle(base_rev_id, info, to_tree)
 
511
        finally:
 
512
            to_tree.unlock()
 
513
 
 
514
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
435
515
        original_parents = to_tree.get_parent_ids()
436
516
        repository = to_tree.branch.repository
437
517
        original_parents = to_tree.get_parent_ids()
485
565
        self.tree1.add('one')
486
566
        self.tree1.commit('add one', rev_id='a@cset-0-1')
487
567
 
488
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
489
 
        # FIXME: The current write_bundle api no longer supports
490
 
        #        setting a custom summary message
491
 
        #        We should re-introduce the ability, and update
492
 
        #        the tests to make sure it works.
493
 
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
494
 
        #         message='With a specialized message')
 
568
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
495
569
 
496
570
        # Make sure we can handle files with spaces, tabs, other
497
571
        # bogus characters
510
584
        tt = TreeTransform(self.tree1)
511
585
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
512
586
        tt.apply()
 
587
        # have to fix length of file-id so that we can predictably rewrite
 
588
        # a (length-prefixed) record containing it later.
 
589
        self.tree1.add('with space.txt', 'withspace-id')
513
590
        self.tree1.add([
514
 
                'with space.txt'
515
 
                , 'dir'
 
591
                  'dir'
516
592
                , 'dir/filein subdir.c'
517
593
                , 'dir/WithCaps.txt'
518
594
                , 'dir/ pre space'
527
603
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
528
604
 
529
605
        # Check a rollup bundle 
530
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
 
606
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
531
607
 
532
608
        # Now delete entries
533
609
        self.tree1.remove(
542
618
        self.tree1.commit('removed', rev_id='a@cset-0-3')
543
619
        
544
620
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
545
 
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
546
 
                          'a@cset-0-2', 'a@cset-0-3')
 
621
        self.assertRaises((TestamentMismatch,
 
622
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
623
            'a@cset-0-2', 'a@cset-0-3')
547
624
        # Check a rollup bundle 
548
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
 
625
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
549
626
 
550
627
        # Now move the directory
551
628
        self.tree1.rename_one('dir', 'sub/dir')
553
630
 
554
631
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
555
632
        # Check a rollup bundle 
556
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
 
633
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
557
634
 
558
635
        # Modified files
559
636
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
560
 
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
 
637
        open('b1/sub/dir/ pre space', 'ab').write(
 
638
             '\r\nAdding some\r\nDOS format lines\r\n')
561
639
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
562
640
        self.tree1.rename_one('sub/dir/ pre space', 
563
641
                              'sub/ start space')
571
649
                          verbose=False)
572
650
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
573
651
        other = self.get_checkout('a@cset-0-5')
 
652
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
 
653
            'a@cset-0-5')
 
654
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
655
        self.assertEqualDiff(tree1_inv, tree2_inv)
574
656
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
575
657
        other.commit('rename file', rev_id='a@cset-0-6b')
576
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
 
658
        self.tree1.merge_from_branch(other.branch)
577
659
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
578
660
                          verbose=False)
579
661
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
580
662
 
581
663
    def test_symlink_bundle(self):
582
 
        if not has_symlinks():
583
 
            raise TestSkipped("No symlink support")
584
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
664
        self.requireFeature(SymlinkFeature)
 
665
        self.tree1 = self.make_branch_and_tree('b1')
585
666
        self.b1 = self.tree1.branch
586
667
        tt = TreeTransform(self.tree1)
587
668
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
588
669
        tt.apply()
589
670
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
590
 
        self.get_valid_bundle(None, 'l@cset-0-1')
 
671
        self.get_valid_bundle('null:', 'l@cset-0-1')
591
672
        tt = TreeTransform(self.tree1)
592
673
        trans_id = tt.trans_id_tree_file_id('link-1')
593
674
        tt.adjust_path('link2', tt.root, trans_id)
611
692
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
612
693
 
613
694
    def test_binary_bundle(self):
614
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
695
        self.tree1 = self.make_branch_and_tree('b1')
615
696
        self.b1 = self.tree1.branch
616
697
        tt = TreeTransform(self.tree1)
617
698
        
618
699
        # Add
619
700
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
620
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
 
701
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
 
702
            'binary-2')
621
703
        tt.apply()
622
704
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
623
 
        self.get_valid_bundle(None, 'b@cset-0-1')
 
705
        self.get_valid_bundle('null:', 'b@cset-0-1')
624
706
 
625
707
        # Delete
626
708
        tt = TreeTransform(self.tree1)
650
732
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
651
733
 
652
734
        # Rollup
653
 
        self.get_valid_bundle(None, 'b@cset-0-4')
 
735
        self.get_valid_bundle('null:', 'b@cset-0-4')
654
736
 
655
737
    def test_last_modified(self):
656
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
738
        self.tree1 = self.make_branch_and_tree('b1')
657
739
        self.b1 = self.tree1.branch
658
740
        tt = TreeTransform(self.tree1)
659
741
        tt.new_file('file', tt.root, 'file', 'file')
674
756
        tt.create_file('file2', trans_id)
675
757
        tt.apply()
676
758
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
677
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
 
759
        self.tree1.merge_from_branch(other.branch)
678
760
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
679
761
                          verbose=False)
680
762
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
681
763
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
682
764
 
683
765
    def test_hide_history(self):
684
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
766
        self.tree1 = self.make_branch_and_tree('b1')
685
767
        self.b1 = self.tree1.branch
686
768
 
687
769
        open('b1/one', 'wb').write('one\n')
693
775
        self.tree1.commit('modify', rev_id='a@cset-0-3')
694
776
        bundle_file = StringIO()
695
777
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
 
778
                               'a@cset-0-1', bundle_file, format=self.format)
 
779
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
 
780
        self.assertContainsRe(self.get_raw(bundle_file), 'one')
 
781
        self.assertContainsRe(self.get_raw(bundle_file), 'three')
 
782
 
 
783
    def test_bundle_same_basis(self):
 
784
        """Ensure using the basis as the target doesn't cause an error"""
 
785
        self.tree1 = self.make_branch_and_tree('b1')
 
786
        self.tree1.commit('add file', rev_id='a@cset-0-1')
 
787
        bundle_file = StringIO()
 
788
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-1',
696
789
                               'a@cset-0-1', bundle_file)
697
 
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
698
 
        self.assertContainsRe(bundle_file.getvalue(), 'one')
699
 
        self.assertContainsRe(bundle_file.getvalue(), 'three')
 
790
 
 
791
    @staticmethod
 
792
    def get_raw(bundle_file):
 
793
        return bundle_file.getvalue()
700
794
 
701
795
    def test_unicode_bundle(self):
702
796
        # Handle international characters
714
808
            u'William Dod\xe9\n').encode('utf-8'))
715
809
        f.close()
716
810
 
717
 
        self.tree1.add([u'with Dod\xe9'])
718
 
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
 
811
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
 
812
        self.tree1.commit(u'i18n commit from William Dod\xe9',
719
813
                          rev_id='i18n-1', committer=u'William Dod\xe9')
720
814
 
 
815
        if sys.platform == 'darwin':
 
816
            from bzrlib.workingtree import WorkingTree3
 
817
            if type(self.tree1) is WorkingTree3:
 
818
                self.knownFailure("Bug #141438: fails for WorkingTree3 on OSX")
 
819
 
 
820
            # On Mac the '\xe9' gets changed to 'e\u0301'
 
821
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
 
822
                             sorted(os.listdir(u'b1')))
 
823
            delta = self.tree1.changes_from(self.tree1.basis_tree())
 
824
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
 
825
                             delta.removed)
 
826
            self.knownFailure("Mac OSX doesn't preserve unicode"
 
827
                              " combining characters.")
 
828
 
721
829
        # Add
722
 
        bundle = self.get_valid_bundle(None, 'i18n-1')
 
830
        bundle = self.get_valid_bundle('null:', 'i18n-1')
723
831
 
724
832
        # Modified
725
833
        f = open(u'b1/with Dod\xe9', 'wb')
743
851
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
744
852
 
745
853
        # Rollup
746
 
        bundle = self.get_valid_bundle(None, 'i18n-4')
 
854
        bundle = self.get_valid_bundle('null:', 'i18n-4')
747
855
 
748
856
 
749
857
    def test_whitespace_bundle(self):
761
869
        # Added
762
870
        self.tree1.commit('funky whitespace', rev_id='white-1')
763
871
 
764
 
        bundle = self.get_valid_bundle(None, 'white-1')
 
872
        bundle = self.get_valid_bundle('null:', 'white-1')
765
873
 
766
874
        # Modified
767
875
        open('b1/trailing space ', 'ab').write('add some text\n')
782
890
        bundle = self.get_valid_bundle('white-3', 'white-4')
783
891
        
784
892
        # Now test a complet roll-up
785
 
        bundle = self.get_valid_bundle(None, 'white-4')
 
893
        bundle = self.get_valid_bundle('null:', 'white-4')
786
894
 
787
895
    def test_alt_timezone_bundle(self):
788
 
        self.tree1 = self.make_branch_and_tree('b1')
 
896
        self.tree1 = self.make_branch_and_memory_tree('b1')
789
897
        self.b1 = self.tree1.branch
 
898
        builder = treebuilder.TreeBuilder()
790
899
 
791
 
        self.build_tree(['b1/newfile'])
792
 
        self.tree1.add(['newfile'])
 
900
        self.tree1.lock_write()
 
901
        builder.start_tree(self.tree1)
 
902
        builder.build(['newfile'])
 
903
        builder.finish_tree()
793
904
 
794
905
        # Asia/Colombo offset = 5 hours 30 minutes
795
906
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
796
907
                          timezone=19800, timestamp=1152544886.0)
797
908
 
798
 
        bundle = self.get_valid_bundle(None, 'tz-1')
 
909
        bundle = self.get_valid_bundle('null:', 'tz-1')
799
910
        
800
911
        rev = bundle.revisions[0]
801
912
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
802
913
        self.assertEqual(19800, rev.timezone)
803
914
        self.assertEqual(1152544886.0, rev.timestamp)
 
915
        self.tree1.unlock()
804
916
 
805
917
    def test_bundle_root_id(self):
806
918
        self.tree1 = self.make_branch_and_tree('b1')
807
919
        self.b1 = self.tree1.branch
808
920
        self.tree1.commit('message', rev_id='revid1')
809
 
        bundle = self.get_valid_bundle(None, 'revid1')
810
 
        tree = bundle.revision_tree(self.b1.repository, 'revid1')
 
921
        bundle = self.get_valid_bundle('null:', 'revid1')
 
922
        tree = self.get_bundle_tree(bundle, 'revid1')
811
923
        self.assertEqual('revid1', tree.inventory.root.revision)
812
924
 
813
 
 
814
 
class MungedBundleTester(TestCaseWithTransport):
 
925
    def test_install_revisions(self):
 
926
        self.tree1 = self.make_branch_and_tree('b1')
 
927
        self.b1 = self.tree1.branch
 
928
        self.tree1.commit('message', rev_id='rev2a')
 
929
        bundle = self.get_valid_bundle('null:', 'rev2a')
 
930
        branch2 = self.make_branch('b2')
 
931
        self.assertFalse(branch2.repository.has_revision('rev2a'))
 
932
        target_revision = bundle.install_revisions(branch2.repository)
 
933
        self.assertTrue(branch2.repository.has_revision('rev2a'))
 
934
        self.assertEqual('rev2a', target_revision)
 
935
 
 
936
    def test_bundle_empty_property(self):
 
937
        """Test serializing revision properties with an empty value."""
 
938
        tree = self.make_branch_and_memory_tree('tree')
 
939
        tree.lock_write()
 
940
        self.addCleanup(tree.unlock)
 
941
        tree.add([''], ['TREE_ROOT'])
 
942
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
943
        self.b1 = tree.branch
 
944
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
945
        bundle = read_bundle(bundle_sio)
 
946
        revision_info = bundle.revisions[0]
 
947
        self.assertEqual('rev1', revision_info.revision_id)
 
948
        rev = revision_info.as_revision()
 
949
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
950
                         rev.properties)
 
951
 
 
952
    def test_bundle_sorted_properties(self):
 
953
        """For stability the writer should write properties in sorted order."""
 
954
        tree = self.make_branch_and_memory_tree('tree')
 
955
        tree.lock_write()
 
956
        self.addCleanup(tree.unlock)
 
957
 
 
958
        tree.add([''], ['TREE_ROOT'])
 
959
        tree.commit('One', rev_id='rev1',
 
960
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
961
        self.b1 = tree.branch
 
962
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
963
        bundle = read_bundle(bundle_sio)
 
964
        revision_info = bundle.revisions[0]
 
965
        self.assertEqual('rev1', revision_info.revision_id)
 
966
        rev = revision_info.as_revision()
 
967
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
 
968
                          'd':'1'}, rev.properties)
 
969
 
 
970
    def test_bundle_unicode_properties(self):
 
971
        """We should be able to round trip a non-ascii property."""
 
972
        tree = self.make_branch_and_memory_tree('tree')
 
973
        tree.lock_write()
 
974
        self.addCleanup(tree.unlock)
 
975
 
 
976
        tree.add([''], ['TREE_ROOT'])
 
977
        # Revisions themselves do not require anything about revision property
 
978
        # keys, other than that they are a basestring, and do not contain
 
979
        # whitespace.
 
980
        # However, Testaments assert than they are str(), and thus should not
 
981
        # be Unicode.
 
982
        tree.commit('One', rev_id='rev1',
 
983
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
984
        self.b1 = tree.branch
 
985
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
986
        bundle = read_bundle(bundle_sio)
 
987
        revision_info = bundle.revisions[0]
 
988
        self.assertEqual('rev1', revision_info.revision_id)
 
989
        rev = revision_info.as_revision()
 
990
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
 
991
                          'alpha':u'\u03b1'}, rev.properties)
 
992
 
 
993
    def test_bundle_with_ghosts(self):
 
994
        tree = self.make_branch_and_tree('tree')
 
995
        self.b1 = tree.branch
 
996
        self.build_tree_contents([('tree/file', 'content1')])
 
997
        tree.add(['file'])
 
998
        tree.commit('rev1')
 
999
        self.build_tree_contents([('tree/file', 'content2')])
 
1000
        tree.add_parent_tree_id('ghost')
 
1001
        tree.commit('rev2', rev_id='rev2')
 
1002
        bundle = self.get_valid_bundle('null:', 'rev2')
 
1003
 
 
1004
    def make_simple_tree(self, format=None):
 
1005
        tree = self.make_branch_and_tree('b1', format=format)
 
1006
        self.b1 = tree.branch
 
1007
        self.build_tree(['b1/file'])
 
1008
        tree.add('file')
 
1009
        return tree
 
1010
 
 
1011
    def test_across_serializers(self):
 
1012
        tree = self.make_simple_tree('knit')
 
1013
        tree.commit('hello', rev_id='rev1')
 
1014
        tree.commit('hello', rev_id='rev2')
 
1015
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1016
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1017
        bundle.install_revisions(repo)
 
1018
        inv_text = repo.get_inventory_xml('rev2')
 
1019
        self.assertNotContainsRe(inv_text, 'format="5"')
 
1020
        self.assertContainsRe(inv_text, 'format="7"')
 
1021
 
 
1022
    def make_repo_with_installed_revisions(self):
 
1023
        tree = self.make_simple_tree('knit')
 
1024
        tree.commit('hello', rev_id='rev1')
 
1025
        tree.commit('hello', rev_id='rev2')
 
1026
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1027
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1028
        bundle.install_revisions(repo)
 
1029
        return repo
 
1030
 
 
1031
    def test_across_models(self):
 
1032
        repo = self.make_repo_with_installed_revisions()
 
1033
        inv = repo.get_inventory('rev2')
 
1034
        self.assertEqual('rev2', inv.root.revision)
 
1035
        root_id = inv.root.file_id
 
1036
        repo.lock_read()
 
1037
        self.addCleanup(repo.unlock)
 
1038
        self.assertEqual({(root_id, 'rev1'):(),
 
1039
            (root_id, 'rev2'):((root_id, 'rev1'),)},
 
1040
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
 
1041
 
 
1042
    def test_inv_hash_across_serializers(self):
 
1043
        repo = self.make_repo_with_installed_revisions()
 
1044
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
 
1045
        xml = repo.get_inventory_xml('rev2')
 
1046
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
 
1047
 
 
1048
    def test_across_models_incompatible(self):
 
1049
        tree = self.make_simple_tree('dirstate-with-subtree')
 
1050
        tree.commit('hello', rev_id='rev1')
 
1051
        tree.commit('hello', rev_id='rev2')
 
1052
        try:
 
1053
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1054
        except errors.IncompatibleBundleFormat:
 
1055
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1056
        repo = self.make_repository('repo', format='knit')
 
1057
        bundle.install_revisions(repo)
 
1058
 
 
1059
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1060
        self.assertRaises(errors.IncompatibleRevision,
 
1061
                          bundle.install_revisions, repo)
 
1062
 
 
1063
    def test_get_merge_request(self):
 
1064
        tree = self.make_simple_tree()
 
1065
        tree.commit('hello', rev_id='rev1')
 
1066
        tree.commit('hello', rev_id='rev2')
 
1067
        bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1068
        result = bundle.get_merge_request(tree.branch.repository)
 
1069
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
 
1070
 
 
1071
    def test_with_subtree(self):
 
1072
        tree = self.make_branch_and_tree('tree',
 
1073
                                         format='dirstate-with-subtree')
 
1074
        self.b1 = tree.branch
 
1075
        subtree = self.make_branch_and_tree('tree/subtree',
 
1076
                                            format='dirstate-with-subtree')
 
1077
        tree.add('subtree')
 
1078
        tree.commit('hello', rev_id='rev1')
 
1079
        try:
 
1080
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1081
        except errors.IncompatibleBundleFormat:
 
1082
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1083
        if isinstance(bundle, v09.BundleInfo09):
 
1084
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
 
1085
        repo = self.make_repository('repo', format='knit')
 
1086
        self.assertRaises(errors.IncompatibleRevision,
 
1087
                          bundle.install_revisions, repo)
 
1088
        repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
 
1089
        bundle.install_revisions(repo2)
 
1090
 
 
1091
    def test_revision_id_with_slash(self):
 
1092
        self.tree1 = self.make_branch_and_tree('tree')
 
1093
        self.b1 = self.tree1.branch
 
1094
        try:
 
1095
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
 
1096
        except ValueError:
 
1097
            raise TestSkipped("Repository doesn't support revision ids with"
 
1098
                              " slashes")
 
1099
        bundle = self.get_valid_bundle('null:', 'rev/id')
 
1100
 
 
1101
    def test_skip_file(self):
 
1102
        """Make sure we don't accidentally write to the wrong versionedfile"""
 
1103
        self.tree1 = self.make_branch_and_tree('tree')
 
1104
        self.b1 = self.tree1.branch
 
1105
        # rev1 is not present in bundle, done by fetch
 
1106
        self.build_tree_contents([('tree/file2', 'contents1')])
 
1107
        self.tree1.add('file2', 'file2-id')
 
1108
        self.tree1.commit('rev1', rev_id='reva')
 
1109
        self.build_tree_contents([('tree/file3', 'contents2')])
 
1110
        # rev2 is present in bundle, and done by fetch
 
1111
        # having file1 in the bunle causes file1's versionedfile to be opened.
 
1112
        self.tree1.add('file3', 'file3-id')
 
1113
        self.tree1.commit('rev2')
 
1114
        # Updating file2 should not cause an attempt to add to file1's vf
 
1115
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
 
1116
        self.build_tree_contents([('tree/file2', 'contents3')])
 
1117
        self.tree1.commit('rev3', rev_id='rev3')
 
1118
        bundle = self.get_valid_bundle('reva', 'rev3')
 
1119
        if getattr(bundle, 'get_bundle_reader', None) is None:
 
1120
            raise TestSkipped('Bundle format cannot provide reader')
 
1121
        # be sure that file1 comes before file2
 
1122
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
 
1123
            if f == 'file3-id':
 
1124
                break
 
1125
            self.assertNotEqual(f, 'file2-id')
 
1126
        bundle.install_revisions(target.branch.repository)
 
1127
 
 
1128
 
 
1129
class V08BundleTester(BundleTester, TestCaseWithTransport):
 
1130
 
 
1131
    format = '0.8'
 
1132
 
 
1133
    def test_bundle_empty_property(self):
 
1134
        """Test serializing revision properties with an empty value."""
 
1135
        tree = self.make_branch_and_memory_tree('tree')
 
1136
        tree.lock_write()
 
1137
        self.addCleanup(tree.unlock)
 
1138
        tree.add([''], ['TREE_ROOT'])
 
1139
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1140
        self.b1 = tree.branch
 
1141
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1142
        self.assertContainsRe(bundle_sio.getvalue(),
 
1143
                              '# properties:\n'
 
1144
                              '#   branch-nick: tree\n'
 
1145
                              '#   empty: \n'
 
1146
                              '#   one: two\n'
 
1147
                             )
 
1148
        bundle = read_bundle(bundle_sio)
 
1149
        revision_info = bundle.revisions[0]
 
1150
        self.assertEqual('rev1', revision_info.revision_id)
 
1151
        rev = revision_info.as_revision()
 
1152
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1153
                         rev.properties)
 
1154
 
 
1155
    def get_bundle_tree(self, bundle, revision_id):
 
1156
        repository = self.make_repository('repo')
 
1157
        return bundle.revision_tree(repository, 'revid1')
 
1158
 
 
1159
    def test_bundle_empty_property_alt(self):
 
1160
        """Test serializing revision properties with an empty value.
 
1161
 
 
1162
        Older readers had a bug when reading an empty property.
 
1163
        They assumed that all keys ended in ': \n'. However they would write an
 
1164
        empty value as ':\n'. This tests make sure that all newer bzr versions
 
1165
        can handle th second form.
 
1166
        """
 
1167
        tree = self.make_branch_and_memory_tree('tree')
 
1168
        tree.lock_write()
 
1169
        self.addCleanup(tree.unlock)
 
1170
        tree.add([''], ['TREE_ROOT'])
 
1171
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1172
        self.b1 = tree.branch
 
1173
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1174
        txt = bundle_sio.getvalue()
 
1175
        loc = txt.find('#   empty: ') + len('#   empty:')
 
1176
        # Create a new bundle, which strips the trailing space after empty
 
1177
        bundle_sio = StringIO(txt[:loc] + txt[loc+1:])
 
1178
 
 
1179
        self.assertContainsRe(bundle_sio.getvalue(),
 
1180
                              '# properties:\n'
 
1181
                              '#   branch-nick: tree\n'
 
1182
                              '#   empty:\n'
 
1183
                              '#   one: two\n'
 
1184
                             )
 
1185
        bundle = read_bundle(bundle_sio)
 
1186
        revision_info = bundle.revisions[0]
 
1187
        self.assertEqual('rev1', revision_info.revision_id)
 
1188
        rev = revision_info.as_revision()
 
1189
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1190
                         rev.properties)
 
1191
 
 
1192
    def test_bundle_sorted_properties(self):
 
1193
        """For stability the writer should write properties in sorted order."""
 
1194
        tree = self.make_branch_and_memory_tree('tree')
 
1195
        tree.lock_write()
 
1196
        self.addCleanup(tree.unlock)
 
1197
 
 
1198
        tree.add([''], ['TREE_ROOT'])
 
1199
        tree.commit('One', rev_id='rev1',
 
1200
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
1201
        self.b1 = tree.branch
 
1202
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1203
        self.assertContainsRe(bundle_sio.getvalue(),
 
1204
                              '# properties:\n'
 
1205
                              '#   a: 4\n'
 
1206
                              '#   b: 3\n'
 
1207
                              '#   branch-nick: tree\n'
 
1208
                              '#   c: 2\n'
 
1209
                              '#   d: 1\n'
 
1210
                             )
 
1211
        bundle = read_bundle(bundle_sio)
 
1212
        revision_info = bundle.revisions[0]
 
1213
        self.assertEqual('rev1', revision_info.revision_id)
 
1214
        rev = revision_info.as_revision()
 
1215
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
 
1216
                          'd':'1'}, rev.properties)
 
1217
 
 
1218
    def test_bundle_unicode_properties(self):
 
1219
        """We should be able to round trip a non-ascii property."""
 
1220
        tree = self.make_branch_and_memory_tree('tree')
 
1221
        tree.lock_write()
 
1222
        self.addCleanup(tree.unlock)
 
1223
 
 
1224
        tree.add([''], ['TREE_ROOT'])
 
1225
        # Revisions themselves do not require anything about revision property
 
1226
        # keys, other than that they are a basestring, and do not contain
 
1227
        # whitespace.
 
1228
        # However, Testaments assert than they are str(), and thus should not
 
1229
        # be Unicode.
 
1230
        tree.commit('One', rev_id='rev1',
 
1231
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
1232
        self.b1 = tree.branch
 
1233
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1234
        self.assertContainsRe(bundle_sio.getvalue(),
 
1235
                              '# properties:\n'
 
1236
                              '#   alpha: \xce\xb1\n'
 
1237
                              '#   branch-nick: tree\n'
 
1238
                              '#   omega: \xce\xa9\n'
 
1239
                             )
 
1240
        bundle = read_bundle(bundle_sio)
 
1241
        revision_info = bundle.revisions[0]
 
1242
        self.assertEqual('rev1', revision_info.revision_id)
 
1243
        rev = revision_info.as_revision()
 
1244
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
 
1245
                          'alpha':u'\u03b1'}, rev.properties)
 
1246
 
 
1247
 
 
1248
class V09BundleKnit2Tester(V08BundleTester):
 
1249
 
 
1250
    format = '0.9'
 
1251
 
 
1252
    def bzrdir_format(self):
 
1253
        format = bzrdir.BzrDirMetaFormat1()
 
1254
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
1255
        return format
 
1256
 
 
1257
 
 
1258
class V09BundleKnit1Tester(V08BundleTester):
 
1259
 
 
1260
    format = '0.9'
 
1261
 
 
1262
    def bzrdir_format(self):
 
1263
        format = bzrdir.BzrDirMetaFormat1()
 
1264
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
1265
        return format
 
1266
 
 
1267
 
 
1268
class V4BundleTester(BundleTester, TestCaseWithTransport):
 
1269
 
 
1270
    format = '4'
 
1271
 
 
1272
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
 
1273
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1274
        Make sure that the text generated is valid, and that it
 
1275
        can be applied against the base, and generate the same information.
 
1276
        
 
1277
        :return: The in-memory bundle 
 
1278
        """
 
1279
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1280
 
 
1281
        # This should also validate the generated bundle 
 
1282
        bundle = read_bundle(bundle_txt)
 
1283
        repository = self.b1.repository
 
1284
        for bundle_rev in bundle.real_revisions:
 
1285
            # These really should have already been checked when we read the
 
1286
            # bundle, since it computes the sha1 hash for the revision, which
 
1287
            # only will match if everything is okay, but lets be explicit about
 
1288
            # it
 
1289
            branch_rev = repository.get_revision(bundle_rev.revision_id)
 
1290
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
 
1291
                      'timestamp', 'timezone', 'message', 'committer', 
 
1292
                      'parent_ids', 'properties'):
 
1293
                self.assertEqual(getattr(branch_rev, a), 
 
1294
                                 getattr(bundle_rev, a))
 
1295
            self.assertEqual(len(branch_rev.parent_ids), 
 
1296
                             len(bundle_rev.parent_ids))
 
1297
        self.assertEqual(set(rev_ids),
 
1298
                         set([r.revision_id for r in bundle.real_revisions]))
 
1299
        self.valid_apply_bundle(base_rev_id, bundle,
 
1300
                                   checkout_dir=checkout_dir)
 
1301
 
 
1302
        return bundle
 
1303
 
 
1304
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
1305
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1306
        Munge the text so that it's invalid.
 
1307
 
 
1308
        :return: The in-memory bundle
 
1309
        """
 
1310
        from bzrlib.bundle import serializer
 
1311
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1312
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1313
        new_text = new_text.replace('<file file_id="exe-1"',
 
1314
                                    '<file executable="y" file_id="exe-1"')
 
1315
        new_text = new_text.replace('B222', 'B237')
 
1316
        bundle_txt = StringIO()
 
1317
        bundle_txt.write(serializer._get_bundle_header('4'))
 
1318
        bundle_txt.write('\n')
 
1319
        bundle_txt.write(new_text.encode('bz2'))
 
1320
        bundle_txt.seek(0)
 
1321
        bundle = read_bundle(bundle_txt)
 
1322
        self.valid_apply_bundle(base_rev_id, bundle)
 
1323
        return bundle
 
1324
 
 
1325
    def create_bundle_text(self, base_rev_id, rev_id):
 
1326
        bundle_txt = StringIO()
 
1327
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
1328
                               bundle_txt, format=self.format)
 
1329
        bundle_txt.seek(0)
 
1330
        self.assertEqual(bundle_txt.readline(), 
 
1331
                         '# Bazaar revision bundle v%s\n' % self.format)
 
1332
        self.assertEqual(bundle_txt.readline(), '#\n')
 
1333
        rev = self.b1.repository.get_revision(rev_id)
 
1334
        bundle_txt.seek(0)
 
1335
        return bundle_txt, rev_ids
 
1336
 
 
1337
    def get_bundle_tree(self, bundle, revision_id):
 
1338
        repository = self.make_repository('repo')
 
1339
        bundle.install_revisions(repository)
 
1340
        return repository.revision_tree(revision_id)
 
1341
 
 
1342
    def test_creation(self):
 
1343
        tree = self.make_branch_and_tree('tree')
 
1344
        self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
 
1345
        tree.add('file', 'fileid-2')
 
1346
        tree.commit('added file', rev_id='rev1')
 
1347
        self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
 
1348
        tree.commit('changed file', rev_id='rev2')
 
1349
        s = StringIO()
 
1350
        serializer = BundleSerializerV4('1.0')
 
1351
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
 
1352
        s.seek(0)
 
1353
        tree2 = self.make_branch_and_tree('target')
 
1354
        target_repo = tree2.branch.repository
 
1355
        install_bundle(target_repo, serializer.read(s))
 
1356
        target_repo.lock_read()
 
1357
        self.addCleanup(target_repo.unlock)
 
1358
        self.assertEqual({'1':'contents1\nstatic\n',
 
1359
            '2':'contents2\nstatic\n'},
 
1360
            dict(target_repo.iter_files_bytes(
 
1361
                [('fileid-2', 'rev1', '1'), ('fileid-2', 'rev2', '2')])))
 
1362
        rtree = target_repo.revision_tree('rev2')
 
1363
        inventory_vf = target_repo.inventories
 
1364
        # If the inventory store has a graph, it must match the revision graph.
 
1365
        self.assertSubset(
 
1366
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
 
1367
            [None, (('rev1',),)])
 
1368
        self.assertEqual('changed file',
 
1369
                         target_repo.get_revision('rev2').message)
 
1370
 
 
1371
    @staticmethod
 
1372
    def get_raw(bundle_file):
 
1373
        bundle_file.seek(0)
 
1374
        line = bundle_file.readline()
 
1375
        line = bundle_file.readline()
 
1376
        lines = bundle_file.readlines()
 
1377
        return ''.join(lines).decode('bz2')
 
1378
 
 
1379
    def test_copy_signatures(self):
 
1380
        tree_a = self.make_branch_and_tree('tree_a')
 
1381
        import bzrlib.gpg
 
1382
        import bzrlib.commit as commit
 
1383
        oldstrategy = bzrlib.gpg.GPGStrategy
 
1384
        branch = tree_a.branch
 
1385
        repo_a = branch.repository
 
1386
        tree_a.commit("base", allow_pointless=True, rev_id='A')
 
1387
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1388
        try:
 
1389
            from bzrlib.testament import Testament
 
1390
            # monkey patch gpg signing mechanism
 
1391
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
 
1392
            new_config = test_commit.MustSignConfig(branch)
 
1393
            commit.Commit(config=new_config).commit(message="base",
 
1394
                                                    allow_pointless=True,
 
1395
                                                    rev_id='B',
 
1396
                                                    working_tree=tree_a)
 
1397
            def sign(text):
 
1398
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
 
1399
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
 
1400
        finally:
 
1401
            bzrlib.gpg.GPGStrategy = oldstrategy
 
1402
        tree_b = self.make_branch_and_tree('tree_b')
 
1403
        repo_b = tree_b.branch.repository
 
1404
        s = StringIO()
 
1405
        serializer = BundleSerializerV4('4')
 
1406
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
 
1407
        s.seek(0)
 
1408
        install_bundle(repo_b, serializer.read(s))
 
1409
        self.assertTrue(repo_b.has_signature_for_revision_id('B'))
 
1410
        self.assertEqual(repo_b.get_signature_text('B'),
 
1411
                         repo_a.get_signature_text('B'))
 
1412
        s.seek(0)
 
1413
        # ensure repeat installs are harmless
 
1414
        install_bundle(repo_b, serializer.read(s))
 
1415
 
 
1416
 
 
1417
class V4WeaveBundleTester(V4BundleTester):
 
1418
 
 
1419
    def bzrdir_format(self):
 
1420
        return 'metaweave'
 
1421
 
 
1422
 
 
1423
class MungedBundleTester(object):
815
1424
 
816
1425
    def build_test_bundle(self):
817
1426
        wt = self.make_branch_and_tree('b1')
826
1435
 
827
1436
        bundle_txt = StringIO()
828
1437
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
829
 
                               'a@cset-0-1', bundle_txt)
830
 
        self.assertEqual(['a@cset-0-2'], rev_ids)
 
1438
                               'a@cset-0-1', bundle_txt, self.format)
 
1439
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
831
1440
        bundle_txt.seek(0, 0)
832
1441
        return bundle_txt
833
1442
 
862
1471
        bundle = read_bundle(bundle_txt)
863
1472
        self.check_valid(bundle)
864
1473
 
 
1474
 
 
1475
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
 
1476
 
 
1477
    format = '0.9'
 
1478
 
865
1479
    def test_missing_trailing_whitespace(self):
866
1480
        bundle_txt = self.build_test_bundle()
867
1481
 
895
1509
        bundle = read_bundle(bundle_txt)
896
1510
        self.check_valid(bundle)
897
1511
 
 
1512
 
 
1513
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
 
1514
 
 
1515
    format = '4'
 
1516
 
 
1517
 
 
1518
class TestBundleWriterReader(TestCase):
 
1519
 
 
1520
    def test_roundtrip_record(self):
 
1521
        fileobj = StringIO()
 
1522
        writer = v4.BundleWriter(fileobj)
 
1523
        writer.begin()
 
1524
        writer.add_info_record(foo='bar')
 
1525
        writer._add_record("Record body", {'parents': ['1', '3'],
 
1526
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1527
        writer.end()
 
1528
        fileobj.seek(0)
 
1529
        reader = v4.BundleReader(fileobj, stream_input=True)
 
1530
        record_iter = reader.iter_records()
 
1531
        record = record_iter.next()
 
1532
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1533
            'info', None, None), record)
 
1534
        record = record_iter.next()
 
1535
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
 
1536
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
 
1537
                          record)
 
1538
 
 
1539
    def test_roundtrip_record_memory_hungry(self):
 
1540
        fileobj = StringIO()
 
1541
        writer = v4.BundleWriter(fileobj)
 
1542
        writer.begin()
 
1543
        writer.add_info_record(foo='bar')
 
1544
        writer._add_record("Record body", {'parents': ['1', '3'],
 
1545
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1546
        writer.end()
 
1547
        fileobj.seek(0)
 
1548
        reader = v4.BundleReader(fileobj, stream_input=False)
 
1549
        record_iter = reader.iter_records()
 
1550
        record = record_iter.next()
 
1551
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1552
            'info', None, None), record)
 
1553
        record = record_iter.next()
 
1554
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
 
1555
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
 
1556
                          record)
 
1557
 
 
1558
    def test_encode_name(self):
 
1559
        self.assertEqual('revision/rev1',
 
1560
            v4.BundleWriter.encode_name('revision', 'rev1'))
 
1561
        self.assertEqual('file/rev//1/file-id-1',
 
1562
            v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
 
1563
        self.assertEqual('info',
 
1564
            v4.BundleWriter.encode_name('info', None, None))
 
1565
 
 
1566
    def test_decode_name(self):
 
1567
        self.assertEqual(('revision', 'rev1', None),
 
1568
            v4.BundleReader.decode_name('revision/rev1'))
 
1569
        self.assertEqual(('file', 'rev/1', 'file-id-1'),
 
1570
            v4.BundleReader.decode_name('file/rev//1/file-id-1'))
 
1571
        self.assertEqual(('info', None, None),
 
1572
                         v4.BundleReader.decode_name('info'))
 
1573
 
 
1574
    def test_too_many_names(self):
 
1575
        fileobj = StringIO()
 
1576
        writer = v4.BundleWriter(fileobj)
 
1577
        writer.begin()
 
1578
        writer.add_info_record(foo='bar')
 
1579
        writer._container.add_bytes_record('blah', ['two', 'names'])
 
1580
        writer.end()
 
1581
        fileobj.seek(0)
 
1582
        record_iter = v4.BundleReader(fileobj).iter_records()
 
1583
        record = record_iter.next()
 
1584
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1585
            'info', None, None), record)
 
1586
        self.assertRaises(BadBundle, record_iter.next)
 
1587
 
 
1588
 
 
1589
class TestReadMergeableFromUrl(TestCaseWithTransport):
 
1590
 
 
1591
    def test_read_mergeable_skips_local(self):
 
1592
        """A local bundle named like the URL should not be read.
 
1593
        """
 
1594
        out, wt = test_read_bundle.create_bundle_file(self)
 
1595
        class FooService(object):
 
1596
            """A directory service that always returns source"""
 
1597
 
 
1598
            def look_up(self, name, url):
 
1599
                return 'source'
 
1600
        directories.register('foo:', FooService, 'Testing directory service')
 
1601
        self.addCleanup(lambda: directories.remove('foo:'))
 
1602
        self.build_tree_contents([('./foo:bar', out.getvalue())])
 
1603
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
 
1604
                          'foo:bar')