~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Patch Queue Manager
  • Date: 2016-04-21 04:10:52 UTC
  • mfrom: (6616.1.1 fix-en-user-guide)
  • Revision ID: pqm@pqm.ubuntu.com-20160421041052-clcye7ns1qcl2n7w
(richard-wilbur) Ensure build of English use guide always uses English text
 even when user's locale specifies a different language. (Jelmer Vernooij)

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-2013, 2016 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 SocketServer
19
20
import sys
20
21
 
21
22
from bzrlib import (
22
23
    bzrdir,
 
24
    diff,
23
25
    errors,
24
26
    inventory,
 
27
    merge,
25
28
    osutils,
26
 
    repository,
27
29
    revision as _mod_revision,
 
30
    tests,
28
31
    treebuilder,
29
32
    )
30
 
from bzrlib.bzrdir import BzrDir
31
33
from bzrlib.bundle import read_mergeable_from_url
32
34
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
33
35
from bzrlib.bundle.bundle_data import BundleTree
36
38
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
37
39
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
38
40
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
44
41
from bzrlib.repofmt import knitrepo
45
 
from bzrlib.osutils import sha_file, sha_string
46
42
from bzrlib.tests import (
47
 
    SymlinkFeature,
48
 
    TestCase,
49
 
    TestCaseInTempDir,
50
 
    TestCaseWithTransport,
51
 
    TestSkipped,
 
43
    features,
 
44
    test_commit,
52
45
    test_read_bundle,
53
 
    test_commit,
 
46
    test_server,
54
47
    )
55
48
from bzrlib.transform import TreeTransform
56
49
 
57
50
 
 
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
 
58
67
class MockTree(object):
 
68
 
59
69
    def __init__(self):
60
70
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
61
71
        object.__init__(self)
65
75
        self.root = InventoryDirectory(ROOT_ID, '', None)
66
76
 
67
77
    inventory = property(lambda x:x)
68
 
 
69
 
    def __iter__(self):
70
 
        return self.paths.iterkeys()
 
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
71
89
 
72
90
    def __getitem__(self, file_id):
73
91
        if file_id == self.root.file_id:
85
103
        for path, file_id in self.ids.iteritems():
86
104
            yield path, self[file_id]
87
105
 
88
 
    def get_file_kind(self, file_id):
 
106
    def kind(self, file_id):
89
107
        if file_id in self.contents:
90
108
            kind = 'file'
91
109
        else:
93
111
        return kind
94
112
 
95
113
    def make_entry(self, file_id, path):
96
 
        from bzrlib.inventory import (InventoryEntry, InventoryFile
97
 
                                    , InventoryDirectory, InventoryLink)
 
114
        from bzrlib.inventory import (InventoryFile , InventoryDirectory,
 
115
            InventoryLink)
98
116
        name = os.path.basename(path)
99
 
        kind = self.get_file_kind(file_id)
 
117
        kind = self.kind(file_id)
100
118
        parent_id = self.parent_id(file_id)
101
119
        text_sha_1, text_size = self.contents_stats(file_id)
102
120
        if kind == 'directory':
103
121
            ie = InventoryDirectory(file_id, name, parent_id)
104
122
        elif kind == 'file':
105
123
            ie = InventoryFile(file_id, name, parent_id)
 
124
            ie.text_sha1 = text_sha_1
 
125
            ie.text_size = text_size
106
126
        elif kind == 'symlink':
107
127
            ie = InventoryLink(file_id, name, parent_id)
108
128
        else:
109
 
            raise BzrError('unknown kind %r' % kind)
110
 
        ie.text_sha1 = text_sha_1
111
 
        ie.text_size = text_size
 
129
            raise errors.BzrError('unknown kind %r' % kind)
112
130
        return ie
113
131
 
114
132
    def add_dir(self, file_id, path):
115
133
        self.paths[file_id] = path
116
134
        self.ids[path] = file_id
117
 
    
 
135
 
118
136
    def add_file(self, file_id, path, contents):
119
137
        self.add_dir(file_id, path)
120
138
        self.contents[file_id] = contents
134
152
        result.seek(0,0)
135
153
        return result
136
154
 
 
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
 
137
164
    def contents_stats(self, file_id):
138
165
        if file_id not in self.contents:
139
166
            return None, None
