~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Robert Collins
  • Date: 2010-04-08 04:34:03 UTC
  • mfrom: (5138 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5139.
  • Revision ID: robertc@robertcollins.net-20100408043403-56z0d07vdqrx7f3t
Update bugfix for 528114 to trunk.

Show diffs side-by-side

added added

removed removed

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