~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

Implement version 3 of the network protocol. (Andrew Bennetts)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2013, 2016 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
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
from cStringIO import StringIO
18
18
import os
19
 
import SocketServer
20
19
import sys
 
20
import tempfile
21
21
 
22
22
from bzrlib import (
23
23
    bzrdir,
24
 
    diff,
25
24
    errors,
26
25
    inventory,
27
 
    merge,
28
 
    osutils,
 
26
    repository,
29
27
    revision as _mod_revision,
30
 
    tests,
31
28
    treebuilder,
32
29
    )
 
30
from bzrlib.bzrdir import BzrDir
33
31
from bzrlib.bundle import read_mergeable_from_url
34
32
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
35
33
from bzrlib.bundle.bundle_data import BundleTree
38
36
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
39
37
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
40
38
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
 
39
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
41
44
from bzrlib.repofmt import knitrepo
 
45
from bzrlib.osutils import sha_file, sha_string
42
46
from bzrlib.tests import (
43
 
    features,
 
47
    SymlinkFeature,
 
48
    TestCase,
 
49
    TestCaseInTempDir,
 
50
    TestCaseWithTransport,
 
51
    TestSkipped,
 
52
    test_read_bundle,
44
53
    test_commit,
45
 
    test_read_bundle,
46
 
    test_server,
47
54
    )
48
55
from bzrlib.transform import TreeTransform
49
56
 
50
57
 
51
 
def get_text(vf, key):
52
 
    """Get the fulltext for a given revision id that is present in the vf"""
53
 
    stream = vf.get_record_stream([key], 'unordered', True)
54
 
    record = stream.next()
55
 
    return record.get_bytes_as('fulltext')
56
 
 
57
 
 
58
 
def get_inventory_text(repo, revision_id):
59
 
    """Get the fulltext for the inventory at revision id"""
60
 
    repo.lock_read()
61
 
    try:
62
 
        return get_text(repo.inventories, (revision_id,))
63
 
    finally:
64
 
        repo.unlock()
65
 
 
66
 
 
67
58
class MockTree(object):
68
 
 
69
59
    def __init__(self):
70
60
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
71
61
        object.__init__(self)
75
65
        self.root = InventoryDirectory(ROOT_ID, '', None)
76
66
 
77
67
    inventory = property(lambda x:x)
78
 
    root_inventory = property(lambda x:x)
79
 
 
80
 
    def get_root_id(self):
81
 
        return self.root.file_id
82
 
 
83
 
    def all_file_ids(self):
84
 
        return set(self.paths.keys())
85
 
 
86
 
    def is_executable(self, file_id):
87
 
        # Not all the files are executable.
88
 
        return False
 
68
 
 
69
    def __iter__(self):
 
70
        return self.paths.iterkeys()
89
71
 
90
72
    def __getitem__(self, file_id):
91
73
        if file_id == self.root.file_id:
103
85
        for path, file_id in self.ids.iteritems():
104
86
            yield path, self[file_id]
105
87
 
106
 
    def kind(self, file_id):
 
88
    def get_file_kind(self, file_id):
107
89
        if file_id in self.contents:
108
90
            kind = 'file'
109
91
        else:
111
93
        return kind
112
94
 
113
95
    def make_entry(self, file_id, path):
114
 
        from bzrlib.inventory import (InventoryFile , InventoryDirectory,
115
 
            InventoryLink)
 
96
        from bzrlib.inventory import (InventoryEntry, InventoryFile
 
97
                                    , InventoryDirectory, InventoryLink)
116
98
        name = os.path.basename(path)
117
 
        kind = self.kind(file_id)
 
99
        kind = self.get_file_kind(file_id)
118
100
        parent_id = self.parent_id(file_id)
119
101
        text_sha_1, text_size = self.contents_stats(file_id)
120
102
        if kind == 'directory':
121
103
            ie = InventoryDirectory(file_id, name, parent_id)
122
104
        elif kind == 'file':
123
105
            ie = InventoryFile(file_id, name, parent_id)
124
 
            ie.text_sha1 = text_sha_1
125
 
            ie.text_size = text_size
126
106
        elif kind == 'symlink':
127
107
            ie = InventoryLink(file_id, name, parent_id)
128
108
        else:
129
 
            raise errors.BzrError('unknown kind %r' % kind)
 
109
            raise BzrError('unknown kind %r' % kind)
 
110
        ie.text_sha1 = text_sha_1
 
111
        ie.text_size = text_size
130
112
        return ie
131
113
 
132
114
    def add_dir(self, file_id, path):
133
115
        self.paths[file_id] = path
134
116
        self.ids[path] = file_id
135
 
 
 
117
    
136
118
    def add_file(self, file_id, path, contents):
137
119
        self.add_dir(file_id, path)
138
120
        self.contents[file_id] = contents
152
134
        result.seek(0,0)
153
135
        return result
154
136
 
155
 
    def get_file_revision(self, file_id):
156
 
        return self.inventory[file_id].revision
157
 
 
158
 
    def get_file_size(self, file_id):
159
 
        return self.inventory[file_id].text_size
160
 
 
161
 
    def get_file_sha1(self, file_id):
162
 
        return self.inventory[file_id].text_sha1
163
 
 
164
137
    def contents_stats(self, file_id):
165
138
        if file_id not in self.contents:
166
139
            return None, None
167
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
140
        text_sha1 = sha_file(self.get_file(file_id))
168
141
        return text_sha1, len(self.contents[file_id])
169
142
 
170
143
 
171
 
class BTreeTester(tests.TestCase):
 
144
class BTreeTester(TestCase):
172
145
    """A simple unittest tester for the BundleTree class."""
173
146
 
174
147
    def make_tree_1(self):
178
151
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
179
152
        mtree.add_dir("d", "grandparent/alt_parent")
180
153
        return BundleTree(mtree, ''), mtree
181
 
 
 
154
        
182
155
    def test_renames(self):
183
156
        """Ensure that file renames have the proper effect on children"""