140
 
        text_sha1 = sha_file(self.get_file(file_id))
 
167
        text_sha1 = osutils.sha_file(self.get_file(file_id))
141
168
        return text_sha1, len(self.contents[file_id])
142
169
 
143
170
 
144
 
class BTreeTester(TestCase):
 
171
class BTreeTester(tests.TestCase):
145
172
    """A simple unittest tester for the BundleTree class."""
146
173
 
147
174
    def make_tree_1(self):
151
178
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
152
179
        mtree.add_dir("d", "grandparent/alt_parent")
153
180
        return BundleTree(mtree, ''), mtree
154
 
        
 
181
 
155
182
    def test_renames(self):
156
183
        """Ensure that file renames have the proper effect on children"""
157
184
        btree = self.make_tree_1()[0]
158
185
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
159
 
        self.assertEqual(btree.old_path("grandparent/parent"), 
 
186
        self.assertEqual(btree.old_path("grandparent/parent"),
160
187
                         "grandparent/parent")
161
188
        self.assertEqual(btree.old_path("grandparent/parent/file"),
162
189
                         "grandparent/parent/file")
202
229
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
203
230
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
204
231
 
205
 
        btree.note_rename("grandparent/parent/file", 
 
232
        btree.note_rename("grandparent/parent/file",
206
233
                          "grandparent2/parent2/file2")
207
234
        self.assertEqual(btree.id2path("a"), "grandparent2")
208
235
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
217
244
    def test_moves(self):
218
245
        """Ensure that file moves have the proper effect on children"""
219
246
        btree = self.make_tree_1()[0]
220
 
        btree.note_rename("grandparent/parent/file", 
 
247
        btree.note_rename("grandparent/parent/file",
221
248
                          "grandparent/alt_parent/file")
222
249
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
223
250
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
225
252
 
226
253
    def unified_diff(self, old, new):
227
254
        out = StringIO()
228
 
        internal_diff("old", old, "new", new, out)
 
255
        diff.internal_diff("old", old, "new", new, out)
229
256
        out.seek(0,0)
230
257
        return out.read()
231
258
 
232
259
    def make_tree_2(self):
233
260
        btree = self.make_tree_1()[0]
234
 
        btree.note_rename("grandparent/parent/file", 
 
261
        btree.note_rename("grandparent/parent/file",
235
262
                          "grandparent/alt_parent/file")
236
263
        self.assertTrue(btree.id2path("e") is None)
237
264
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
266
293
    def make_tree_3(self):
267
294
        btree, mtree = self.make_tree_1()
268
295
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
269
 
        btree.note_rename("grandparent/parent/file", 
 
296
        btree.note_rename("grandparent/parent/file",
270
297
                          "grandparent/alt_parent/file")
271
 
        btree.note_rename("grandparent/parent/topping", 
 
298
        btree.note_rename("grandparent/parent/topping",
272
299
                          "grandparent/alt_parent/stopping")
273
300
        return btree
274
301
 
302
329
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
303
330
 
304
331
    def sorted_ids(self, tree):
305
 
        ids = list(tree)
 
332
        ids = list(tree.all_file_ids())
306
333
        ids.sort()
307
334
        return ids
308
335
 
313
340
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
314
341
        btree.note_deletion("grandparent/parent/file")
315
342
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
316
 
        btree.note_last_changed("grandparent/alt_parent/fool", 
 
343
        btree.note_last_changed("grandparent/alt_parent/fool",
317
344
                                "revisionidiguess")
318
345
        self.assertEqual(self.sorted_ids(btree),
319
346
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
320
347
 
321
348
 
322
 
class BundleTester1(TestCaseWithTransport):
 
349
class BundleTester1(tests.TestCaseWithTransport):
323
350
 
324
351
    def test_mismatched_bundle(self):
325
352
        format = bzrdir.BzrDirMetaFormat1()
326
353
        format.repository_format = knitrepo.RepositoryFormatKnit3()
327
354
        serializer = BundleSerializerV08('0.8')
328
355
        b = self.make_branch('.', format=format)
329
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
 
356
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
330
357
                          b.repository, [], {}, StringIO())
331
358
 
332
359
    def test_matched_bundle(self):
352
379
        format = bzrdir.BzrDirMetaFormat1()
353
380
        format.repository_format = knitrepo.RepositoryFormatKnit1()
354
381
        target = self.make_branch('target', format=format)
355
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
382
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
356
383
                          target.repository, read_bundle(text))
357
384
 
358
385
 
366
393
    def make_branch_and_tree(self, path, format=None):
367
394
        if format is None:
368
395
            format = self.bzrdir_format()
369
 
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
396
        return tests.TestCaseWithTransport.make_branch_and_tree(
 
397
            self, path, format)
370
398
 
371
399
    def make_branch(self, path, format=None):
372
400
        if format is None:
373
401
            format = self.bzrdir_format()
374
 
        return TestCaseWithTransport.make_branch(self, path, format)
 
402
        return tests.TestCaseWithTransport.make_branch(self, path, format)
375
403
 
376
404
    def create_bundle_text(self, base_rev_id, rev_id):
377
405
        bundle_txt = StringIO()
378
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
406
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
379
407
                               bundle_txt, format=self.format)
380
408
        bundle_txt.seek(0)
381
 
        self.assertEqual(bundle_txt.readline(), 
 
409
        self.assertEqual(bundle_txt.readline(),
382
410
                         '# Bazaar revision bundle v%s\n' % self.format)
383
411
        self.assertEqual(bundle_txt.readline(), '#\n')
384
412
 
392
420
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
393
421
        Make sure that the text generated is valid, and that it
394
422
        can be applied against the base, and generate the same information.
395
 
        
396
 
        :return: The in-memory bundle 
 
423
 
 
424
        :return: The in-memory bundle
397
425
        """
398
426
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
399
427
 
400
 
        # This should also validate the generated bundle 
 
428
        # This should also validate the generated bundle
401
429
        bundle = read_bundle(bundle_txt)
402
430
        repository = self.b1.repository
403
431
        for bundle_rev in bundle.real_revisions:
407
435
            # it
408
436
            branch_rev = repository.get_revision(bundle_rev.revision_id)
409
437
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
410
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
438
                      'timestamp', 'timezone', 'message', 'committer',
411
439
                      'parent_ids', 'properties'):
412
 
                self.assertEqual(getattr(branch_rev, a), 
 
440
                self.assertEqual(getattr(branch_rev, a),
413
441
                                 getattr(bundle_rev, a))
414
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
442
            self.assertEqual(len(branch_rev.parent_ids),
415
443
                             len(bundle_rev.parent_ids))
416
 
        self.assertEqual(rev_ids, 
 
444
        self.assertEqual(rev_ids,
417
445
                         [r.revision_id for r in bundle.real_revisions])
418
446
        self.valid_apply_bundle(base_rev_id, bundle,
419
447
                                   checkout_dir=checkout_dir)
423
451
    def get_invalid_bundle(self, base_rev_id, rev_id):
424
452
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
425
453
        Munge the text so that it's invalid.
426
 
        
 
454
 
427
455
        :return: The in-memory bundle
428
456
        """
429
457
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
430
 
        new_text = bundle_txt.getvalue().replace('executable:no', 
 
458
        new_text = bundle_txt.getvalue().replace('executable:no',
431
459
                                               'executable:yes')
432
460
        bundle_txt = StringIO(new_text)
433
461
        bundle = read_bundle(bundle_txt)
434
462
        self.valid_apply_bundle(base_rev_id, bundle)
435
 
        return bundle 
 
463
        return bundle
436
464
 
437
465
    def test_non_bundle(self):
438
 
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
 
466
        self.assertRaises(errors.NotABundle,
 
467
                          read_bundle, StringIO('#!/bin/sh\n'))
439
468
 
440
469
    def test_malformed(self):
441
 
        self.assertRaises(BadBundle, read_bundle, 
 
470
        self.assertRaises(errors.BadBundle, read_bundle,
442
471
                          StringIO('# Bazaar revision bundle v'))
443
472
 
444
473
    def test_crlf_bundle(self):
445
474
        try:
446
475
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
447
 
        except BadBundle:
 
476
        except errors.BadBundle:
448
477
            # It is currently permitted for bundles with crlf line endings to
449
478
            # make read_bundle raise a BadBundle, but this should be fixed.
450
479
            # Anything else, especially NotABundle, is an error.
479
508
                                 % (ancestor,))
480
509
 
481
510
                # Now check that the file contents are all correct
482
 
                for inventory_id in old:
 
511
                for inventory_id in old.all_file_ids():
483
512
                    try:
484
513
                        old_file = old.get_file(inventory_id)
485
 
                    except NoSuchFile:
 
514
                    except errors.NoSuchFile:
486
515
                        continue
487
516
                    if old_file is None:
488
517
                        continue
492
521
                new.unlock()
493
522
                old.unlock()
494
523
        if not _mod_revision.is_null(rev_id):
495
 
            rh = self.b1.revision_history()
496
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
 
524
            tree.branch.generate_revision_history(rev_id)
497
525
            tree.update()
498
526
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
499
527
            self.assertFalse(delta.has_changed(),
517
545
        original_parents = to_tree.get_parent_ids()
518
546
        self.assertIs(repository.has_revision(base_rev_id), True)
519
547
        for rev in info.real_revisions:
520
 
            self.assert_(not repository.has_revision(rev.revision_id),
521
 
                'Revision {%s} present before applying bundle' 
522
 
                % rev.revision_id)
523
 
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
 
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)
524
552
 
525
553
        for rev in info.real_revisions:
526
 
            self.assert_(repository.has_revision(rev.revision_id),
527
 
                'Missing revision {%s} after applying bundle' 
528
 
                % rev.revision_id)
 
554
            self.assertTrue(repository.has_revision(rev.revision_id),
 
555
                            'Missing revision {%s} after applying bundle'
 
556
                            % rev.revision_id)
529
557
 
530
 
        self.assert_(to_tree.branch.repository.has_revision(info.target))
 
558
        self.assertTrue(to_tree.branch.repository.has_revision(info.target))
531
559
        # Do we also want to verify that all the texts have been added?
532
560
 
533
561
        self.assertEqual(original_parents + [info.target],
534
 
            to_tree.get_parent_ids())
 
562
                         to_tree.get_parent_ids())
535
563
 
536
564
        rev = info.real_revisions[-1]
537
565
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
538
566
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
539
 
        
 
567
 
540
568
        # TODO: make sure the target tree is identical to base tree
541
569
        #       we might also check the working tree.
542
570
 
561
589
        self.tree1 = self.make_branch_and_tree('b1')
562
590
        self.b1 = self.tree1.branch
563
591
 
564
 
        open('b1/one', 'wb').write('one\n')
565
 
        self.tree1.add('one')
 
592
        self.build_tree_contents([('b1/one', 'one\n')])
 
593
        self.tree1.add('one', 'one-id')
 
594
        self.tree1.set_root_id('root-id')
566
595
        self.tree1.commit('add one', rev_id='a@cset-0-1')
567
596
 
568
597
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
579
608
                , 'b1/sub/sub/'
580
609
                , 'b1/sub/sub/nonempty.txt'
581
610
                ])
582
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
583
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
 
611
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
 
612
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
584
613
        tt = TreeTransform(self.tree1)
585
614
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
586
615
        tt.apply()
602
631
 
603
632
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
604
633
 
605
 
        # Check a rollup bundle 
 
634
        # Check a rollup bundle
606
635
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
607
636
 
608
637
        # Now delete entries
616
645
        tt.set_executability(False, trans_id)
617
646
        tt.apply()
618
647
        self.tree1.commit('removed', rev_id='a@cset-0-3')
619
 
        
 
648
 
620
649
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
621
 
        self.assertRaises((TestamentMismatch,
622
 
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
650
        self.assertRaises((errors.TestamentMismatch,
 
651
            errors.VersionedFileInvalidChecksum,
 
652
            errors.BadBundle), self.get_invalid_bundle,
623
653
            'a@cset-0-2', 'a@cset-0-3')
624
 
        # Check a rollup bundle 
 
654
        # Check a rollup bundle
625
655
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
626
656
 
627
657
        # Now move the directory
629
659
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
630
660
 
631
661
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
632
 
        # Check a rollup bundle 
 
662
        # Check a rollup bundle
633
663
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
634
664
 
635
665
        # Modified files
636
 
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
637
 
        open('b1/sub/dir/ pre space', 'ab').write(
 
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(
638
668
             '\r\nAdding some\r\nDOS format lines\r\n')
639
 
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
640
 
        self.tree1.rename_one('sub/dir/ pre space', 
 
669
        with open('b1/sub/dir/nolastnewline.txt', 'ab') as f: f.write('\n')
 
670
        self.tree1.rename_one('sub/dir/ pre space',
641
671
                              'sub/ start space')
642
672
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
643
673
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
649
679
                          verbose=False)
650
680
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
651
681
        other = self.get_checkout('a@cset-0-5')
652
 
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
653
 
            'a@cset-0-5')
654
 
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
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')
655
686
        self.assertEqualDiff(tree1_inv, tree2_inv)
656
687
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
657
688
        other.commit('rename file', rev_id='a@cset-0-6b')
660
691
                          verbose=False)
661
692
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
662
693
 
663
 
    def test_symlink_bundle(self):
664
 
        self.requireFeature(SymlinkFeature)
 
694
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
 
695
        link_id = 'link-1'
 
696
 
 
697
        self.requireFeature(features.SymlinkFeature)
665
698
        self.tree1 = self.make_branch_and_tree('b1')
666
699
        self.b1 = self.tree1.branch
 
700
 
667
701
        tt = TreeTransform(self.tree1)
668
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
 
702
        tt.new_symlink(link_name, tt.root, link_target, link_id)
669
703
        tt.apply()
670
704
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
671
 
        self.get_valid_bundle('null:', '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
 
672
711
        tt = TreeTransform(self.tree1)
673
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
712
        trans_id = tt.trans_id_tree_file_id(link_id)
674
713
        tt.adjust_path('link2', tt.root, trans_id)
675
714
        tt.delete_contents(trans_id)
676
 
        tt.create_symlink('mars', trans_id)
 
715
        tt.create_symlink(new_link_target, trans_id)
677
716
        tt.apply()
678
717
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
679
 
        self.get_valid_bundle('l@cset-0-1', '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
 
680
725
        tt = TreeTransform(self.tree1)
681
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
726
        trans_id = tt.trans_id_tree_file_id(link_id)
682
727
        tt.delete_contents(trans_id)
683
728
        tt.create_symlink('jupiter', trans_id)
684
729
        tt.apply()
685
730
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
686
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
731
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
732
 
687
733
        tt = TreeTransform(self.tree1)
688
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
734
        trans_id = tt.trans_id_tree_file_id(link_id)
689
735
        tt.delete_contents(trans_id)
690
736
        tt.apply()
691
737
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
692
 
        self.get_valid_bundle('l@cset-0-3', '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}')
693
748
 
694
749
    def test_binary_bundle(self):
695
750
        self.tree1 = self.make_branch_and_tree('b1')
696
751
        self.b1 = self.tree1.branch
697
752
        tt = TreeTransform(self.tree1)
698
 
        
 
753
 
699
754
        # Add
700
755
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
701
756
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
766
821
        self.tree1 = self.make_branch_and_tree('b1')
767
822
        self.b1 = self.tree1.branch
768
823
 
769
 
        open('b1/one', 'wb').write('one\n')
 
824
        with open('b1/one', 'wb') as f: f.write('one\n')
770
825
        self.tree1.add('one')
771
826
        self.tree1.commit('add file', rev_id='a@cset-0-1')
772
 
        open('b1/one', 'wb').write('two\n')
 
827
        with open('b1/one', 'wb') as f: f.write('two\n')
773
828
        self.tree1.commit('modify', rev_id='a@cset-0-2')
774
 
        open('b1/one', 'wb').write('three\n')
 
829
        with open('b1/one', 'wb') as f: f.write('three\n')
775
830
        self.tree1.commit('modify', rev_id='a@cset-0-3')
776
831
        bundle_file = StringIO()
777
832
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
793
848
        return bundle_file.getvalue()
794
849
 
795
850
    def test_unicode_bundle(self):
 
851
        self.requireFeature(features.UnicodeFilenameFeature)
796
852
        # Handle international characters
797
853
        os.mkdir('b1')
798
 
        try:
799
 
            f = open(u'b1/with Dod\xe9', 'wb')
800
 
        except UnicodeEncodeError:
801
 
            raise TestSkipped("Filesystem doesn't support unicode")
 
854
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
802
855
 
803
856
        self.tree1 = self.make_branch_and_tree('b1')
804
857
        self.b1 = self.tree1.branch
808
861
            u'William Dod\xe9\n').encode('utf-8'))
809
862
        f.close()
810
863
 
811
 
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
 
864
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
812
865
        self.tree1.commit(u'i18n commit from William Dod\xe9',
813
866
                          rev_id='i18n-1', committer=u'William Dod\xe9')
814
867
 
815
 
        if sys.platform == 'darwin':
816
 
            from bzrlib.workingtree import WorkingTree3
817
 
            if type(self.tree1) is WorkingTree3:
818
 
                self.knownFailure("Bug #141438: fails for WorkingTree3 on OSX")
819
 
 
820
 
            # On Mac the '\xe9' gets changed to 'e\u0301'
821
 
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
822
 
                             sorted(os.listdir(u'b1')))
823
 
            delta = self.tree1.changes_from(self.tree1.basis_tree())
824
 
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
825
 
                             delta.removed)
826
 
            self.knownFailure("Mac OSX doesn't preserve unicode"
827
 
                              " combining characters.")
828
 
 
829
868
        # Add
830
869
        bundle = self.get_valid_bundle('null:', 'i18n-1')
831
870
 
832
871
        # Modified
833
 
        f = open(u'b1/with Dod\xe9', 'wb')
 
872
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
834
873
        f.write(u'Modified \xb5\n'.encode('utf8'))
835
874
        f.close()
836
875
        self.tree1.commit(u'modified', rev_id='i18n-2')
837
876
 
838
877
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
839
 
        
 
878
 
840
879
        # Renamed
841
 
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
 
880
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
842
881
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
843
882
                          committer=u'Erik B\xe5gfors')
844
883
 
845
884
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
846
885
 
847
886
        # Removed
848
 
        self.tree1.remove([u'B\xe5gfors'])
 
887
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
849
888
        self.tree1.commit(u'removed', rev_id='i18n-4')
850
889
 
851
890
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
856
895
 
857
896
    def test_whitespace_bundle(self):
858
897
        if sys.platform in ('win32', 'cygwin'):
859
 
            raise TestSkipped('Windows doesn\'t support filenames'
860
 
                              ' with tabs or trailing spaces')
 
898
            raise tests.TestSkipped('Windows doesn\'t support filenames'
 
899
                                    ' with tabs or trailing spaces')
861
900
        self.tree1 = self.make_branch_and_tree('b1')
862
901
        self.b1 = self.tree1.branch
863
902
 
872
911
        bundle = self.get_valid_bundle('null:', 'white-1')
873
912
 
874
913
        # Modified
875
 
        open('b1/trailing space ', 'ab').write('add some text\n')
 
914
        with open('b1/trailing space ', 'ab') as f: f.write('add some text\n')
876
915
        self.tree1.commit('add text', rev_id='white-2')
877
916
 
878
917
        bundle = self.get_valid_bundle('white-1', 'white-2')
888
927
        self.tree1.commit('removed', rev_id='white-4')
889
928
 
890
929
        bundle = self.get_valid_bundle('white-3', 'white-4')
891
 
        
 
930
 
892
931
        # Now test a complet roll-up
893
932
        bundle = self.get_valid_bundle('null:', 'white-4')
894
933
 
907
946
                          timezone=19800, timestamp=1152544886.0)
908
947
 
909
948
        bundle = self.get_valid_bundle('null:', 'tz-1')
910
 
        
 
949
 
911
950
        rev = bundle.revisions[0]
912
951
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
913
952
        self.assertEqual(19800, rev.timezone)
920
959
        self.tree1.commit('message', rev_id='revid1')
921
960
        bundle = self.get_valid_bundle('null:', 'revid1')
922
961
        tree = self.get_bundle_tree(bundle, 'revid1')
923
 
        self.assertEqual('revid1', tree.inventory.root.revision)
 
962
        root_revision = tree.get_file_revision(tree.get_root_id())
 
963
        self.assertEqual('revid1', root_revision)
924
964
 
925
965
    def test_install_revisions(self):
926
966
        self.tree1 = self.make_branch_and_tree('b1')
1015
1055
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1016
1056
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1017
1057
        bundle.install_revisions(repo)
1018
 
        inv_text = repo.get_inventory_xml('rev2')
 
1058
        inv_text = repo._get_inventory_xml('rev2')
1019
1059
        self.assertNotContainsRe(inv_text, 'format="5"')
1020
1060
        self.assertContainsRe(inv_text, 'format="7"')
1021
1061
 
1041
1081
 
1042
1082
    def test_inv_hash_across_serializers(self):
1043
1083
        repo = self.make_repo_with_installed_revisions()
1044
 
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
1045
 
        xml = repo.get_inventory_xml('rev2')
1046
 
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
 
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)
1047
1087
 
