~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-09-03 06:31:11 UTC
  • mfrom: (1739.2.12 readdir)
  • Revision ID: pqm@pqm.ubuntu.com-20080903063111-jr3ru4gv44xkwl2l
(robertc) Stat the contents of directories in inode order. (Robert
        Collins)

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
21
20
 
22
21
from bzrlib import (
23
22
    bzrdir,
24
 
    diff,
25
23
    errors,
26
24
    inventory,
27
 
    merge,
28
25
    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.
508
479
                                 % (ancestor,))
509
480
 
510
481
                # Now check that the file contents are all correct
511
 
                for inventory_id in old.all_file_ids():
 
482
                for inventory_id in old:
512
483
                    try:
513
484
                        old_file = old.get_file(inventory_id)
514
 
                    except errors.NoSuchFile:
 
485
                    except NoSuchFile:
515
486
                        continue
516
487
                    if old_file is None:
517
488
                        continue
521
492
                new.unlock()
522
493
                old.unlock()
523
494
        if not _mod_revision.is_null(rev_id):
524
 
            tree.branch.generate_revision_history(rev_id)
 
495
            rh = self.b1.revision_history()
 
496
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
525
497
            tree.update()
526
498
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
527
499
            self.assertFalse(delta.has_changed(),
545
517
        original_parents = to_tree.get_parent_ids()
546
518
        self.assertIs(repository.has_revision(base_rev_id), True)
547
519
        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)
 
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)
552
524
 
553
525
        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)
 
526
            self.assert_(repository.has_revision(rev.revision_id),
 
527
                'Missing revision {%s} after applying bundle' 
 
528
                % rev.revision_id)
557
529
 
558
 
        self.assertTrue(to_tree.branch.repository.has_revision(info.target))
 
530
        self.assert_(to_tree.branch.repository.has_revision(info.target))
559
531
        # Do we also want to verify that all the texts have been added?
560
532
 
561
533
        self.assertEqual(original_parents + [info.target],
562
 
                         to_tree.get_parent_ids())
 
534
            to_tree.get_parent_ids())
563
535
 
564
536
        rev = info.real_revisions[-1]
565
537
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
566
538
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
567
 
 
 
539
        
568
540
        # TODO: make sure the target tree is identical to base tree
569
541
        #       we might also check the working tree.
570
542
 
589
561
        self.tree1 = self.make_branch_and_tree('b1')
590
562
        self.b1 = self.tree1.branch
591
563
 
592
 
        self.build_tree_contents([('b1/one', 'one\n')])
593
 
        self.tree1.add('one', 'one-id')
594
 
        self.tree1.set_root_id('root-id')
 
564
        open('b1/one', 'wb').write('one\n')
 
565
        self.tree1.add('one')
595
566
        self.tree1.commit('add one', rev_id='a@cset-0-1')
596
567
 
597
568
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
608
579
                , 'b1/sub/sub/'
609
580
                , 'b1/sub/sub/nonempty.txt'
610
581
                ])
611
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
612
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
582
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
583
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
613
584
        tt = TreeTransform(self.tree1)
614
585
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
615
586
        tt.apply()
631
602
 
632
603
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
633
604
 
634
 
        # Check a rollup bundle
 
605
        # Check a rollup bundle 
635
606
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
636
607
 
637
608
        # Now delete entries
645
616
        tt.set_executability(False, trans_id)
646
617
        tt.apply()
647
618
        self.tree1.commit('removed', rev_id='a@cset-0-3')
648
 
 
 
619
        
649
620
        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,
 
621
        self.assertRaises((TestamentMismatch,
 
622
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
653
623
            'a@cset-0-2', 'a@cset-0-3')
654
 
        # Check a rollup bundle
 
624
        # Check a rollup bundle 
655
625
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
656
626
 
657
627
        # Now move the directory
659
629
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
660
630
 
661
631
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
662
 
        # Check a rollup bundle
 
632
        # Check a rollup bundle 
663
633
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
664
634
 
665
635
        # 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(
 
636
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
 
637
        open('b1/sub/dir/ pre space', 'ab').write(
668
638
             '\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',
 
639
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
 
640
        self.tree1.rename_one('sub/dir/ pre space', 
671
641
                              'sub/ start space')
672
642
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
673
643
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
679
649
                          verbose=False)
680
650
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
681
651
        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')
 
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')
686
655
        self.assertEqualDiff(tree1_inv, tree2_inv)
687
656
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
688
657
        other.commit('rename file', rev_id='a@cset-0-6b')
691
660
                          verbose=False)