184
157
        btree = self.make_tree_1()[0]
185
158
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
186
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
159
        self.assertEqual(btree.old_path("grandparent/parent"), 
187
160
                         "grandparent/parent")
188
161
        self.assertEqual(btree.old_path("grandparent/parent/file"),
189
162
                         "grandparent/parent/file")
229
202
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
230
203
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
231
204
 
232
 
        btree.note_rename("grandparent/parent/file",
 
205
        btree.note_rename("grandparent/parent/file", 
233
206
                          "grandparent2/parent2/file2")
234
207
        self.assertEqual(btree.id2path("a"), "grandparent2")
235
208
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
244
217
    def test_moves(self):
245
218
        """Ensure that file moves have the proper effect on children"""
246
219
        btree = self.make_tree_1()[0]
247
 
        btree.note_rename("grandparent/parent/file",
 
220
        btree.note_rename("grandparent/parent/file", 
248
221
                          "grandparent/alt_parent/file")
249
222
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
250
223
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
252
225
 
253
226
    def unified_diff(self, old, new):
254
227
        out = StringIO()
255
 
        diff.internal_diff("old", old, "new", new, out)
 
228
        internal_diff("old", old, "new", new, out)
256
229
        out.seek(0,0)
257
230
        return out.read()
258
231
 
259
232
    def make_tree_2(self):
260
233
        btree = self.make_tree_1()[0]
261
 
        btree.note_rename("grandparent/parent/file",
 
234
        btree.note_rename("grandparent/parent/file", 
262
235
                          "grandparent/alt_parent/file")
263
236
        self.assertTrue(btree.id2path("e") is None)
264
237
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
293
266
    def make_tree_3(self):
294
267
        btree, mtree = self.make_tree_1()
295
268
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
296
 
        btree.note_rename("grandparent/parent/file",
 
269
        btree.note_rename("grandparent/parent/file", 
297
270
                          "grandparent/alt_parent/file")
298
 
        btree.note_rename("grandparent/parent/topping",
 
271
        btree.note_rename("grandparent/parent/topping", 
299
272
                          "grandparent/alt_parent/stopping")
300
273
        return btree
301
274
 
329
302
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
330
303
 
331
304
    def sorted_ids(self, tree):
332
 
        ids = list(tree.all_file_ids())
 
305
        ids = list(tree)
333
306
        ids.sort()
334
307
        return ids
335
308
 
340
313
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
341
314
        btree.note_deletion("grandparent/parent/file")
342
315
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
343
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
316
        btree.note_last_changed("grandparent/alt_parent/fool", 
344
317
                                "revisionidiguess")
345
318
        self.assertEqual(self.sorted_ids(btree),
346
319
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
347
320
 
348
321
 
349
 
class BundleTester1(tests.TestCaseWithTransport):
 
322
class BundleTester1(TestCaseWithTransport):
350
323
 
351
324
    def test_mismatched_bundle(self):
352
325
        format = bzrdir.BzrDirMetaFormat1()
353
326
        format.repository_format = knitrepo.RepositoryFormatKnit3()
354
327
        serializer = BundleSerializerV08('0.8')
355
328
        b = self.make_branch('.', format=format)
356
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
329
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
357
330
                          b.repository, [], {}, StringIO())
358
331
 
359
332
    def test_matched_bundle(self):
379
352
        format = bzrdir.BzrDirMetaFormat1()
380
353
        format.repository_format = knitrepo.RepositoryFormatKnit1()
381
354
        target = self.make_branch('target', format=format)
382
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
355
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
383
356
                          target.repository, read_bundle(text))
384
357
 
385
358
 
393
366
    def make_branch_and_tree(self, path, format=None):
394
367
        if format is None:
395
368
            format = self.bzrdir_format()
396
 
        return tests.TestCaseWithTransport.make_branch_and_tree(
397
 
            self, path, format)
 
369
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
398
370
 
399
371
    def make_branch(self, path, format=None):
400
372
        if format is None:
401
373
            format = self.bzrdir_format()
402
 
        return tests.TestCaseWithTransport.make_branch(self, path, format)
 
374
        return TestCaseWithTransport.make_branch(self, path, format)
403
375
 
404
376
    def create_bundle_text(self, base_rev_id, rev_id):
405
377
        bundle_txt = StringIO()
406
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
378
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
407
379
                               bundle_txt, format=self.format)
408
380
        bundle_txt.seek(0)
409
 
        self.assertEqual(bundle_txt.readline(),
 
381
        self.assertEqual(bundle_txt.readline(), 
410
382
                         '# Bazaar revision bundle v%s\n' % self.format)
411
383
        self.assertEqual(bundle_txt.readline(), '#\n')
412
384
 
420
392
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
421
393
        Make sure that the text generated is valid, and that it
422
394
        can be applied against the base, and generate the same information.
423
 
 
424
 
        :return: The in-memory bundle
 
395
        
 
396
        :return: The in-memory bundle 
425
397
        """
426
398
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
427
399
 
428
 
        # This should also validate the generated bundle
 
400
        # This should also validate the generated bundle 
429
401
        bundle = read_bundle(bundle_txt)
430
402
        repository = self.b1.repository
431
403
        for bundle_rev in bundle.real_revisions:
435
407
            # it
436
408
            branch_rev = repository.get_revision(bundle_rev.revision_id)
437
409
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
438
 
                      'timestamp', 'timezone', 'message', 'committer',
 
410
                      'timestamp', 'timezone', 'message', 'committer', 
439
411
                      'parent_ids', 'properties'):
440
 
                self.assertEqual(getattr(branch_rev, a),
 
412
                self.assertEqual(getattr(branch_rev, a), 
441
413
                                 getattr(bundle_rev, a))
442
 
            self.assertEqual(len(branch_rev.parent_ids),
 
414
            self.assertEqual(len(branch_rev.parent_ids), 
443
415
                             len(bundle_rev.parent_ids))
444
 
        self.assertEqual(rev_ids,
 
416
        self.assertEqual(rev_ids, 
445
417
                         [r.revision_id for r in bundle.real_revisions])
446
418
        self.valid_apply_bundle(base_rev_id, bundle,
447
419
                                   checkout_dir=checkout_dir)
451
423
    def get_invalid_bundle(self, base_rev_id, rev_id):
452
424
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
453
425
        Munge the text so that it's invalid.
454
 
 
 
426
        
455
427
        :return: The in-memory bundle
456
428
        """
457
429
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
458
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
430
        new_text = bundle_txt.getvalue().replace('executable:no', 
459
431
                                               'executable:yes')
460
432
        bundle_txt = StringIO(new_text)
461
433
        bundle = read_bundle(bundle_txt)
462
434
        self.valid_apply_bundle(base_rev_id, bundle)
463
 
        return bundle
 
435
        return bundle 
464
436
 
465
437
    def test_non_bundle(self):
466
 
        self.assertRaises(errors.NotABundle,
467
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
438
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
468
439
 
469
440
    def test_malformed(self):
470
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
441
        self.assertRaises(BadBundle, read_bundle, 
471
442
                          StringIO('# Bazaar revision bundle v'))
472
443
 
473
444
    def test_crlf_bundle(self):
474
445
        try:
475
446
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
476
 
        except errors.BadBundle:
 
447
        except BadBundle:
477
448
            # It is currently permitted for bundles with crlf line endings to
478
449
            # make read_bundle raise a BadBundle, but this should be fixed.
479
450
            # Anything else, especially NotABundle, is an error.
484
455
        """
485
456
 
486
457
        if checkout_dir is None:
487
 
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
 
458
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
488
459
        else:
489
460
            if not os.path.exists(checkout_dir):
490
461
                os.mkdir(checkout_dir)
498
469
        for ancestor in ancestors:
499
470
            old = self.b1.repository.revision_tree(ancestor)
500
471
            new = tree.branch.repository.revision_tree(ancestor)
501
 
            old.lock_read()
502
 
            new.lock_read()
503
 
            try:
504
 
                # Check that there aren't any inventory level changes
505
 
                delta = new.changes_from(old)
506
 
                self.assertFalse(delta.has_changed(),
507
 
                                 'Revision %s not copied correctly.'
508
 
                                 % (ancestor,))
509
 
 
510
 
                # Now check that the file contents are all correct
511
 
                for inventory_id in old.all_file_ids():
512
 
                    try:
513
 
                        old_file = old.get_file(inventory_id)
514
 
                    except errors.NoSuchFile:
515
 
                        continue
516
 
                    if old_file is None:
517
 
                        continue
518
 
                    self.assertEqual(old_file.read(),
519
 
                                     new.get_file(inventory_id).read())
520
 
            finally:
521
 
                new.unlock()
522
 
                old.unlock()
 
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())
523
489
        if not _mod_revision.is_null(rev_id):
524
 
            tree.branch.generate_revision_history(rev_id)
 
490
            rh = self.b1.revision_history()
 
491
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
525
492
            tree.update()
526
493
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
527
494
            self.assertFalse(delta.has_changed(),
545
512
        original_parents = to_tree.get_parent_ids()
546
513
        self.assertIs(repository.has_revision(base_rev_id), True)
547
514
        for rev in info.real_revisions:
548
 
            self.assertTrue(not repository.has_revision(rev.revision_id),
549
 
                            'Revision {%s} present before applying bundle'
550
 
                            % rev.revision_id)
551
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
515
            self.assert_(not repository.has_revision(rev.revision_id),
 
516
                'Revision {%s} present before applying bundle' 
 
517
                % rev.revision_id)
 
518
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
552
519
 
553
520
        for rev in info.real_revisions:
554
 
            self.assertTrue(repository.has_revision(rev.revision_id),
555
 
                            'Missing revision {%s} after applying bundle'
556
 
                            % rev.revision_id)
 
521
            self.assert_(repository.has_revision(rev.revision_id),
 
522
                'Missing revision {%s} after applying bundle' 
 
523
                % rev.revision_id)
557
524
 
558
 
        self.assertTrue(to_tree.branch.repository.has_revision(info.target))
 
525
        self.assert_(to_tree.branch.repository.has_revision(info.target))
559
526
        # Do we also want to verify that all the texts have been added?
560
527
 
561
528
        self.assertEqual(original_parents + [info.target],
562
 
                         to_tree.get_parent_ids())
 
529
            to_tree.get_parent_ids())
563
530
 
564
531
        rev = info.real_revisions[-1]
565
532
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
566
533
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
567
 
 
 
534
        
568
535
        # TODO: make sure the target tree is identical to base tree
569
536
        #       we might also check the working tree.
570
537
 
589
556
        self.tree1 = self.make_branch_and_tree('b1')
590
557
        self.b1 = self.tree1.branch
591
558
 
592
 
        self.build_tree_contents([('b1/one', 'one\n')])
593
 
        self.tree1.add('one', 'one-id')
594
 
        self.tree1.set_root_id('root-id')
 
559
        open('b1/one', 'wb').write('one\n')
 
560
        self.tree1.add('one')
595
561
        self.tree1.commit('add one', rev_id='a@cset-0-1')
596
562
 
597
563
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
608
574
                , 'b1/sub/sub/'
609
575
                , 'b1/sub/sub/nonempty.txt'
610
576
                ])
611
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
612
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
577
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
578
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
613
579
        tt = TreeTransform(self.tree1)
614
580
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
615
581
        tt.apply()
631
597
 
632
598
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
633
599
 
634
 
        # Check a rollup bundle
 
600
        # Check a rollup bundle 
635
601
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
636
602
 
637
603
        # Now delete entries
645
611
        tt.set_executability(False, trans_id)
646
612
        tt.apply()
647
613
        self.tree1.commit('removed', rev_id='a@cset-0-3')
648
 
 
 
614
        
649
615
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
650
 
        self.assertRaises((errors.TestamentMismatch,
651
 
            errors.VersionedFileInvalidChecksum,
652
 
            errors.BadBundle), self.get_invalid_bundle,
 
616
        self.assertRaises((TestamentMismatch,
 
617
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
653
618
            'a@cset-0-2', 'a@cset-0-3')
654
 
        # Check a rollup bundle
 
619
        # Check a rollup bundle 
655
620
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
656
621
 
657
622
        # Now move the directory
659
624
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
660
625
 
661
626
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
662
 
        # Check a rollup bundle
 
627
        # Check a rollup bundle 
663
628
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
664
629
 
665
630
        # Modified files
666
 
        with open('b1/sub/dir/WithCaps.txt', 'ab') as f: f.write('\nAdding some text\n')
667
 
        with open('b1/sub/dir/ pre space', 'ab') as f: f.write(
 
631
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
 
632
        open('b1/sub/dir/ pre space', 'ab').write(
668
633
             '\r\nAdding some\r\nDOS format lines\r\n')
669
 
        with open('b1/sub/dir/nolastnewline.txt', 'ab') as f: f.write('\n')
670
 
        self.tree1.rename_one('sub/dir/ pre space',
 
634
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
 
635
        self.tree1.rename_one('sub/dir/ pre space', 
671
636
                              'sub/ start space')
672
637
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
673
638
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
679
644
                          verbose=False)
680
645
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
681
646
        other = self.get_checkout('a@cset-0-5')
682
 
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
683
 
                                       'a@cset-0-5')
684
 
        tree2_inv = get_inventory_text(other.branch.repository,
685
 
                                       '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')
686
650
        self.assertEqualDiff(tree1_inv, tree2_inv)
687
651
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
688
652
        other.commit('rename file', rev_id='a@cset-0-6b')
691
655
                          verbose=False)
692
656
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
693
657
 
694
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
695
 
        link_id = 'link-1'
696
 
 
697
 
        self.requireFeature(features.SymlinkFeature)
 
658
    def test_symlink_bundle(self):
 
659
        self.requireFeature(SymlinkFeature)
698
660
        self.tree1 = self.make_branch_and_tree('b1')
699
661
        self.b1 = self.tree1.branch
700
 
 
701
662
        tt = TreeTransform(self.tree1)
702
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
663
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
703
664
        tt.apply()
704
665
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
705
 
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
706
 
        if getattr(bundle ,'revision_tree', None) is not None:
707
 
            # Not all bundle formats supports revision_tree
708
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
709
 
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
710
 
 
 
666
        self.get_valid_bundle('null:', 'l@cset-0-1')
711
667
        tt = TreeTransform(self.tree1)
712
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
668
        trans_id = tt.trans_id_tree_file_id('link-1')
713
669
        tt.adjust_path('link2', tt.root, trans_id)
714
670
        tt.delete_contents(trans_id)
715
 
        tt.create_symlink(new_link_target, trans_id)
 
671
        tt.create_symlink('mars', trans_id)
716
672
        tt.apply()
717
673
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
718
 
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
719
 
        if getattr(bundle ,'revision_tree', None) is not None:
720
 
            # Not all bundle formats supports revision_tree
721
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
722
 
            self.assertEqual(new_link_target,
723
 
                             bund_tree.get_symlink_target(link_id))
724
 
 
 
674
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
725
675
        tt = TreeTransform(self.tree1)
726
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
676
        trans_id = tt.trans_id_tree_file_id('link-1')
727
677
        tt.delete_contents(trans_id)
728
678
        tt.create_symlink('jupiter', trans_id)
729
679
        tt.apply()
730
680
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
731
 
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
732
 
 
 
681
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
733
682
        tt = TreeTransform(self.tree1)
734
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
683
        trans_id = tt.trans_id_tree_file_id('link-1')
735
684
        tt.delete_contents(trans_id)
736
685
        tt.apply()
737
686
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
738
 
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
739
 
 
740
 
    def test_symlink_bundle(self):
741
 
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
742
 
 
743
 
    def test_unicode_symlink_bundle(self):
744
 
        self.requireFeature(features.UnicodeFilenameFeature)
745
 
        self._test_symlink_bundle(u'\N{Euro Sign}link',
746
 
                                  u'bar/\N{Euro Sign}foo',
747
 
                                  u'mars\N{Euro Sign}')
 
687
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
748
688
 
749
689
    def test_binary_bundle(self):
750
690
        self.tree1 = self.make_branch_and_tree('b1')
751
691
        self.b1 = self.tree1.branch
752
692
        tt = TreeTransform(self.tree1)
753
 
 
 
693
        
754
694
        # Add
755
695
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
756
696
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
821
761
        self.tree1 = self.make_branch_and_tree('b1')
822
762
        self.b1 = self.tree1.branch
823
763
 
824
 
        with open('b1/one', 'wb') as f: f.write('one\n')
 
764
        open('b1/one', 'wb').write('one\n')
825
765
        self.tree1.add('one')
826
766
        self.tree1.commit('add file', rev_id='a@cset-0-1')
827
 
        with open('b1/one', 'wb') as f: f.write('two\n')
 
767
        open('b1/one', 'wb').write('two\n')
828
768
        self.tree1.commit('modify', rev_id='a@cset-0-2')
829
 
        with open('b1/one', 'wb') as f: f.write('three\n')
 
769
        open('b1/one', 'wb').write('three\n')
830
770
        self.tree1.commit('modify', rev_id='a@cset-0-3')
831
771
        bundle_file = StringIO()
832
772
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
848
788
        return bundle_file.getvalue()
849
789
 
850
790
    def test_unicode_bundle(self):
851
 
        self.requireFeature(features.UnicodeFilenameFeature)
852
791
        # Handle international characters
853
792
        os.mkdir('b1')
854
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
793
        try:
 
794
            f = open(u'b1/with Dod\xe9', 'wb')
 
795
        except UnicodeEncodeError:
 
796
            raise TestSkipped("Filesystem doesn't support unicode")
855
797
 
856
798
        self.tree1 = self.make_branch_and_tree('b1')
857
799
        self.b1 = self.tree1.branch
861
803
            u'William Dod\xe9\n').encode('utf-8'))
862
804
        f.close()
863
805
 
864
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
806
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
865
807
        self.tree1.commit(u'i18n commit from William Dod\xe9',
866
808
                          rev_id='i18n-1', committer=u'William Dod\xe9')
867
809
 
 
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
 
868
824
        # Add
869
825
        bundle = self.get_valid_bundle('null:', 'i18n-1')
870
826
 
871
827
        # Modified
872
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
828
        f = open(u'b1/with Dod\xe9', 'wb')
873
829
        f.write(u'Modified \xb5\n'.encode('utf8'))
874
830
        f.close()
875
831
        self.tree1.commit(u'modified', rev_id='i18n-2')
876
832
 
877
833
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
878
 
 
 
834
        
879
835
        # Renamed
880
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
836
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
881
837
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
882
838
                          committer=u'Erik B\xe5gfors')
883
839
 
884
840
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
885
841
 
886
842
        # Removed
887
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
843
        self.tree1.remove([u'B\xe5gfors'])
888
844
        self.tree1.commit(u'removed', rev_id='i18n-4')
889
845
 
890
846
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
895
851
 
896
852
    def test_whitespace_bundle(self):
897
853
        if sys.platform in ('win32', 'cygwin'):
898
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
899
 
                                    ' with tabs or trailing spaces')
 
854
            raise TestSkipped('Windows doesn\'t support filenames'
 
855
                              ' with tabs or trailing spaces')
900
856
        self.tree1 = self.make_branch_and_tree('b1')
901
857
        self.b1 = self.tree1.branch
902
858
 
911
867
        bundle = self.get_valid_bundle('null:', 'white-1')
912
868
 
913
869
        # Modified
914
 
        with open('b1/trailing space ', 'ab') as f: f.write('add some text\n')
 
870
        open('b1/trailing space ', 'ab').write('add some text\n')
915
871
        self.tree1.commit('add text', rev_id='white-2')
916
872
 
917
873
        bundle = self.get_valid_bundle('white-1', 'white-2')
927
883
        self.tree1.commit('removed', rev_id='white-4')
928
884
 
929
885
        bundle = self.get_valid_bundle('white-3', 'white-4')
930
 
 
 
886
        
931
887
        # Now test a complet roll-up
932
888
        bundle = self.get_valid_bundle('null:', 'white-4')
933
889
 
946
902
                          timezone=19800, timestamp=1152544886.0)
947
903
 
948
904
        bundle = self.get_valid_bundle('null:', 'tz-1')
949
 
 
 
905
        
950
906
        rev = bundle.revisions[0]
951
907
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
952
908
        self.assertEqual(19800, rev.timezone)
959
915
        self.tree1.commit('message', rev_id='revid1')
960
916
        bundle = self.get_valid_bundle('null:', 'revid1')
961
917
        tree = self.get_bundle_tree(bundle, 'revid1')
962
 
        root_revision = tree.get_file_revision(tree.get_root_id())
963
 
        self.assertEqual('revid1', root_revision)
 
918
        self.assertEqual('revid1', tree.inventory.root.revision)
964
919
 
965
920
    def test_install_revisions(self):
966
921
        self.tree1 = self.make_branch_and_tree('b1')
1055
1010
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1056
1011
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1057
1012
        bundle.install_revisions(repo)
1058
 
        inv_text = repo._get_inventory_xml('rev2')
 
1013
        inv_text = repo.get_inventory_xml('rev2')
1059
1014
        self.assertNotContainsRe(inv_text, 'format="5"')
1060
1015
        self.assertContainsRe(inv_text, 'format="7"')
1061
1016
 
1072
1027
        repo = self.make_repo_with_installed_revisions()
1073
1028
        inv = repo.get_inventory('rev2')
1074
1029
        self.assertEqual('rev2', inv.root.revision)
1075
 
        root_id = inv.root.file_id
1076
 
        repo.lock_read()
1077
 
        self.addCleanup(repo.unlock)
1078
 
        self.assertEqual({(root_id, 'rev1'):(),
1079
 
            (root_id, 'rev2'):((root_id, 'rev1'),)},
1080
 
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
 
1030
        root_vf = repo.weave_store.get_weave(inv.root.file_id,
 
1031
                                             repo.get_transaction())
 
1032
        self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
1081
1033
 
1082
1034
    def test_inv_hash_across_serializers(self):
1083
1035
        repo = self.make_repo_with_installed_revisions()
1084
 
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
1085
 
        xml = repo._get_inventory_xml('rev2')
1086
 
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
 
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)
1087
1039
 
1088
1040
    def test_across_models_incompatible(self):
1089
1041
        tree = self.make_simple_tree('dirstate-with-subtree')
1092
1044
        try:
1093
1045
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1094
1046
        except errors.IncompatibleBundleFormat:
1095
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1047
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1096
1048
        repo = self.make_repository('repo', format='knit')
1097
1049
        bundle.install_revisions(repo)
1098
1050
 
1119
1071
        try:
1120
1072
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1121
1073
        except errors.IncompatibleBundleFormat:
1122
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1074
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1123
1075
        if isinstance(bundle, v09.BundleInfo09):
1124
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
 
1076
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
1125
1077
        repo = self.make_repository('repo', format='knit')
1126
1078
        self.assertRaises(errors.IncompatibleRevision,
1127
1079
                          bundle.install_revisions, repo)
1134
1086
        try:
1135
1087
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1136
1088
        except ValueError:
1137
 
            raise tests.TestSkipped(
1138
 
                "Repository doesn't support revision ids with slashes")
 
1089
            raise TestSkipped("Repository doesn't support revision ids with"
 
1090
                              " slashes")
1139
1091
        bundle = self.get_valid_bundle('null:', 'rev/id')
1140
1092
 
1141
1093
    def test_skip_file(self):
1157
1109
        self.tree1.commit('rev3', rev_id='rev3')
1158
1110
        bundle = self.get_valid_bundle('reva', 'rev3')
1159
1111
        if getattr(bundle, 'get_bundle_reader', None) is None:
1160
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
 
1112
            raise TestSkipped('Bundle format cannot provide reader')
1161
1113
        # be sure that file1 comes before file2
1162
1114
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1163
1115
            if f == 'file3-id':
1166
1118
        bundle.install_revisions(target.branch.repository)
1167
1119
 
1168
1120
 
1169
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1121
class V08BundleTester(BundleTester, TestCaseWithTransport):
1170
1122
 
1171
1123
    format = '0.8'
1172
1124
 
1305
1257
        return format
1306
1258
 
1307
1259
 
1308
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1260
class V4BundleTester(BundleTester, TestCaseWithTransport):
1309
1261
 
1310
1262
    format = '4'
1311
1263
 
1313
1265
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1314
1266
        Make sure that the text generated is valid, and that it
1315
1267
        can be applied against the base, and generate the same information.
1316
 
 
1317
 
        :return: The in-memory bundle
 
1268
        
 
1269
        :return: The in-memory bundle 
1318
1270
        """
1319
1271
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1320
1272
 
1321
 
        # This should also validate the generated bundle
 
1273
        # This should also validate the generated bundle 
1322
1274
        bundle = read_bundle(bundle_txt)
1323
1275
        repository = self.b1.repository
1324
1276
        for bundle_rev in bundle.real_revisions:
1328
1280
            # it
1329
1281
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1330
1282
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1331
 
                      'timestamp', 'timezone', 'message', 'committer',
 
1283
                      'timestamp', 'timezone', 'message', 'committer', 
1332
1284
                      'parent_ids', 'properties'):
1333
 
                self.assertEqual(getattr(branch_rev, a),
 
1285
                self.assertEqual(getattr(branch_rev, a), 
1334
1286
                                 getattr(bundle_rev, a))
1335
 
            self.assertEqual(len(branch_rev.parent_ids),
 
1287
            self.assertEqual(len(branch_rev.parent_ids), 
1336
1288
                             len(bundle_rev.parent_ids))
1337
1289
        self.assertEqual(set(rev_ids),
1338
1290
                         set([r.revision_id for r in bundle.real_revisions]))
1352
1304
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1353
1305
        new_text = new_text.replace('<file file_id="exe-1"',
1354
1306
                                    '<file executable="y" file_id="exe-1"')
1355
 
        new_text = new_text.replace('B260', 'B275')
 
1307
        new_text = new_text.replace('B222', 'B237')
1356
1308
        bundle_txt = StringIO()
1357
1309
        bundle_txt.write(serializer._get_bundle_header('4'))
1358
1310
        bundle_txt.write('\n')
1364
1316
 
1365
1317
    def create_bundle_text(self, base_rev_id, rev_id):
1366
1318
        bundle_txt = StringIO()
1367
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
1319
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
1368
1320
                               bundle_txt, format=self.format)
1369
1321
        bundle_txt.seek(0)
1370
 
        self.assertEqual(bundle_txt.readline(),
 
1322
        self.assertEqual(bundle_txt.readline(), 
1371
1323
                         '# Bazaar revision bundle v%s\n' % self.format)
1372
1324
        self.assertEqual(bundle_txt.readline(), '#\n')
1373
1325
        rev = self.b1.repository.get_revision(rev_id)
1393
1345
        tree2 = self.make_branch_and_tree('target')
1394
1346
        target_repo = tree2.branch.repository
1395
1347
        install_bundle(target_repo, serializer.read(s))
1396
 
        target_repo.lock_read()
1397
 
        self.addCleanup(target_repo.unlock)
1398
 
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1399
 
        repo_texts = dict((i, ''.join(content)) for i, content
1400
 
                          in target_repo.iter_files_bytes(
1401
 
                                [('fileid-2', 'rev1', '1'),
1402
 
                                 ('fileid-2', 'rev2', '2')]))
1403
 
        self.assertEqual({'1':'contents1\nstatic\n',
1404
 
                          '2':'contents2\nstatic\n'},
1405
 
                         repo_texts)
 
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'))
1406
1352
        rtree = target_repo.revision_tree('rev2')
1407
 
        inventory_vf = target_repo.inventories
1408
 
        # If the inventory store has a graph, it must match the revision graph.
1409
 
        self.assertSubset(
1410
 
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
1411
 
            [None, (('rev1',),)])
 
1353
        inventory_vf = target_repo.get_inventory_weave()
 
1354
        self.assertEqual({'rev2':('rev1',)},
 
1355
            inventory_vf.get_parent_map(['rev2']))
1412
1356
        self.assertEqual('changed file',
1413
1357
                         target_repo.get_revision('rev2').message)
1414
1358
 
1428
1372
        branch = tree_a.branch
1429
1373
        repo_a = branch.repository
1430
1374
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1431
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
1375
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
1432
1376
        try:
1433
1377
            from bzrlib.testament import Testament
1434
1378
            # monkey patch gpg signing mechanism
1435
1379
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1436
 
            new_config = test_commit.MustSignConfig()
1437
 
            commit.Commit(config_stack=new_config).commit(message="base",
 
1380
            new_config = test_commit.MustSignConfig(branch)
 
1381
            commit.Commit(config=new_config).commit(message="base",
1438
1382
                                                    allow_pointless=True,
1439
1383
                                                    rev_id='B',
1440
1384
                                                    working_tree=tree_a)
1458
1402
        install_bundle(repo_b, serializer.read(s))
1459
1403
 
1460
1404
 
1461
 
class V4_2aBundleTester(V4BundleTester):
 
1405
class V4WeaveBundleTester(V4BundleTester):
1462
1406
 
1463
1407
    def bzrdir_format(self):
1464
 
        return '2a'
1465
 
 
1466
 
    def get_invalid_bundle(self, base_rev_id, rev_id):
1467
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1468
 
        Munge the text so that it's invalid.
1469
 
 
1470
 
        :return: The in-memory bundle
1471
 
        """
1472
 
        from bzrlib.bundle import serializer
1473
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1474
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1475
 
        # We are going to be replacing some text to set the executable bit on a
1476
 
        # file. Make sure the text replacement actually works correctly.
1477
 
        self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
1478
 
        new_text = new_text.replace('<file file_id="exe-1"',
1479
 
                                    '<file executable="y" file_id="exe-1"')
1480
 
        new_text = new_text.replace('B244', 'B259')
1481
 
        bundle_txt = StringIO()
1482
 
        bundle_txt.write(serializer._get_bundle_header('4'))
1483
 
        bundle_txt.write('\n')
1484
 
        bundle_txt.write(new_text.encode('bz2'))
1485
 
        bundle_txt.seek(0)
1486
 
        bundle = read_bundle(bundle_txt)
1487
 
        self.valid_apply_bundle(base_rev_id, bundle)
1488
 
        return bundle
1489
 
 
1490
 
    def make_merged_branch(self):
1491
 
        builder = self.make_branch_builder('source')
1492
 
        builder.start_series()
1493
 
        builder.build_snapshot('a@cset-0-1', None, [
1494
 
            ('add', ('', 'root-id', 'directory', None)),
1495
 
            ('add', ('file', 'file-id', 'file', 'original content\n')),
1496
 
            ])
1497
 
        builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
1498
 
            ('modify', ('file-id', 'new-content\n')),
1499
 
            ])
1500
 
        builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
1501
 
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1502
 
            ])
1503
 
        builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
1504
 
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1505
 
            ])
1506
 
        builder.finish_series()
1507
 
        self.b1 = builder.get_branch()
1508
 
        self.b1.lock_read()
1509
 
        self.addCleanup(self.b1.unlock)
1510
 
 
1511
 
    def make_bundle_just_inventories(self, base_revision_id,
1512
 
                                     target_revision_id,
1513
 
                                     revision_ids):
1514
 
        sio = StringIO()
1515
 
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
1516
 
                                         self.b1.repository, sio)
1517
 
        writer.bundle.begin()
1518
 
        writer._add_inventory_mpdiffs_from_serializer(revision_ids)
1519
 
        writer.bundle.end()
1520
 
        sio.seek(0)
1521
 
        return sio
1522
 
 
1523
 
    def test_single_inventory_multiple_parents_as_xml(self):
1524
 
        self.make_merged_branch()
1525
 
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1526
 
                                                ['a@cset-0-3'])
1527
 
        reader = v4.BundleReader(sio, stream_input=False)
1528
 
        records = list(reader.iter_records())
1529
 
        self.assertEqual(1, len(records))
1530
 
        (bytes, metadata, repo_kind, revision_id,
1531
 
         file_id) = records[0]
1532
 
        self.assertIs(None, file_id)
1533
 
        self.assertEqual('a@cset-0-3', revision_id)
1534
 
        self.assertEqual('inventory', repo_kind)
1535
 
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1536
 
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1537
 
                          'storage_kind': 'mpdiff',
1538
 
                         }, metadata)
1539
 
        # We should have an mpdiff that takes some lines from both parents.
1540
 
        self.assertEqualDiff(
1541
 
            'i 1\n'
1542
 
            '<inventory format="10" revision_id="a@cset-0-3">\n'
1543
 
            '\n'
1544
 
            'c 0 1 1 2\n'
1545
 
            'c 1 3 3 2\n', bytes)
1546
 
 
1547
 
    def test_single_inv_no_parents_as_xml(self):
1548
 
        self.make_merged_branch()
1549
 
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
1550
 
                                                ['a@cset-0-1'])
1551
 
        reader = v4.BundleReader(sio, stream_input=False)
1552
 
        records = list(reader.iter_records())
1553
 
        self.assertEqual(1, len(records))
1554
 
        (bytes, metadata, repo_kind, revision_id,
1555
 
         file_id) = records[0]
1556
 
        self.assertIs(None, file_id)
1557
 
        self.assertEqual('a@cset-0-1', revision_id)
1558
 
        self.assertEqual('inventory', repo_kind)
1559
 
        self.assertEqual({'parents': [],
1560
 
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
1561
 
                          'storage_kind': 'mpdiff',
1562
 
                         }, metadata)
1563
 
        # We should have an mpdiff that takes some lines from both parents.
1564
 
        self.assertEqualDiff(
1565
 
            'i 4\n'
1566
 
            '<inventory format="10" revision_id="a@cset-0-1">\n'
1567
 
            '<directory file_id="root-id" name=""'
1568
 
                ' revision="a@cset-0-1" />\n'
1569
 
            '<file file_id="file-id" name="file" parent_id="root-id"'
1570
 
                ' revision="a@cset-0-1"'
1571
 
                ' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
1572
 
                ' text_size="17" />\n'
1573
 
            '</inventory>\n'
1574
 
            '\n', bytes)
1575
 
 
1576
 
    def test_multiple_inventories_as_xml(self):
1577
 
        self.make_merged_branch()
1578
 
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1579
 
            ['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
1580
 
        reader = v4.BundleReader(sio, stream_input=False)
1581
 
        records = list(reader.iter_records())
1582
 
        self.assertEqual(3, len(records))
1583
 
        revision_ids = [rev_id for b, m, k, rev_id, f in records]
1584
 
        self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
1585
 
                         revision_ids)
1586
 
        metadata_2a = records[0][1]
1587
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1588
 
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
1589
 
                          'storage_kind': 'mpdiff',
1590
 
                         }, metadata_2a)
1591
 
        metadata_2b = records[1][1]
1592
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1593
 
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
1594
 
                          'storage_kind': 'mpdiff',
1595
 
                         }, metadata_2b)
1596
 
        metadata_3 = records[2][1]
1597
 
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1598
 
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1599
 
                          'storage_kind': 'mpdiff',
1600
 
                         }, metadata_3)
1601
 
        bytes_2a = records[0][0]
1602
 
        self.assertEqualDiff(
1603
 
            'i 1\n'
1604
 
            '<inventory format="10" revision_id="a@cset-0-2a">\n'
1605
 
            '\n'
1606
 
            'c 0 1 1 1\n'
1607
 
            'i 1\n'
1608
 
            '<file file_id="file-id" name="file" parent_id="root-id"'
1609
 
                ' revision="a@cset-0-2a"'
1610
 
                ' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
1611
 
                ' text_size="12" />\n'
1612
 
            '\n'
1613
 
            'c 0 3 3 1\n', bytes_2a)
1614
 
        bytes_2b = records[1][0]
1615
 
        self.assertEqualDiff(
1616
 
            'i 1\n'
1617
 
            '<inventory format="10" revision_id="a@cset-0-2b">\n'
1618
 
            '\n'
1619
 
            'c 0 1 1 2\n'
1620
 
            'i 1\n'
1621
 
            '<file file_id="file2-id" name="other-file" parent_id="root-id"'
1622
 
                ' revision="a@cset-0-2b"'
1623
 
                ' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
1624
 
                ' text_size="14" />\n'
1625
 
            '\n'
1626
 
            'c 0 3 4 1\n', bytes_2b)
1627
 
        bytes_3 = records[2][0]
1628
 
        self.assertEqualDiff(
1629
 
            'i 1\n'
1630
 
            '<inventory format="10" revision_id="a@cset-0-3">\n'
1631
 
            '\n'
1632
 
            'c 0 1 1 2\n'
1633
 
            'c 1 3 3 2\n', bytes_3)
1634
 
 
1635
 
    def test_creating_bundle_preserves_chk_pages(self):
1636
 
        self.make_merged_branch()
1637
 
        target = self.b1.bzrdir.sprout('target',
1638
 
                                       revision_id='a@cset-0-2a').open_branch()
1639
 
        bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
1640
 
                                                      'a@cset-0-3')
1641
 
        self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
1642
 
        bundle = read_bundle(bundle_txt)
1643
 
        target.lock_write()
1644
 
        self.addCleanup(target.unlock)
1645
 
        install_bundle(target.repository, bundle)
1646
 
        inv1 = self.b1.repository.inventories.get_record_stream([
1647
 
            ('a@cset-0-3',)], 'unordered',
1648
 
            True).next().get_bytes_as('fulltext')
1649
 
        inv2 = target.repository.inventories.get_record_stream([
1650
 
            ('a@cset-0-3',)], 'unordered',
1651
 
            True).next().get_bytes_as('fulltext')
1652
 
        self.assertEqualDiff(inv1, inv2)
 
1408
        return 'metaweave'
1653
1409
 
1654
1410
 
1655
1411
class MungedBundleTester(object):
1704
1460
        self.check_valid(bundle)
1705
1461
 
1706
1462
 
1707
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
 
1463
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
1708
1464
 
1709
1465
    format = '0.9'
1710
1466
 
1742
1498
        self.check_valid(bundle)
1743
1499
 
1744
1500
 
1745
 
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
 
1501
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
1746
1502
 
1747
1503
    format = '4'
1748
1504
 
1749
1505
 
1750
 
class TestBundleWriterReader(tests.TestCase):
 
1506
class TestBundleWriterReader(TestCase):
1751
1507
 
1752
1508
    def test_roundtrip_record(self):
1753
1509
        fileobj = StringIO()
1815
1571
        record = record_iter.next()
1816
1572
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1817
1573
            'info', None, None), record)
1818
 
        self.assertRaises(errors.BadBundle, record_iter.next)
1819
 
 
1820
 
 
1821
 
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
 
1574
        self.assertRaises(BadBundle, record_iter.next)
 
1575
 
 
1576
 
 
1577
class TestReadMergeableFromUrl(TestCaseWithTransport):
1822
1578
 
1823
1579
    def test_read_mergeable_skips_local(self):
1824
1580
        """A local bundle named like the URL should not be read.
1830
1586
            def look_up(self, name, url):
1831
1587
                return 'source'
1832
1588
        directories.register('foo:', FooService, 'Testing directory service')
1833
 
        self.addCleanup(directories.remove, 'foo:')
 
1589
        self.addCleanup(lambda: directories.remove('foo:'))
1834
1590
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1835
1591
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1836
1592
                          'foo:bar')
1837
 
 
1838
 
    def test_infinite_redirects_are_not_a_bundle(self):
1839
 
        """If a URL causes TooManyRedirections then NotABundle is raised.
1840
 
        """
1841
 
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
1842
 
        server = RedirectingMemoryServer()
1843
 
        self.start_server(server)
1844
 
        url = server.get_url() + 'infinite-loop'
1845
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
1846
 
 
1847
 
    def test_smart_server_connection_reset(self):
1848
 
        """If a smart server connection fails during the attempt to read a
1849
 
        bundle, then the ConnectionReset error should be propagated.
1850
 
        """
1851
 
        # Instantiate a server that will provoke a ConnectionReset
1852
 
        sock_server = DisconnectingServer()
1853
 
        self.start_server(sock_server)
1854
 
        # We don't really care what the url is since the server will close the
1855
 
        # connection without interpreting it
1856
 
        url = sock_server.get_url()
1857
 
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1858
 
 
1859
 
 
1860
 
class DisconnectingHandler(SocketServer.BaseRequestHandler):
1861
 
    """A request handler that immediately closes any connection made to it."""
1862
 
 
1863
 
    def handle(self):
1864
 
        self.request.close()
1865
 
 
1866
 
 
1867
 
class DisconnectingServer(test_server.TestingTCPServerInAThread):
1868
 
 
1869
 
    def __init__(self):
1870
 
        super(DisconnectingServer, self).__init__(
1871
 
            ('127.0.0.1', 0),
1872
 
            test_server.TestingTCPServer,
1873
 
            DisconnectingHandler)
1874
 
 
1875
 
    def get_url(self):
1876
 
        """Return the url of the server"""
1877
 
        return "bzr://%s:%d/" % self.server.server_address