1048
1088
    def test_across_models_incompatible(self):
1049
1089
        tree = self.make_simple_tree('dirstate-with-subtree')
1052
1092
        try:
1053
1093
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1054
1094
        except errors.IncompatibleBundleFormat:
1055
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1095
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1056
1096
        repo = self.make_repository('repo', format='knit')
1057
1097
        bundle.install_revisions(repo)
1058
1098
 
1079
1119
        try:
1080
1120
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1081
1121
        except errors.IncompatibleBundleFormat:
1082
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1122
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1083
1123
        if isinstance(bundle, v09.BundleInfo09):
1084
 
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
 
1124
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
1085
1125
        repo = self.make_repository('repo', format='knit')
1086
1126
        self.assertRaises(errors.IncompatibleRevision,
1087
1127
                          bundle.install_revisions, repo)
1094
1134
        try:
1095
1135
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1096
1136
        except ValueError:
1097
 
            raise TestSkipped("Repository doesn't support revision ids with"
1098
 
                              " slashes")
 
1137
            raise tests.TestSkipped(
 
1138
                "Repository doesn't support revision ids with slashes")
1099
1139
        bundle = self.get_valid_bundle('null:', 'rev/id')
1100
1140
 
1101
1141
    def test_skip_file(self):
1117
1157
        self.tree1.commit('rev3', rev_id='rev3')