692
661
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
693
662
 
694
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
695
 
        link_id = 'link-1'
696
 
 
697
 
        self.requireFeature(features.SymlinkFeature)
 
663
    def test_symlink_bundle(self):
 
664
        self.requireFeature(SymlinkFeature)
698
665
        self.tree1 = self.make_branch_and_tree('b1')
699
666
        self.b1 = self.tree1.branch
700
 
 
701
667
        tt = TreeTransform(self.tree1)
702
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
668
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
703
669
        tt.apply()
704
670
        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
 
 
 
671
        self.get_valid_bundle('null:', 'l@cset-0-1')
711
672
        tt = TreeTransform(self.tree1)
712
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
673
        trans_id = tt.trans_id_tree_file_id('link-1')
713
674
        tt.adjust_path('link2', tt.root, trans_id)
714
675
        tt.delete_contents(trans_id)
715
 
        tt.create_symlink(new_link_target, trans_id)
 
676
        tt.create_symlink('mars', trans_id)
716
677
        tt.apply()
717
678
        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
 
 
 
679
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
725
680
        tt = TreeTransform(self.tree1)
726
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
681
        trans_id = tt.trans_id_tree_file_id('link-1')
727
682
        tt.delete_contents(trans_id)
728
683
        tt.create_symlink('jupiter', trans_id)
729
684
        tt.apply()
730
685
        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
 
 
 
686
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
733
687
        tt = TreeTransform(self.tree1)
734
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
688
        trans_id = tt.trans_id_tree_file_id('link-1')
735
689
        tt.delete_contents(trans_id)
736
690
        tt.apply()
737
691
        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}')
 
692
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
748
693
 
749
694
    def test_binary_bundle(self):
750
695
        self.tree1 = self.make_branch_and_tree('b1')
751
696
        self.b1 = self.tree1.branch
752
697
        tt = TreeTransform(self.tree1)
753
 
 
 
698
        
754
699
        # Add
755
700
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
756
701
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
821
766
        self.tree1 = self.make_branch_and_tree('b1')
822
767
        self.b1 = self.tree1.branch
823
768
 
824
 
        with open('b1/one', 'wb') as f: f.write('one\n')
 
769
        open('b1/one', 'wb').write('one\n')
825
770
        self.tree1.add('one')
826
771
        self.tree1.commit('add file', rev_id='a@cset-0-1')
827
 
        with open('b1/one', 'wb') as f: f.write('two\n')
 
772
        open('b1/one', 'wb').write('two\n')
828
773
        self.tree1.commit('modify', rev_id='a@cset-0-2')
829
 
        with open('b1/one', 'wb') as f: f.write('three\n')
 
774
        open('b1/one', 'wb').write('three\n')
830
775
        self.tree1.commit('modify', rev_id='a@cset-0-3')
831
776
        bundle_file = StringIO()
832
777
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
848
793
        return bundle_file.getvalue()
849
794
 
850
795
    def test_unicode_bundle(self):
851
 
        self.requireFeature(features.UnicodeFilenameFeature)
852
796
        # Handle international characters
853
797
        os.mkdir('b1')
854
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
798
        try:
 
799
            f = open(u'b1/with Dod\xe9', 'wb')
 
800
        except UnicodeEncodeError:
 
801
            raise TestSkipped("Filesystem doesn't support unicode")
855
802
 
856
803
        self.tree1 = self.make_branch_and_tree('b1')
857
804
        self.b1 = self.tree1.branch
861
808
            u'William Dod\xe9\n').encode('utf-8'))
862
809
        f.close()
863
810
 
864
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
811
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
865
812
        self.tree1.commit(u'i18n commit from William Dod\xe9',
866
813
                          rev_id='i18n-1', committer=u'William Dod\xe9')
