~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Martin Pool
  • Date: 2010-10-11 04:29:32 UTC
  • mto: This revision was merged to the branch mainline in revision 5490.
  • Revision ID: mbp@sourcefrog.net-20101011042932-jqhtsszuznse808w
Add clearer heading descriptions in news template

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
from cStringIO import StringIO
18
18
import os
 
19
import socket
19
20
import sys
20
 
import tempfile
 
21
import threading
21
22
 
22
23
from bzrlib import (
23
24
    bzrdir,
 
25
    diff,
24
26
    errors,
25
27
    inventory,
 
28
    merge,
 
29
    osutils,
26
30
    repository,
27
31
    revision as _mod_revision,
 
32
    tests,
28
33
    treebuilder,
29
34
    )
30
 
from bzrlib.bzrdir import BzrDir
31
35
from bzrlib.bundle import read_mergeable_from_url
32
36
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
33
37
from bzrlib.bundle.bundle_data import BundleTree
 
38
from bzrlib.bzrdir import BzrDir
34
39
from bzrlib.directory_service import directories
35
40
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
36
41
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
37
42
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
38
43
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
39
44
from bzrlib.branch import Branch
40
 
from bzrlib.diff import internal_diff
41
 
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
42
 
                           NoSuchFile,)
43
 
from bzrlib.merge import Merge3Merger
44
45
from bzrlib.repofmt import knitrepo
45
 
from bzrlib.osutils import sha_file, sha_string
46
46
from bzrlib.tests import (
47
 
    SymlinkFeature,
48
 
    TestCase,
49
 
    TestCaseInTempDir,
50
 
    TestCaseWithTransport,
51
 
    TestSkipped,
52
47
    test_read_bundle,
53
48
    test_commit,
54
49
    )
55
50
from bzrlib.transform import TreeTransform
56
51
 
57
52
 
 
53
def get_text(vf, key):
 
54
    """Get the fulltext for a given revision id that is present in the vf"""
 
55
    stream = vf.get_record_stream([key], 'unordered', True)
 
56
    record = stream.next()
 
57
    return record.get_bytes_as('fulltext')
 
58
 
 
59
 
 
60
def get_inventory_text(repo, revision_id):
 
61
    """Get the fulltext for the inventory at revision id"""
 
62
    repo.lock_read()
 
63
    try:
 
64
        return get_text(repo.inventories, (revision_id,))
 
65
    finally:
 
66
        repo.unlock()
 
67
 
 
68
 
58
69
class MockTree(object):
59
70
    def __init__(self):
60
71
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
103
114
            ie = InventoryDirectory(file_id, name, parent_id)
104
115
        elif kind == 'file':
105
116
            ie = InventoryFile(file_id, name, parent_id)
 
117
            ie.text_sha1 = text_sha_1
 
118
            ie.text_size = text_size
106
119
        elif kind == 'symlink':
107
120
            ie = InventoryLink(file_id, name, parent_id)
108
121
        else:
109
 
            raise BzrError('unknown kind %r' % kind)
110
 
        ie.text_sha1 = text_sha_1
111
 
        ie.text_size = text_size
 
122
            raise errors.BzrError('unknown kind %r' % kind)
112
123
        return ie
113
124
 
114
125
    def add_dir(self, file_id, path):
115
126
        self.paths[file_id] = path
116
127
        self.ids[path] = file_id
117
 
    
 
128
 
118
129
    def add_file(self, file_id, path, contents):
119
130
        self.add_dir(file_id, path)
120
131
        self.contents[file_id] = contents
137
148
    def contents_stats(self, file_id):
138
149
        if file_id not in self.contents:
139
150
            return None, None
140
 
        text_sha1 = sha_file(self.get_file(file_id))
 
151
        text_sha1 = osutils.sha_file(self.get_file(file_id))
141
152
        return text_sha1, len(self.contents[file_id])
142
153
 
143
154
 
144
 
class BTreeTester(TestCase):
 
155
class BTreeTester(tests.TestCase):
145
156
    """A simple unittest tester for the BundleTree class."""
146
157
 
147
158
    def make_tree_1(self):
151
162
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
152
163
        mtree.add_dir("d", "grandparent/alt_parent")
153
164
        return BundleTree(mtree, ''), mtree
154
 
        
 
165
 
155
166
    def test_renames(self):
156
167
        """Ensure that file renames have the proper effect on children"""
157
168
        btree = self.make_tree_1()[0]