1118
1158
        bundle = self.get_valid_bundle('reva', 'rev3')
1119
1159
        if getattr(bundle, 'get_bundle_reader', None) is None:
1120
 
            raise TestSkipped('Bundle format cannot provide reader')
 
1160
            raise tests.TestSkipped('Bundle format cannot provide reader')
1121
1161
        # be sure that file1 comes before file2
1122
1162
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1123
1163
            if f == 'file3-id':
1126
1166
        bundle.install_revisions(target.branch.repository)
1127
1167
 
1128
1168
 
1129
 
class V08BundleTester(BundleTester, TestCaseWithTransport):
 
1169
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
1130
1170
 
1131
1171
    format = '0.8'
1132
1172
 
1265
1305
        return format
1266
1306
 
1267
1307
 
1268
 
class V4BundleTester(BundleTester, TestCaseWithTransport):
 
1308
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
1269
1309
 
1270
1310
    format = '4'
1271
1311
 
1273
1313
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1274
1314
        Make sure that the text generated is valid, and that it
1275
1315
        can be applied against the base, and generate the same information.
1276
 
        
1277
 
        :return: The in-memory bundle 
 
1316
 
 
1317
        :return: The in-memory bundle
1278
1318
        """
1279
1319
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1280
1320
 
1281
 
        # This should also validate the generated bundle 
 
1321
        # This should also validate the generated bundle
1282
1322
        bundle = read_bundle(bundle_txt)
1283
1323
        repository = self.b1.repository
1284
1324
        for bundle_rev in bundle.real_revisions:
1288
1328
            # it
1289
1329
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1290
1330
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1291
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
1331
                      'timestamp', 'timezone', 'message', 'committer',
1292
1332
                      'parent_ids', 'properties'):
1293
 
                self.assertEqual(getattr(branch_rev, a), 
 
1333
                self.assertEqual(getattr(branch_rev, a),
1294
1334
                                 getattr(bundle_rev, a))
1295
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
1335
            self.assertEqual(len(branch_rev.parent_ids),
1296
1336
                             len(bundle_rev.parent_ids))
1297
1337
        self.assertEqual(set(rev_ids),
1298
1338
                         set([r.revision_id for r in bundle.real_revisions]))
1312
1352
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1313
1353
        new_text = new_text.replace('<file file_id="exe-1"',
1314
1354
                                    '<file executable="y" file_id="exe-1"')
1315
 
        new_text = new_text.replace('B222', 'B237')
 
1355
        new_text = new_text.replace('B260', 'B275')
1316
1356
        bundle_txt = StringIO()
1317
1357
        bundle_txt.write(serializer._get_bundle_header('4'))
1318
1358
        bundle_txt.write('\n')
1324
1364
 
1325
1365
    def create_bundle_text(self, base_rev_id, rev_id):
1326
1366
        bundle_txt = StringIO()
1327
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
1367
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1328
1368
                               bundle_txt, format=self.format)
1329
1369
        bundle_txt.seek(0)
1330
 
        self.assertEqual(bundle_txt.readline(), 
 
1370
        self.assertEqual(bundle_txt.readline(),
1331
1371
                         '# Bazaar revision bundle v%s\n' % self.format)
1332
1372
        self.assertEqual(bundle_txt.readline(), '#\n')
1333
1373
        rev = self.b1.repository.get_revision(rev_id)
1355
1395
        install_bundle(target_repo, serializer.read(s))
1356
1396
        target_repo.lock_read()
1357
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')]))
1358
1403
        self.assertEqual({'1':'contents1\nstatic\n',
1359
 
            '2':'contents2\nstatic\n'},
1360
 
            dict(target_repo.iter_files_bytes(
1361
 
                [('fileid-2', 'rev1', '1'), ('fileid-2', 'rev2', '2')])))
 
1404
                          '2':'contents2\nstatic\n'},
 
1405
                         repo_texts)
1362
1406
        rtree = target_repo.revision_tree('rev2')
1363
1407
        inventory_vf = target_repo.inventories
1364
1408
        # If the inventory store has a graph, it must match the revision graph.
1384
1428
        branch = tree_a.branch
1385
1429
        repo_a = branch.repository
1386
1430
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1387
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1431
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
1388
1432
        try:
1389
1433
            from bzrlib.testament import Testament
1390
1434
            # monkey patch gpg signing mechanism
1391
1435
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1392
 
            new_config = test_commit.MustSignConfig(branch)
1393
 
            commit.Commit(config=new_config).commit(message="base",
 
1436
            new_config = test_commit.MustSignConfig()
 
1437
            commit.Commit(config_stack=new_config).commit(message="base",
1394
1438
                                                    allow_pointless=True,
1395
1439
                                                    rev_id='B',
1396
1440
                                                    working_tree=tree_a)
1414
1458
        install_bundle(repo_b, serializer.read(s))
1415
1459
 
1416
1460
 
1417
 
class V4WeaveBundleTester(V4BundleTester):
 
1461
class V4_2aBundleTester(V4BundleTester):
1418
1462
 
1419
1463
    def bzrdir_format(self):
1420
 
        return 'metaweave'
 
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)
1421
1653
 
1422
1654
 
1423
1655
class MungedBundleTester(object):
1472
1704
        self.check_valid(bundle)
1473
1705
 
1474
1706
 
1475
 
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
 
1707
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
1476
1708
 
1477
1709
    format = '0.9'
1478
1710
 
1510
1742
        self.check_valid(bundle)
1511
1743
 
1512
1744
 
1513
 
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
 
1745
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
1514
1746
 
1515
1747
    format = '4'
1516
1748
 
1517
1749
 
1518
 
class TestBundleWriterReader(TestCase):
 
1750
class TestBundleWriterReader(tests.TestCase):
1519
1751
 
1520
1752
    def test_roundtrip_record(self):
1521
1753
        fileobj = StringIO()
1583
1815
        record = record_iter.next()
1584
1816
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1585
1817
            'info', None, None), record)
1586
 
        self.assertRaises(BadBundle, record_iter.next)
1587
 
 
1588
 
 
1589
 
class TestReadMergeableFromUrl(TestCaseWithTransport):
 
1818
        self.assertRaises(errors.BadBundle, record_iter.next)
 
1819
 
 
1820
 
 
1821
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
1590
1822
 
1591
1823
    def test_read_mergeable_skips_local(self):
1592
1824
        """A local bundle named like the URL should not be read.
1598
1830
            def look_up(self, name, url):
1599
1831
                return 'source'
1600
1832
        directories.register('foo:', FooService, 'Testing directory service')
1601
 
        self.addCleanup(lambda: directories.remove('foo:'))
 
1833
        self.addCleanup(directories.remove, 'foo:')
1602
1834
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1603
1835
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1604
1836
                          '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