867
814
 
 
815
        if sys.platform == 'darwin':
 
816
            from bzrlib.workingtree import WorkingTree3
 
817
            if type(self.tree1) is WorkingTree3:
 
818
                self.knownFailure("Bug #141438: fails for WorkingTree3 on OSX")
 
819
 
 
820
            # On Mac the '\xe9' gets changed to 'e\u0301'
 
821
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
 
822
                             sorted(os.listdir(u'b1')))
 
823
            delta = self.tree1.changes_from(self.tree1.basis_tree())
 
824
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
 
825
                             delta.removed)
 
826
            self.knownFailure("Mac OSX doesn't preserve unicode"
 
827
                              " combining characters.")
 
828
 
868
829
        # Add
869
830
        bundle = self.get_valid_bundle('null:', 'i18n-1')
870
831
 
871
832
        # Modified
872
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
833
        f = open(u'b1/with Dod\xe9', 'wb')
873
834
        f.write(u'Modified \xb5\n'.encode('utf8'))
874
835
        f.close()
875
836
        self.tree1.commit(u'modified', rev_id='i18n-2')
876
837
 
877
838
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
878
 
 
 
839
        
879
840
        # Renamed
880
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
841
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
881
842
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
882
843
                          committer=u'Erik B\xe5gfors')
883
844
 
884
845
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
885
846
 
886
847
        # Removed
887
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
848
        self.tree1.remove([u'B\xe5gfors'])
888
849
        self.tree1.commit(u'removed', rev_id='i18n-4')
889
850
 
890
851
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
895
856
 
896
857
    def test_whitespace_bundle(self):
897
858
        if sys.platform in ('win32', 'cygwin'):
898
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
899
 
                                    ' with tabs or trailing spaces')
 
859
            raise TestSkipped('Windows doesn\'t support filenames'
 
860
                              ' with tabs or trailing spaces')
900
861
        self.tree1 = self.make_branch_and_tree('b1')
901
862
        self.b1 = self.tree1.branch
902
863
 
911
872
        bundle = self.get_valid_bundle('null:', 'white-1')
912
873
 
913
874
        # Modified
914
 
        with open('b1/trailing space ', 'ab') as f: f.write('add some text\n')
 
875
        open('b1/trailing space ', 'ab').write('add some text\n')
915
876
        self.tree1.commit('add text', rev_id='white-2')
916
877
 
917
878
        bundle = self.get_valid_bundle('white-1', 'white-2')
927
888
        self.tree1.commit('removed', rev_id='white-4')
928
889
 
929
890
        bundle = self.get_valid_bundle('white-3', 'white-4')
930
 
 
 
891
        
931
892
        # Now test a complet roll-up
932
893
        bundle = self.get_valid_bundle('null:', 'white-4')
933
894
 
946
907
                          timezone=19800, timestamp=1152544886.0)
947
908
 
948
909
        bundle = self.get_valid_bundle('null:', 'tz-1')
949
 
 
 
910
        
950
911
        rev = bundle.revisions[0]
951
912
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
952
913
        self.assertEqual(19800, rev.timezone)
959
920
        self.tree1.commit('message', rev_id='revid1')
960
921
        bundle = self.get_valid_bundle('null:', 'revid1')
961
922
        tree = self.get_bundle_tree(bundle, 'revid1')
962
 
        root_revision = tree.get_file_revision(tree.get_root_id())
963
 
        self.assertEqual('revid1', root_revision)
 
923
        self.assertEqual('revid1', tree.inventory.root.revision)
964
924
 
965
925
    def test_install_revisions(self):
966
926
        self.tree1 = self.make_branch_and_tree('b1')
1055
1015
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1056
1016
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1057
1017
        bundle.install_revisions(repo)
1058
 
        inv_text = repo._get_inventory_xml('rev2')
 
1018
        inv_text = repo.get_inventory_xml('rev2')
1059
1019
        self.assertNotContainsRe(inv_text, 'format="5"')
1060
1020
        self.assertContainsRe(inv_text, 'format="7"')
1061
1021
 
1081
1041
 
1082
1042
    def test_inv_hash_across_serializers(self):