158
169
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
159
 
        self.assertEqual(btree.old_path("grandparent/parent"), 
 
170
        self.assertEqual(btree.old_path("grandparent/parent"),
160
171
                         "grandparent/parent")
161
172
        self.assertEqual(btree.old_path("grandparent/parent/file"),
162
173
                         "grandparent/parent/file")
202
213
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
203
214
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
204
215
 
205
 
        btree.note_rename("grandparent/parent/file", 
 
216
        btree.note_rename("grandparent/parent/file",
206
217
                          "grandparent2/parent2/file2")
207
218
        self.assertEqual(btree.id2path("a"), "grandparent2")
208
219
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
217
228
    def test_moves(self):
218
229
        """Ensure that file moves have the proper effect on children"""
219
230
        btree = self.make_tree_1()[0]
220
 
        btree.note_rename("grandparent/parent/file", 
 
231
        btree.note_rename("grandparent/parent/file",
221
232
                          "grandparent/alt_parent/file")
222
233
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
223
234
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
225
236
 
226
237
    def unified_diff(self, old, new):
227
238
        out = StringIO()
228
 
        internal_diff("old", old, "new", new, out)
 
239
        diff.internal_diff("old", old, "new", new, out)
229
240
        out.seek(0,0)
230
241
        return out.read()
231
242
 
232
243
    def make_tree_2(self):
233
244
        btree = self.make_tree_1()[0]
234
 
        btree.note_rename("grandparent/parent/file", 
 
245
        btree.note_rename("grandparent/parent/file",
235
246
                          "grandparent/alt_parent/file")
236
247
        self.assertTrue(btree.id2path("e") is None)
237
248
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
266
277
    def make_tree_3(self):
267
278
        btree, mtree = self.make_tree_1()
268
279
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
269
 
        btree.note_rename("grandparent/parent/file", 
 
280
        btree.note_rename("grandparent/parent/file",
270
281
                          "grandparent/alt_parent/file")
271
 
        btree.note_rename("grandparent/parent/topping", 
 
282
        btree.note_rename("grandparent/parent/topping",
272
283
                          "grandparent/alt_parent/stopping")
273
284
        return btree
274
285
 
313
324
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
314
325
        btree.note_deletion("grandparent/parent/file")
315
326
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
316
 
        btree.note_last_changed("grandparent/alt_parent/fool", 
 
327
        btree.note_last_changed("grandparent/alt_parent/fool",
317
328
                                "revisionidiguess")
318
329
        self.assertEqual(self.sorted_ids(btree),
319
330
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
320
331
 
321
332
 
322
 
class BundleTester1(TestCaseWithTransport):
 
333
class BundleTester1(tests.TestCaseWithTransport):
323
334
 
324
335
    def test_mismatched_bundle(self):
325
336
        format = bzrdir.BzrDirMetaFormat1()
326
337
        format.repository_format = knitrepo.RepositoryFormatKnit3()
327
338
        serializer = BundleSerializerV08('0.8')
328
339
        b = self.make_branch('.', format=format)
329
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
 
340
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
330
341
                          b.repository, [], {}, StringIO())
331
342
 
332
343
    def test_matched_bundle(self):
352
363
        format = bzrdir.BzrDirMetaFormat1()
353
364
        format.repository_format = knitrepo.RepositoryFormatKnit1()
354
365
        target = self.make_branch('target', format=format)
355
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
366
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
356
367
                          target.repository, read_bundle(text))
357
368
 
358
369
 
366
377
    def make_branch_and_tree(self, path, format=None):
367
378
        if format is None:
368
379
            format = self.bzrdir_format()
369
 
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
380
        return tests.TestCaseWithTransport.make_branch_and_tree(
 
381
            self, path, format)
370
382
 
371
383
    def make_branch(self, path, format=None):
372
384
        if format is None:
373
385
            format = self.bzrdir_format()
374
 
        return TestCaseWithTransport.make_branch(self, path, format)
 
386
        return tests.TestCaseWithTransport.make_branch(self, path, format)
375
387
 
376
388
    def create_bundle_text(self, base_rev_id, rev_id):
377
389
        bundle_txt = StringIO()
378
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
390
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
379
391
                               bundle_txt, format=self.format)
380
392
        bundle_txt.seek(0)
381
 
        self.assertEqual(bundle_txt.readline(), 
 
393
        self.assertEqual(bundle_txt.readline(),
382
394
                         '# Bazaar revision bundle v%s\n' % self.format)
383
395
        self.assertEqual(bundle_txt.readline(), '#\n')
384
396
 
392
404
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
393
405
        Make sure that the text generated is valid, and that it
394
406
        can be applied against the base, and generate the same information.
395
 
        
396
 
        :return: The in-memory bundle 
 
407
 
 
408
        :return: The in-memory bundle
397
409
        """
398
410
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
399
411
 
400
 
        # This should also validate the generated bundle 
 
412
        # This should also validate the generated bundle
401
413
        bundle = read_bundle(bundle_txt)
402
414
        repository = self.b1.repository
403
415
        for bundle_rev in bundle.real_revisions:
407
419
            # it
408
420
            branch_rev = repository.get_revision(bundle_rev.revision_id)
409
421
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
410
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
422
                      'timestamp', 'timezone', 'message', 'committer',
411
423
                      'parent_ids', 'properties'):
412
 
                self.assertEqual(getattr(branch_rev, a), 
 
424
                self.assertEqual(getattr(branch_rev, a),
413
425
                                 getattr(bundle_rev, a))
414
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
426
            self.assertEqual(len(branch_rev.parent_ids),
415
427
                             len(bundle_rev.parent_ids))
416
 
        self.assertEqual(rev_ids, 
 
428
        self.assertEqual(rev_ids,
417
429
                         [r.revision_id for r in bundle.real_revisions])
418
430
        self.valid_apply_bundle(base_rev_id, bundle,
419
431
                                   checkout_dir=checkout_dir)
423
435
    def get_invalid_bundle(self, base_rev_id, rev_id):
424
436
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
425
437
        Munge the text so that it's invalid.
426
 
        
 
438
 
427
439
        :return: The in-memory bundle
428
440
        """
429
441
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
430
 
        new_text = bundle_txt.getvalue().replace('executable:no', 
 
442
        new_text = bundle_txt.getvalue().replace('executable:no',
431
443
                                               'executable:yes')
432
444
        bundle_txt = StringIO(new_text)
433
445
        bundle = read_bundle(bundle_txt)
434
446
        self.valid_apply_bundle(base_rev_id, bundle)
435
 
        return bundle 
 
447
        return bundle
436
448
 
437
449
    def test_non_bundle(self):
438
 
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
 
450
        self.assertRaises(errors.NotABundle,
 
451
                          read_bundle, StringIO('#!/bin/sh\n'))
439
452
 
440
453
    def test_malformed(self):
441
 
        self.assertRaises(BadBundle, read_bundle, 
 
454
        self.assertRaises(errors.BadBundle, read_bundle,
442
455
                          StringIO('# Bazaar revision bundle v'))
443
456
 
444
457
    def test_crlf_bundle(self):
445
458
        try:
446
459
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
447
 
        except BadBundle:
 
460
        except errors.BadBundle:
448
461
            # It is currently permitted for bundles with crlf line endings to
449
462
            # make read_bundle raise a BadBundle, but this should be fixed.
450
463
            # Anything else, especially NotABundle, is an error.
455
468
        """
456
469
 
457
470
        if checkout_dir is None:
458
 
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
 
471
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
459
472
        else:
460
473
            if not os.path.exists(checkout_dir):
461
474
                os.mkdir(checkout_dir)
469
482
        for ancestor in ancestors:
470
483
            old = self.b1.repository.revision_tree(ancestor)
471
484
            new = tree.branch.repository.revision_tree(ancestor)
472
 
 
473
 
            # Check that there aren't any inventory level changes
474
 
            delta = new.changes_from(old)
475
 
            self.assertFalse(delta.has_changed(),
476
 
                             'Revision %s not copied correctly.'
477
 
                             % (ancestor,))
478
 
 
479
 
            # Now check that the file contents are all correct
480
 
            for inventory_id in old:
481
 
                try:
482
 
                    old_file = old.get_file(inventory_id)
483
 
                except NoSuchFile:
484
 
                    continue
485
 
                if old_file is None:
486
 
                    continue
487
 
                self.assertEqual(old_file.read(),
488
 
                                 new.get_file(inventory_id).read())
 
485
            old.lock_read()
 
486
            new.lock_read()
 
487
            try:
 
488
                # Check that there aren't any inventory level changes
 
489
                delta = new.changes_from(old)
 
490
                self.assertFalse(delta.has_changed(),
 
491
                                 'Revision %s not copied correctly.'
 
492
                                 % (ancestor,))
 
493
 
 
494
                # Now check that the file contents are all correct
 
495
                for inventory_id in old:
 
496
                    try:
 
497
                        old_file = old.get_file(inventory_id)
 
498
                    except errors.NoSuchFile:
 
499
                        continue
 
500
                    if old_file is None:
 
501
                        continue
 
502
                    self.assertEqual(old_file.read(),
 
503
                                     new.get_file(inventory_id).read())
 
504
            finally:
 
505
                new.unlock()
 
506
                old.unlock()
489
507
        if not _mod_revision.is_null(rev_id):
490
508
            rh = self.b1.revision_history()
491
509
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
513
531
        self.assertIs(repository.has_revision(base_rev_id), True)
514
532
        for rev in info.real_revisions:
515
533
            self.assert_(not repository.has_revision(rev.revision_id),
516
 
                'Revision {%s} present before applying bundle' 
 
534
                'Revision {%s} present before applying bundle'
517
535
                % rev.revision_id)
518
 
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
 
536
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
519
537
 
520
538
        for rev in info.real_revisions:
521
539
            self.assert_(repository.has_revision(rev.revision_id),
522
 
                'Missing revision {%s} after applying bundle' 
 
540
                'Missing revision {%s} after applying bundle'
523
541
                % rev.revision_id)
524
542
 
525
543
        self.assert_(to_tree.branch.repository.has_revision(info.target))
531
549
        rev = info.real_revisions[-1]
532
550
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
533
551
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
534
 
        
 
552
 
535
553
        # TODO: make sure the target tree is identical to base tree
536
554
        #       we might also check the working tree.
537
555
 
556
574
        self.tree1 = self.make_branch_and_tree('b1')
557
575
        self.b1 = self.tree1.branch
558
576
 
559
 
        open('b1/one', 'wb').write('one\n')
560
 
        self.tree1.add('one')
 
577
        self.build_tree_contents([('b1/one', 'one\n')])
 
578
        self.tree1.add('one', 'one-id')
 
579
        self.tree1.set_root_id('root-id')
561
580
        self.tree1.commit('add one', rev_id='a@cset-0-1')
562
581
 
563
582
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
574
593
                , 'b1/sub/sub/'
575
594
                , 'b1/sub/sub/nonempty.txt'
576
595
                ])
577
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
578
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
 
596
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
 
597
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
579
598
        tt = TreeTransform(self.tree1)
580
599
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
581
600
        tt.apply()
597
616
 
598
617
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
599
618
 
600
 
        # Check a rollup bundle 
 
619
        # Check a rollup bundle
601
620
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
602
621
 
603
622
        # Now delete entries
611
630
        tt.set_executability(False, trans_id)
612
631
        tt.apply()
613
632
        self.tree1.commit('removed', rev_id='a@cset-0-3')
614
 
        
 
633
 
615
634
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
616
 
        self.assertRaises((TestamentMismatch,
617
 
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
635
        self.assertRaises((errors.TestamentMismatch,
 
636
            errors.VersionedFileInvalidChecksum,
 
637
            errors.BadBundle), self.get_invalid_bundle,
618
638
            'a@cset-0-2', 'a@cset-0-3')
619
 
        # Check a rollup bundle 
 
639
        # Check a rollup bundle
620
640
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
621
641
 
622
642
        # Now move the directory
624
644
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
625
645
 
626
646
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
627
 
        # Check a rollup bundle 
 
647
        # Check a rollup bundle
628
648
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
629
649
 
630
650
        # Modified files
632
652
        open('b1/sub/dir/ pre space', 'ab').write(
633
653
             '\r\nAdding some\r\nDOS format lines\r\n')
634
654
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
635
 
        self.tree1.rename_one('sub/dir/ pre space', 
 
655
        self.tree1.rename_one('sub/dir/ pre space',
636
656
                              'sub/ start space')
637
657
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
638
658
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
644
664
                          verbose=False)
645
665
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
646
666
        other = self.get_checkout('a@cset-0-5')
647
 
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
648
 
            'a@cset-0-5')
649
 
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
667
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
 
668
                                       'a@cset-0-5')
 
669
        tree2_inv = get_inventory_text(other.branch.repository,
 
670
                                       'a@cset-0-5')
650
671
        self.assertEqualDiff(tree1_inv, tree2_inv)
651
672
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
652
673
        other.commit('rename file', rev_id='a@cset-0-6b')
655
676
                          verbose=False)
656
677
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
657
678
 
658
 
    def test_symlink_bundle(self):
659
 
        self.requireFeature(SymlinkFeature)
 
679
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
 
680
        link_id = 'link-1'
 
681
 
 
682
        self.requireFeature(tests.SymlinkFeature)
660
683
        self.tree1 = self.make_branch_and_tree('b1')
661
684
        self.b1 = self.tree1.branch
 
685
 
662
686
        tt = TreeTransform(self.tree1)
663
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
 
687
        tt.new_symlink(link_name, tt.root, link_target, link_id)
664
688
        tt.apply()
665
689
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
666
 
        self.get_valid_bundle('null:', 'l@cset-0-1')
 
690
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
 
691
        if getattr(bundle ,'revision_tree', None) is not None:
 
692
            # Not all bundle formats supports revision_tree
 
693
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
 
694
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
 
695
 
667
696
        tt = TreeTransform(self.tree1)
668
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
697
        trans_id = tt.trans_id_tree_file_id(link_id)
669
698
        tt.adjust_path('link2', tt.root, trans_id)
670
699
        tt.delete_contents(trans_id)
671
 
        tt.create_symlink('mars', trans_id)
 
700
        tt.create_symlink(new_link_target, trans_id)
672
701
        tt.apply()
673
702
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
674
 
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
703
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
704
        if getattr(bundle ,'revision_tree', None) is not None:
 
705
            # Not all bundle formats supports revision_tree
 
706
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
 
707
            self.assertEqual(new_link_target,
 
708
                             bund_tree.get_symlink_target(link_id))
 
709
 
675
710
        tt = TreeTransform(self.tree1)
676
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
711
        trans_id = tt.trans_id_tree_file_id(link_id)
677
712
        tt.delete_contents(trans_id)
678
713
        tt.create_symlink('jupiter', trans_id)
679
714
        tt.apply()
680
715
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
681
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
716
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
717
 
682
718
        tt = TreeTransform(self.tree1)
683
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
719
        trans_id = tt.trans_id_tree_file_id(link_id)
684
720
        tt.delete_contents(trans_id)
685
721
        tt.apply()
686
722
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
687
 
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
723
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
724
 
 
725
    def test_symlink_bundle(self):
 
726
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
 
727
 
 
728
    def test_unicode_symlink_bundle(self):
 
729
        self.requireFeature(tests.UnicodeFilenameFeature)
 
730
        self._test_symlink_bundle(u'\N{Euro Sign}link',
 
731
                                  u'bar/\N{Euro Sign}foo',
 
732
                                  u'mars\N{Euro Sign}')
688
733
 
689
734
    def test_binary_bundle(self):
690
735
        self.tree1 = self.make_branch_and_tree('b1')
691
736
        self.b1 = self.tree1.branch
692
737
        tt = TreeTransform(self.tree1)
693
 
        
 
738
 
694
739
        # Add
695
740
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
696
741
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
788
833
        return bundle_file.getvalue()
789
834
 
790
835
    def test_unicode_bundle(self):
 
836
        self.requireFeature(tests.UnicodeFilenameFeature)
791
837
        # Handle international characters
792
838
        os.mkdir('b1')
793
 
        try:
794
 
            f = open(u'b1/with Dod\xe9', 'wb')
795
 
        except UnicodeEncodeError:
796
 
            raise TestSkipped("Filesystem doesn't support unicode")
 
839
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
797
840
 
798
841
        self.tree1 = self.make_branch_and_tree('b1')
799
842
        self.b1 = self.tree1.branch
803
846
            u'William Dod\xe9\n').encode('utf-8'))
804
847
        f.close()
805
848
 
806
 
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
 
849
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
807
850
        self.tree1.commit(u'i18n commit from William Dod\xe9',
808
851
                          rev_id='i18n-1', committer=u'William Dod\xe9')
809
852
 
810
 
        if sys.platform == 'darwin':
811
 
            from bzrlib.workingtree import WorkingTree3
812
 
            if type(self.tree1) is WorkingTree3:
813
 
                self.knownFailure("Bug #141438: fails for WorkingTree3 on OSX")
814
 
 
815
 
            # On Mac the '\xe9' gets changed to 'e\u0301'
816
 
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
817
 
                             sorted(os.listdir(u'b1')))
818
 
            delta = self.tree1.changes_from(self.tree1.basis_tree())
819
 
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
820
 
                             delta.removed)
821
 
            self.knownFailure("Mac OSX doesn't preserve unicode"
822
 
                              " combining characters.")
823
 
 
824
853
        # Add
825
854
        bundle = self.get_valid_bundle('null:', 'i18n-1')
826
855
 
827
856
        # Modified
828
 
        f = open(u'b1/with Dod\xe9', 'wb')
 
857
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
829
858
        f.write(u'Modified \xb5\n'.encode('utf8'))
830
859
        f.close()
831
860
        self.tree1.commit(u'modified', rev_id='i18n-2')
832
861
 
833
862
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
834
 
        
 
863
 
835
864
        # Renamed
836
 
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
 
865
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
837
866
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
838
867
                          committer=u'Erik B\xe5gfors')
839
868
 
840
869
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
841
870
 
842
871
        # Removed
843
 
        self.tree1.remove([u'B\xe5gfors'])
 
872
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
844
873
        self.tree1.commit(u'removed', rev_id='i18n-4')
845
874
 
846
875
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
851
880
 
852
881
    def test_whitespace_bundle(self):
853
882
        if sys.platform in ('win32', 'cygwin'):
854
 
            raise TestSkipped('Windows doesn\'t support filenames'
855
 
                              ' with tabs or trailing spaces')
 
883
            raise tests.TestSkipped('Windows doesn\'t support filenames'
 
884
                                    ' with tabs or trailing spaces')
856
885
        self.tree1 = self.make_branch_and_tree('b1')
857
886
        self.b1 = self.tree1.branch
858
887
 
883
912
        self.tree1.commit('removed', rev_id='white-4')
884
913
 
885
914
        bundle = self.get_valid_bundle('white-3', 'white-4')
886
 
        
 
915
 
887
916
        # Now test a complet roll-up
888
917
        bundle = self.get_valid_bundle('null:', 'white-4')
889
918
 
902
931
                          timezone=19800, timestamp=1152544886.0)
903
932
 
904
933
        bundle = self.get_valid_bundle('null:', 'tz-1')
905
 
        
 
934
 
906
935
        rev = bundle.revisions[0]
907
936
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
908
937
        self.assertEqual(19800, rev.timezone)
1010
1039
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1011
1040
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1012
1041
        bundle.install_revisions(repo)
1013
 
        inv_text = repo.get_inventory_xml('rev2')
 
1042
        inv_text = repo._get_inventory_xml('rev2')
1014
1043
        self.assertNotContainsRe(inv_text, 'format="5"')
1015
1044
        self.assertContainsRe(inv_text, 'format="7"')
1016
1045
 
1027
1056
        repo = self.make_repo_with_installed_revisions()
1028
1057
        inv = repo.get_inventory('rev2')
1029
1058
        self.assertEqual('rev2', inv.root.revision)
1030
 
        root_vf = repo.weave_store.get_weave(inv.root.file_id,
1031
 
                                             repo.get_transaction())
1032
 
        self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
 
1059
        root_id = inv.root.file_id
 
1060
        repo.lock_read()
 
1061
        self.addCleanup(repo.unlock)
 
1062
        self.assertEqual({(root_id, 'rev1'):(),
 
1063
            (root_id, 'rev2'):((root_id, 'rev1'),)},
 
1064
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
1033
1065
 
1034
1066
    def test_inv_hash_across_serializers(self):
1035
1067
        repo = self.make_repo_with_installed_revisions()
1036
 
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
1037
 
        xml = repo.get_inventory_xml('rev2')
1038
 
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
 
1068
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
 
1069
        xml = repo._get_inventory_xml('rev2')
 
1070
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1039
1071
 
1040
1072
    def test_across_models_incompatible(self):
1041
1073
        tree = self.make_simple_tree('dirstate-with-subtree')
1044
1076
        try:
1045
1077
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1046
1078
        except errors.IncompatibleBundleFormat:
1047
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1079
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1048
1080
        repo = self.make_repository('repo', format='knit')
1049
1081
        bundle.install_revisions(repo)
1050
1082
 
1071
1103
        try:
1072
1104
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1073
1105
        except errors.IncompatibleBundleFormat:
1074
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1106
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1075
1107
        if isinstance(bundle, v09.BundleInfo09):
1076
 
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
 
1108
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
1077
1109
        repo = self.make_repository('repo', format='knit')
1078
1110
        self.assertRaises(errors.IncompatibleRevision,
1079
1111
                          bundle.install_revisions, repo)
1086
1118
        try:
1087
1119
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1088
1120
        except ValueError:
1089
 
            raise TestSkipped("Repository doesn't support revision ids with"
1090
 
                              " slashes")
 
1121
            raise tests.TestSkipped(
 
1122
                "Repository doesn't support revision ids with slashes")
1091
1123
        bundle = self.get_valid_bundle('null:', 'rev/id')
1092
1124
 
1093
1125
    def test_skip_file(self):
1109
1141
        self.tree1.commit('rev3', rev_id='rev3')
1110
1142
        bundle = self.get_valid_bundle('reva', 'rev3')
1111
1143
        if getattr(bundle, 'get_bundle_reader', None) is None:
1112
 
            raise TestSkipped('Bundle format cannot provide reader')
 
1144
            raise tests.TestSkipped('Bundle format cannot provide reader')
1113
1145
        # be sure that file1 comes before file2
1114
1146
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1115
1147
            if f == 'file3-id':
1118
1150
        bundle.install_revisions(target.branch.repository)
1119
1151
 
1120
1152
 
1121
 
class V08BundleTester(BundleTester, TestCaseWithTransport):
 
1153
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
1122
1154
 
1123
1155
    format = '0.8'
1124
1156
 
1257
1289
        return format
1258
1290
 
1259
1291
 
1260
 
class V4BundleTester(BundleTester, TestCaseWithTransport):
 
1292
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
1261
1293
 
1262
1294
    format = '4'
1263
1295
 
1265
1297
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1266
1298
        Make sure that the text generated is valid, and that it
1267
1299
        can be applied against the base, and generate the same information.
1268
 
        
1269
 
        :return: The in-memory bundle 
 
1300
 
 
1301
        :return: The in-memory bundle
1270
1302
        """
1271
1303
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1272
1304
 
1273
 
        # This should also validate the generated bundle 
 
1305
        # This should also validate the generated bundle
1274
1306
        bundle = read_bundle(bundle_txt)
1275
1307
        repository = self.b1.repository
1276
1308
        for bundle_rev in bundle.real_revisions:
1280
1312
            # it
1281
1313
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1282
1314
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1283
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
1315
                      'timestamp', 'timezone', 'message', 'committer',
1284
1316
                      'parent_ids', 'properties'):
1285
 
                self.assertEqual(getattr(branch_rev, a), 
 
1317
                self.assertEqual(getattr(branch_rev, a),
1286
1318
                                 getattr(bundle_rev, a))
1287
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
1319
            self.assertEqual(len(branch_rev.parent_ids),
1288
1320
                             len(bundle_rev.parent_ids))
1289
1321
        self.assertEqual(set(rev_ids),
1290
1322
                         set([r.revision_id for r in bundle.real_revisions]))
1304
1336
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1305
1337
        new_text = new_text.replace('<file file_id="exe-1"',
1306
1338
                                    '<file executable="y" file_id="exe-1"')
1307
 
        new_text = new_text.replace('B222', 'B237')
 
1339
        new_text = new_text.replace('B260', 'B275')
1308
1340
        bundle_txt = StringIO()
1309
1341
        bundle_txt.write(serializer._get_bundle_header('4'))
1310
1342
        bundle_txt.write('\n')
1316
1348
 
1317
1349
    def create_bundle_text(self, base_rev_id, rev_id):
1318
1350
        bundle_txt = StringIO()
1319
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
1351
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1320
1352
                               bundle_txt, format=self.format)
1321
1353
        bundle_txt.seek(0)
1322
 
        self.assertEqual(bundle_txt.readline(), 
 
1354
        self.assertEqual(bundle_txt.readline(),
1323
1355
                         '# Bazaar revision bundle v%s\n' % self.format)
1324
1356
        self.assertEqual(bundle_txt.readline(), '#\n')
1325
1357
        rev = self.b1.repository.get_revision(rev_id)
1345
1377
        tree2 = self.make_branch_and_tree('target')
1346
1378
        target_repo = tree2.branch.repository
1347
1379
        install_bundle(target_repo, serializer.read(s))
1348
 
        vf = target_repo.weave_store.get_weave('fileid-2',
1349
 
            target_repo.get_transaction())
1350
 
        self.assertEqual('contents1\nstatic\n', vf.get_text('rev1'))
1351
 
        self.assertEqual('contents2\nstatic\n', vf.get_text('rev2'))
 
1380
        target_repo.lock_read()
 
1381
        self.addCleanup(target_repo.unlock)
 
1382
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
 
1383
        repo_texts = dict((i, ''.join(content)) for i, content
 
1384
                          in target_repo.iter_files_bytes(
 
1385
                                [('fileid-2', 'rev1', '1'),
 
1386
                                 ('fileid-2', 'rev2', '2')]))
 
1387
        self.assertEqual({'1':'contents1\nstatic\n',
 
1388
                          '2':'contents2\nstatic\n'},
 
1389
                         repo_texts)
1352
1390
        rtree = target_repo.revision_tree('rev2')
1353
 
        inventory_vf = target_repo.get_inventory_weave()
1354
 
        self.assertEqual({'rev2':('rev1',)},
1355
 
            inventory_vf.get_parent_map(['rev2']))
 
1391
        inventory_vf = target_repo.inventories
 
1392
        # If the inventory store has a graph, it must match the revision graph.
 
1393
        self.assertSubset(
 
1394
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
 
1395
            [None, (('rev1',),)])
1356
1396
        self.assertEqual('changed file',
1357
1397
                         target_repo.get_revision('rev2').message)
1358
1398
 
1408
1448
        return 'metaweave'
1409
1449
 
1410
1450
 
 
1451
class V4_2aBundleTester(V4BundleTester):
 
1452
 
 
1453
    def bzrdir_format(self):
 
1454
        return '2a'
 
1455
 
 
1456
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
1457
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1458
        Munge the text so that it's invalid.
 
1459
 
 
1460
        :return: The in-memory bundle
 
1461
        """
 
1462
        from bzrlib.bundle import serializer
 
1463
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1464
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1465
        # We are going to be replacing some text to set the executable bit on a
 
1466
        # file. Make sure the text replacement actually works correctly.
 
1467
        self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
 
1468
        new_text = new_text.replace('<file file_id="exe-1"',
 
1469
                                    '<file executable="y" file_id="exe-1"')
 
1470
        new_text = new_text.replace('B244', 'B259')
 
1471
        bundle_txt = StringIO()
 
1472
        bundle_txt.write(serializer._get_bundle_header('4'))
 
1473
        bundle_txt.write('\n')
 
1474
        bundle_txt.write(new_text.encode('bz2'))
 
1475
        bundle_txt.seek(0)
 
1476
        bundle = read_bundle(bundle_txt)
 
1477
        self.valid_apply_bundle(base_rev_id, bundle)
 
1478
        return bundle
 
1479
 
 
1480
    def make_merged_branch(self):
 
1481
        builder = self.make_branch_builder('source')
 
1482
        builder.start_series()
 
1483
        builder.build_snapshot('a@cset-0-1', None, [
 
1484
            ('add', ('', 'root-id', 'directory', None)),
 
1485
            ('add', ('file', 'file-id', 'file', 'original content\n')),
 
1486
            ])
 
1487
        builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
 
1488
            ('modify', ('file-id', 'new-content\n')),
 
1489
            ])
 
1490
        builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
 
1491
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1492
            ])
 
1493
        builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
 
1494
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1495
            ])
 
1496
        builder.finish_series()
 
1497
        self.b1 = builder.get_branch()
 
1498
        self.b1.lock_read()
 
1499
        self.addCleanup(self.b1.unlock)
 
1500
 
 
1501
    def make_bundle_just_inventories(self, base_revision_id,
 
1502
                                     target_revision_id,
 
1503
                                     revision_ids):
 
1504
        sio = StringIO()
 
1505
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
 
1506
                                         self.b1.repository, sio)
 
1507
        writer.bundle.begin()
 
1508
        writer._add_inventory_mpdiffs_from_serializer(revision_ids)
 
1509
        writer.bundle.end()
 
1510
        sio.seek(0)
 
1511
        return sio
 
1512
 
 
1513
    def test_single_inventory_multiple_parents_as_xml(self):
 
1514
        self.make_merged_branch()
 
1515
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1516
                                                ['a@cset-0-3'])
 
1517
        reader = v4.BundleReader(sio, stream_input=False)
 
1518
        records = list(reader.iter_records())
 
1519
        self.assertEqual(1, len(records))
 
1520
        (bytes, metadata, repo_kind, revision_id,
 
1521
         file_id) = records[0]
 
1522
        self.assertIs(None, file_id)
 
1523
        self.assertEqual('a@cset-0-3', revision_id)
 
1524
        self.assertEqual('inventory', repo_kind)
 
1525
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1526
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1527
                          'storage_kind': 'mpdiff',
 
1528
                         }, metadata)
 
1529
        # We should have an mpdiff that takes some lines from both parents.
 
1530
        self.assertEqualDiff(
 
1531
            'i 1\n'
 
1532
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1533
            '\n'
 
1534
            'c 0 1 1 2\n'
 
1535
            'c 1 3 3 2\n', bytes)
 
1536
 
 
1537
    def test_single_inv_no_parents_as_xml(self):
 
1538
        self.make_merged_branch()
 
1539
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
 
1540
                                                ['a@cset-0-1'])
 
1541
        reader = v4.BundleReader(sio, stream_input=False)
 
1542
        records = list(reader.iter_records())
 
1543
        self.assertEqual(1, len(records))
 
1544
        (bytes, metadata, repo_kind, revision_id,
 
1545
         file_id) = records[0]
 
1546
        self.assertIs(None, file_id)
 
1547
        self.assertEqual('a@cset-0-1', revision_id)
 
1548
        self.assertEqual('inventory', repo_kind)
 
1549
        self.assertEqual({'parents': [],
 
1550
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
 
1551
                          'storage_kind': 'mpdiff',
 
1552
                         }, metadata)
 
1553
        # We should have an mpdiff that takes some lines from both parents.
 
1554
        self.assertEqualDiff(
 
1555
            'i 4\n'
 
1556
            '<inventory format="10" revision_id="a@cset-0-1">\n'
 
1557
            '<directory file_id="root-id" name=""'
 
1558
                ' revision="a@cset-0-1" />\n'
 
1559
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1560
                ' revision="a@cset-0-1"'
 
1561
                ' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
 
1562
                ' text_size="17" />\n'
 
1563
            '</inventory>\n'
 
1564
            '\n', bytes)
 
1565
 
 
1566
    def test_multiple_inventories_as_xml(self):
 
1567
        self.make_merged_branch()
 
1568
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1569
            ['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
 
1570
        reader = v4.BundleReader(sio, stream_input=False)
 
1571
        records = list(reader.iter_records())
 
1572
        self.assertEqual(3, len(records))
 
1573
        revision_ids = [rev_id for b, m, k, rev_id, f in records]
 
1574
        self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
 
1575
                         revision_ids)
 
1576
        metadata_2a = records[0][1]
 
1577
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1578
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
 
1579
                          'storage_kind': 'mpdiff',
 
1580
                         }, metadata_2a)
 
1581
        metadata_2b = records[1][1]
 
1582
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1583
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
 
1584
                          'storage_kind': 'mpdiff',
 
1585
                         }, metadata_2b)
 
1586
        metadata_3 = records[2][1]
 
1587
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1588
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1589
                          'storage_kind': 'mpdiff',
 
1590
                         }, metadata_3)
 
1591
        bytes_2a = records[0][0]
 
1592
        self.assertEqualDiff(
 
1593
            'i 1\n'
 
1594
            '<inventory format="10" revision_id="a@cset-0-2a">\n'
 
1595
            '\n'
 
1596
            'c 0 1 1 1\n'
 
1597
            'i 1\n'
 
1598
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1599
                ' revision="a@cset-0-2a"'
 
1600
                ' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
 
1601
                ' text_size="12" />\n'
 
1602
            '\n'
 
1603
            'c 0 3 3 1\n', bytes_2a)
 
1604
        bytes_2b = records[1][0]
 
1605
        self.assertEqualDiff(
 
1606
            'i 1\n'
 
1607
            '<inventory format="10" revision_id="a@cset-0-2b">\n'
 
1608
            '\n'
 
1609
            'c 0 1 1 2\n'
 
1610
            'i 1\n'
 
1611
            '<file file_id="file2-id" name="other-file" parent_id="root-id"'
 
1612
                ' revision="a@cset-0-2b"'
 
1613
                ' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
 
1614
                ' text_size="14" />\n'
 
1615
            '\n'
 
1616
            'c 0 3 4 1\n', bytes_2b)
 
1617
        bytes_3 = records[2][0]
 
1618
        self.assertEqualDiff(
 
1619
            'i 1\n'
 
1620
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1621
            '\n'
 
1622
            'c 0 1 1 2\n'
 
1623
            'c 1 3 3 2\n', bytes_3)
 
1624
 
 
1625
    def test_creating_bundle_preserves_chk_pages(self):
 
1626
        self.make_merged_branch()
 
1627
        target = self.b1.bzrdir.sprout('target',
 
1628
                                       revision_id='a@cset-0-2a').open_branch()
 
1629
        bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
 
1630
                                                      'a@cset-0-3')
 
1631
        self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
 
1632
        bundle = read_bundle(bundle_txt)
 
1633
        target.lock_write()
 
1634
        self.addCleanup(target.unlock)
 
1635
        install_bundle(target.repository, bundle)
 
1636
        inv1 = self.b1.repository.inventories.get_record_stream([
 
1637
            ('a@cset-0-3',)], 'unordered',
 
1638
            True).next().get_bytes_as('fulltext')
 
1639
        inv2 = target.repository.inventories.get_record_stream([
 
1640
            ('a@cset-0-3',)], 'unordered',
 
1641
            True).next().get_bytes_as('fulltext')
 
1642
        self.assertEqualDiff(inv1, inv2)
 
1643
 
 
1644
 
1411
1645
class MungedBundleTester(object):
1412
1646
 
1413
1647
    def build_test_bundle(self):
1460
1694
        self.check_valid(bundle)
1461
1695
 
1462
1696
 
1463
 
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
 
1697
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
1464
1698
 
1465
1699
    format = '0.9'
1466
1700
 
1498
1732
        self.check_valid(bundle)
1499
1733
 
1500
1734
 
1501
 
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
 
1735
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
1502
1736
 
1503
1737
    format = '4'
1504
1738
 
1505
1739
 
1506
 
class TestBundleWriterReader(TestCase):
 
1740
class TestBundleWriterReader(tests.TestCase):
1507
1741
 
1508
1742
    def test_roundtrip_record(self):
1509
1743
        fileobj = StringIO()
1571
1805
        record = record_iter.next()
1572
1806
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1573
1807
            'info', None, None), record)
1574
 
        self.assertRaises(BadBundle, record_iter.next)
1575
 
 
1576
 
 
1577
 
class TestReadMergeableFromUrl(TestCaseWithTransport):
 
1808
        self.assertRaises(errors.BadBundle, record_iter.next)
 
1809
 
 
1810
 
 
1811
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
1578
1812
 
1579
1813
    def test_read_mergeable_skips_local(self):
1580
1814
        """A local bundle named like the URL should not be read.
1586
1820
            def look_up(self, name, url):
1587
1821
                return 'source'
1588
1822
        directories.register('foo:', FooService, 'Testing directory service')
1589
 
        self.addCleanup(lambda: directories.remove('foo:'))
 
1823
        self.addCleanup(directories.remove, 'foo:')
1590
1824
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1591
1825
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1592
1826
                          'foo:bar')
 
1827
 
 
1828
    def test_infinite_redirects_are_not_a_bundle(self):
 
1829
        """If a URL causes TooManyRedirections then NotABundle is raised.
 
1830
        """
 
1831
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
 
1832
        server = RedirectingMemoryServer()
 
1833
        self.start_server(server)
 
1834
        url = server.get_url() + 'infinite-loop'
 
1835
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
 
1836
 
 
1837
    def test_smart_server_connection_reset(self):
 
1838
        """If a smart server connection fails during the attempt to read a
 
1839
        bundle, then the ConnectionReset error should be propagated.
 
1840
        """
 
1841
        # Instantiate a server that will provoke a ConnectionReset
 
1842
        sock_server = _DisconnectingTCPServer()
 
1843
        self.start_server(sock_server)
 
1844
        # We don't really care what the url is since the server will close the
 
1845
        # connection without interpreting it
 
1846
        url = sock_server.get_url()
 
1847
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
 
1848
 
 
1849
 
 
1850
class _DisconnectingTCPServer(object):
 
1851
    """A TCP server that immediately closes any connection made to it."""
 
1852
 
 
1853
    def start_server(self):
 
1854
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
1855
        self.sock.bind(('127.0.0.1', 0))
 
1856
        self.sock.listen(1)
 
1857
        self.port = self.sock.getsockname()[1]
 
1858
        self.thread = threading.Thread(
 
1859
            name='%s (port %d)' % (self.__class__.__name__, self.port),
 
1860
            target=self.accept_and_close)
 
1861
        self.thread.start()
 
1862
 
 
1863
    def accept_and_close(self):
 
1864
        conn, addr = self.sock.accept()
 
1865
        conn.shutdown(socket.SHUT_RDWR)
 
1866
        conn.close()
 
1867
 
 
1868
    def get_url(self):
 
1869
        return 'bzr://127.0.0.1:%d/' % (self.port,)
 
1870
 
 
1871
    def stop_server(self):
 
1872
        try:
 
1873
            # make sure the thread dies by connecting to the listening socket,
 
1874
            # just in case the test failed to do so.
 
1875
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
1876
            conn.connect(self.sock.getsockname())
 
1877
            conn.close()
 
1878
        except socket.error:
 
1879
            pass
 
1880
        self.sock.close()
 
1881
        self.thread.join()