1083
1043
        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)
 
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)
1087
1047
 
1088
1048
    def test_across_models_incompatible(self):
1089
1049
        tree = self.make_simple_tree('dirstate-with-subtree')
1092
1052
        try:
1093
1053
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1094
1054
        except errors.IncompatibleBundleFormat:
1095
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1055
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1096
1056
        repo = self.make_repository('repo', format='knit')
1097
1057
        bundle.install_revisions(repo)
1098
1058
 
1119
1079
        try:
1120
1080
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1121
1081
        except errors.IncompatibleBundleFormat:
1122
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1082
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1123
1083
        if isinstance(bundle, v09.BundleInfo09):
1124
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
 
1084
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
1125
1085
        repo = self.make_repository('repo', format='knit')
1126
1086
        self.assertRaises(errors.IncompatibleRevision,
1127
1087
                          bundle.install_revisions, repo)
1134
1094
        try:
1135
1095
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1136
1096
        except ValueError:
1137
 
            raise tests.TestSkipped(
1138
 
                "Repository doesn't support revision ids with slashes")
 
1097
            raise TestSkipped("Repository doesn't support revision ids with"
 
1098
                              " slashes")
1139
1099
        bundle = self.get_valid_bundle('null:', 'rev/id')
1140
1100
 
1141
1101
    def test_skip_file(self):
1157
1117
        self.tree1.commit('rev3', rev_id='rev3')
1158
1118
        bundle = self.get_valid_bundle('reva', 'rev3')
1159
1119
        if getattr(bundle, 'get_bundle_reader', None) is None:
1160
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
 
1120
            raise TestSkipped('Bundle format cannot provide reader')
1161
1121
        # be sure that file1 comes before file2
1162
1122
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1163
1123
            if f == 'file3-id':
1166
1126
        bundle.install_revisions(target.branch.repository)
1167
1127
 
1168
1128
 
1169
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1129
class V08BundleTester(BundleTester, TestCaseWithTransport):
1170
1130
 
1171
1131
    format = '0.8'
1172
1132
 
1305
1265
        return format
1306
1266
 
1307
1267
 
1308
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1268
class V4BundleTester(BundleTester, TestCaseWithTransport):
1309
1269
 
1310
1270
    format = '4'
1311
1271
 
1313
1273
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1314
1274
        Make sure that the text generated is valid, and that it
1315
1275
        can be applied against the base, and generate the same information.
1316
 
 
1317
 
        :return: The in-memory bundle
 
1276
        
 
1277
        :return: The in-memory bundle 
1318
1278
        """
1319
1279
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1320
1280
 
1321
 
        # This should also validate the generated bundle
 
1281
        # This should also validate the generated bundle 
1322
1282
        bundle = read_bundle(bundle_txt)
1323
1283
        repository = self.b1.repository
1324
1284
        for bundle_rev in bundle.real_revisions:
1328
1288
            # it
1329
1289
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1330
1290
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1331
 
                      'timestamp', 'timezone', 'message', 'committer',
 
1291
                      'timestamp', 'timezone', 'message', 'committer', 
1332
1292
                      'parent_ids', 'properties'):
1333
 
                self.assertEqual(getattr(branch_rev, a),
 
1293
                self.assertEqual(getattr(branch_rev, a), 
1334
1294
                                 getattr(bundle_rev, a))
1335
 
            self.assertEqual(len(branch_rev.parent_ids),
 
1295
            self.assertEqual(len(branch_rev.parent_ids), 
1336
1296
                             len(bundle_rev.parent_ids))
1337
1297
        self.assertEqual(set(rev_ids),
1338
1298
                         set([r.revision_id for r in bundle.real_revisions]))
1352
1312
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1353
1313
        new_text = new_text.replace('<file file_id="exe-1"',
1354
1314
                                    '<file executable="y" file_id="exe-1"')
1355
 
        new_text = new_text.replace('B260', 'B275')
 
1315
        new_text = new_text.replace('B222', 'B237')
1356
1316
        bundle_txt = StringIO()
1357
1317
        bundle_txt.write(serializer._get_bundle_header('4'))
1358
1318
        bundle_txt.write('\n')
1364
1324
 
1365
1325
    def create_bundle_text(self, base_rev_id, rev_id):
1366
1326
        bundle_txt = StringIO()
1367
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
1327
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
1368
1328
                               bundle_txt, format=self.format)
1369
1329
        bundle_txt.seek(0)
1370
 
        self.assertEqual(bundle_txt.readline(),
 
1330
        self.assertEqual(bundle_txt.readline(), 
1371
1331
                         '# Bazaar revision bundle v%s\n' % self.format)
1372
1332
        self.assertEqual(bundle_txt.readline(), '#\n')
1373
1333
        rev = self.b1.repository.get_revision(rev_id)
1395
1355
        install_bundle(target_repo, serializer.read(s))
1396
1356
        target_repo.lock_read()
1397
1357
        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
1358
        self.assertEqual({'1':'contents1\nstatic\n',
1404
 
                          '2':'contents2\nstatic\n'},
1405
 
                         repo_texts)
 
1359
            '2':'contents2\nstatic\n'},
 
1360
            dict(target_repo.iter_files_bytes(
 
1361
                [('fileid-2', 'rev1', '1'), ('fileid-2', 'rev2', '2')])))
1406
1362
        rtree = target_repo.revision_tree('rev2')
1407
1363
        inventory_vf = target_repo.inventories
1408
1364
        # If the inventory store has a graph, it must match the revision graph.
1428
1384
        branch = tree_a.branch
1429
1385
        repo_a = branch.repository
1430
1386
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1431
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
1387
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
1432
1388
        try:
1433
1389
            from bzrlib.testament import Testament
1434
1390
            # monkey patch gpg signing mechanism
1435
1391
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1436
 
            new_config = test_commit.MustSignConfig()
1437
 
            commit.Commit(config_stack=new_config).commit(message="base",
 
1392
            new_config = test_commit.MustSignConfig(branch)
 
1393
            commit.Commit(config=new_config).commit(message="base",
1438
1394
                                                    allow_pointless=True,
1439
1395
                                                    rev_id='B',
1440
1396
                                                    working_tree=tree_a)
1458
1414
        install_bundle(repo_b, serializer.read(s))
1459
1415
 
1460
1416
 
1461
 
class V4_2aBundleTester(V4BundleTester):
 
1417
class V4WeaveBundleTester(V4BundleTester):
1462
1418
 
1463
1419
    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)
 
1420
        return 'metaweave'
1653
1421
 
1654
1422
 
1655
1423
class MungedBundleTester(object):
1704
1472
        self.check_valid(bundle)
1705
1473
 
1706
1474
 
1707
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
 
1475
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
1708
1476
 
1709
1477
    format = '0.9'
1710
1478
 
1742
1510
        self.check_valid(bundle)
1743
1511
 
1744
1512
 
1745
 
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
 
1513
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
1746
1514
 
1747
1515
    format = '4'
1748
1516
 
1749
1517
 
1750
 
class TestBundleWriterReader(tests.TestCase):
 
1518
class TestBundleWriterReader(TestCase):
1751
1519
 
1752
1520
    def test_roundtrip_record(self):
1753
1521
        fileobj = StringIO()
1815
1583
        record = record_iter.next()
1816
1584
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1817
1585
            'info', None, None), record)
1818
 
        self.assertRaises(errors.BadBundle, record_iter.next)
1819
 
 
1820
 
 
1821
 
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
 
1586
        self.assertRaises(BadBundle, record_iter.next)
 
1587
 
 
1588
 
 
1589
class TestReadMergeableFromUrl(TestCaseWithTransport):
1822
1590
 
1823
1591
    def test_read_mergeable_skips_local(self):
1824
1592
        """A local bundle named like the URL should not be read.
1830
1598
            def look_up(self, name, url):
1831
1599
                return 'source'
1832
1600
        directories.register('foo:', FooService, 'Testing directory service')
1833
 
        self.addCleanup(directories.remove, 'foo:')
 
1601
        self.addCleanup(lambda: directories.remove('foo:'))
1834
1602
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1835
1603
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1836
1604
                          '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