~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-03-16 14:01:20 UTC
  • mfrom: (3280.2.5 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20080316140120-i3yq8yr1l66m11h7
Start 1.4 development

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 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 socket
20
19
import sys
21
 
import threading
 
20
import tempfile
22
21
 
23
22
from bzrlib import (
24
23
    bzrdir,
25
 
    diff,
26
24
    errors,
27
25
    inventory,
28
 
    merge,
29
 
    osutils,
30
26
    repository,
31
27
    revision as _mod_revision,
32
 
    tests,
33
28
    treebuilder,
34
29
    )
35
 
from bzrlib.bundle import read_mergeable_from_url
 
30
from bzrlib.bzrdir import BzrDir
36
31
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
37
32
from bzrlib.bundle.bundle_data import BundleTree
38
 
from bzrlib.bzrdir import BzrDir
39
 
from bzrlib.directory_service import directories
40
33
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
41
34
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
42
35
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
43
36
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
44
37
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
45
42
from bzrlib.repofmt import knitrepo
 
43
from bzrlib.osutils import sha_file
46
44
from bzrlib.tests import (
47
 
    test_read_bundle,
 
45
    SymlinkFeature,
 
46
    TestCase,
 
47
    TestCaseInTempDir,
 
48
    TestCaseWithTransport,
 
49
    TestSkipped,
48
50
    test_commit,
49
51
    )
50
52
from bzrlib.transform import TreeTransform
51
53
 
52
54
 
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
 
 
69
55
class MockTree(object):
70
56
    def __init__(self):
71
57
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
114
100
            ie = InventoryDirectory(file_id, name, parent_id)
115
101
        elif kind == 'file':
116
102
            ie = InventoryFile(file_id, name, parent_id)
117
 
            ie.text_sha1 = text_sha_1
118
 
            ie.text_size = text_size
119
103
        elif kind == 'symlink':
120
104
            ie = InventoryLink(file_id, name, parent_id)
121
105
        else:
122
 
            raise errors.BzrError('unknown kind %r' % kind)
 
106
            raise BzrError('unknown kind %r' % kind)
 
107
        ie.text_sha1 = text_sha_1
 
108
        ie.text_size = text_size
123
109
        return ie
124
110
 
125
111
    def add_dir(self, file_id, path):
126
112
        self.paths[file_id] = path
127
113
        self.ids[path] = file_id
128
 
 
 
114
    
129
115
    def add_file(self, file_id, path, contents):
130
116
        self.add_dir(file_id, path)
131
117
        self.contents[file_id] = contents
148
134
    def contents_stats(self, file_id):
149
135
        if file_id not in self.contents:
150
136
            return None, None
151
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
137
        text_sha1 = sha_file(self.get_file(file_id))
152
138
        return text_sha1, len(self.contents[file_id])
153
139
 
154
140
 
155
 
class BTreeTester(tests.TestCase):
 
141
class BTreeTester(TestCase):
156
142
    """A simple unittest tester for the BundleTree class."""
157
143
 
158
144
    def make_tree_1(self):
162
148
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
163
149
        mtree.add_dir("d", "grandparent/alt_parent")
164
150
        return BundleTree(mtree, ''), mtree
165
 
 
 
151
        
166
152
    def test_renames(self):
167
153
        """Ensure that file renames have the proper effect on children"""
168
154
        btree = self.make_tree_1()[0]
169
155
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
170
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
156
        self.assertEqual(btree.old_path("grandparent/parent"), 
171
157
                         "grandparent/parent")
172
158
        self.assertEqual(btree.old_path("grandparent/parent/file"),
173
159
                         "grandparent/parent/file")
180
166
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
181
167
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
182
168
 
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)
 
169
        assert btree.path2id("grandparent2") is None
 
170
        assert btree.path2id("grandparent2/parent") is None
 
171
        assert btree.path2id("grandparent2/parent/file") is None
186
172
 
187
173
        btree.note_rename("grandparent", "grandparent2")
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)
 
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
191
177
 
192
178
        self.assertEqual(btree.id2path("a"), "grandparent2")
193
179
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
197
183
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
198
184
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
199
185
 
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)
 
186
        assert btree.path2id("grandparent") is None
 
187
        assert btree.path2id("grandparent/parent") is None
 
188
        assert btree.path2id("grandparent/parent/file") is None
203
189
 
204
190
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
205
191
        self.assertEqual(btree.id2path("a"), "grandparent2")
210
196
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
211
197
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
212
198
 
213
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
214
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
199
        assert btree.path2id("grandparent2/parent") is None
 
200
        assert btree.path2id("grandparent2/parent/file") is None
215
201
 
216
 
        btree.note_rename("grandparent/parent/file",
 
202
        btree.note_rename("grandparent/parent/file", 
217
203
                          "grandparent2/parent2/file2")
218
204
        self.assertEqual(btree.id2path("a"), "grandparent2")
219
205
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
223
209
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
224
210
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
225
211
 
226
 
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
 
212
        assert btree.path2id("grandparent2/parent2/file") is None
227
213
 
228
214
    def test_moves(self):
229
215
        """Ensure that file moves have the proper effect on children"""
230
216
        btree = self.make_tree_1()[0]
231
 
        btree.note_rename("grandparent/parent/file",
 
217
        btree.note_rename("grandparent/parent/file", 
232
218
                          "grandparent/alt_parent/file")
233
219
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
234
220
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
235
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
221
        assert btree.path2id("grandparent/parent/file") is None
236
222
 
237
223
    def unified_diff(self, old, new):
238
224
        out = StringIO()
239
 
        diff.internal_diff("old", old, "new", new, out)
 
225
        internal_diff("old", old, "new", new, out)
240
226
        out.seek(0,0)
241
227
        return out.read()
242
228
 
243
229
    def make_tree_2(self):
244
230
        btree = self.make_tree_1()[0]
245
 
        btree.note_rename("grandparent/parent/file",
 
231
        btree.note_rename("grandparent/parent/file", 
246
232
                          "grandparent/alt_parent/file")
247
 
        self.assertTrue(btree.id2path("e") is None)
248
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
233
        assert btree.id2path("e") is None
 
234
        assert btree.path2id("grandparent/parent/file") is None
249
235
        btree.note_id("e", "grandparent/parent/file")
250
236
        return btree
251
237
 
277
263
    def make_tree_3(self):
278
264
        btree, mtree = self.make_tree_1()
279
265
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
280
 
        btree.note_rename("grandparent/parent/file",
 
266
        btree.note_rename("grandparent/parent/file", 
281
267
                          "grandparent/alt_parent/file")
282
 
        btree.note_rename("grandparent/parent/topping",
 
268
        btree.note_rename("grandparent/parent/topping", 
283
269
                          "grandparent/alt_parent/stopping")
284
270
        return btree
285
271
 
309
295
        btree = self.make_tree_1()[0]
310
296
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
311
297
        btree.note_deletion("grandparent/parent/file")
312
 
        self.assertTrue(btree.id2path("c") is None)
313
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
298
        assert btree.id2path("c") is None
 
299
        assert btree.path2id("grandparent/parent/file") is None
314
300
 
315
301
    def sorted_ids(self, tree):
316
302
        ids = list(tree)
324
310
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
325
311
        btree.note_deletion("grandparent/parent/file")
326
312
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
327
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
313
        btree.note_last_changed("grandparent/alt_parent/fool", 
328
314
                                "revisionidiguess")
329
315
        self.assertEqual(self.sorted_ids(btree),
330
316
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
331
317
 
332
318
 
333
 
class BundleTester1(tests.TestCaseWithTransport):
 
319
class BundleTester1(TestCaseWithTransport):
334
320
 
335
321
    def test_mismatched_bundle(self):
336
322
        format = bzrdir.BzrDirMetaFormat1()
337
323
        format.repository_format = knitrepo.RepositoryFormatKnit3()
338
324
        serializer = BundleSerializerV08('0.8')
339
325
        b = self.make_branch('.', format=format)
340
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
326
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
341
327
                          b.repository, [], {}, StringIO())
342
328
 
343
329
    def test_matched_bundle(self):
363
349
        format = bzrdir.BzrDirMetaFormat1()
364
350
        format.repository_format = knitrepo.RepositoryFormatKnit1()
365
351
        target = self.make_branch('target', format=format)
366
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
352
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
367
353
                          target.repository, read_bundle(text))
368
354
 
369
355
 
377
363
    def make_branch_and_tree(self, path, format=None):
378
364
        if format is None:
379
365
            format = self.bzrdir_format()
380
 
        return tests.TestCaseWithTransport.make_branch_and_tree(
381
 
            self, path, format)
 
366
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
382
367
 
383
368
    def make_branch(self, path, format=None):
384
369
        if format is None:
385
370
            format = self.bzrdir_format()
386
 
        return tests.TestCaseWithTransport.make_branch(self, path, format)
 
371
        return TestCaseWithTransport.make_branch(self, path, format)
387
372
 
388
373
    def create_bundle_text(self, base_rev_id, rev_id):
389
374
        bundle_txt = StringIO()
390
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
375
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
391
376
                               bundle_txt, format=self.format)
392
377
        bundle_txt.seek(0)
393
 
        self.assertEqual(bundle_txt.readline(),
 
378
        self.assertEqual(bundle_txt.readline(), 
394
379
                         '# Bazaar revision bundle v%s\n' % self.format)
395
380
        self.assertEqual(bundle_txt.readline(), '#\n')
396
381
 
404
389
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
405
390
        Make sure that the text generated is valid, and that it
406
391
        can be applied against the base, and generate the same information.
407
 
 
408
 
        :return: The in-memory bundle
 
392
        
 
393
        :return: The in-memory bundle 
409
394
        """
410
395
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
411
396
 
412
 
        # This should also validate the generated bundle
 
397
        # This should also validate the generated bundle 
413
398
        bundle = read_bundle(bundle_txt)
414
399
        repository = self.b1.repository
415
400
        for bundle_rev in bundle.real_revisions:
419
404
            # it
420
405
            branch_rev = repository.get_revision(bundle_rev.revision_id)
421
406
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
422
 
                      'timestamp', 'timezone', 'message', 'committer',
 
407
                      'timestamp', 'timezone', 'message', 'committer', 
423
408
                      'parent_ids', 'properties'):
424
 
                self.assertEqual(getattr(branch_rev, a),
 
409
                self.assertEqual(getattr(branch_rev, a), 
425
410
                                 getattr(bundle_rev, a))
426
 
            self.assertEqual(len(branch_rev.parent_ids),
 
411
            self.assertEqual(len(branch_rev.parent_ids), 
427
412
                             len(bundle_rev.parent_ids))
428
 
        self.assertEqual(rev_ids,
 
413
        self.assertEqual(rev_ids, 
429
414
                         [r.revision_id for r in bundle.real_revisions])
430
415
        self.valid_apply_bundle(base_rev_id, bundle,
431
416
                                   checkout_dir=checkout_dir)
435
420
    def get_invalid_bundle(self, base_rev_id, rev_id):
436
421
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
437
422
        Munge the text so that it's invalid.
438
 
 
 
423
        
439
424
        :return: The in-memory bundle
440
425
        """
441
426
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
442
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
427
        new_text = bundle_txt.getvalue().replace('executable:no', 
443
428
                                               'executable:yes')
444
429
        bundle_txt = StringIO(new_text)
445
430
        bundle = read_bundle(bundle_txt)
446
431
        self.valid_apply_bundle(base_rev_id, bundle)
447
 
        return bundle
 
432
        return bundle 
448
433
 
449
434
    def test_non_bundle(self):
450
 
        self.assertRaises(errors.NotABundle,
451
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
435
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
452
436
 
453
437
    def test_malformed(self):
454
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
438
        self.assertRaises(BadBundle, read_bundle, 
455
439
                          StringIO('# Bazaar revision bundle v'))
456
440
 
457
441
    def test_crlf_bundle(self):
458
442
        try:
459
443
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
460
 
        except errors.BadBundle:
 
444
        except BadBundle:
461
445
            # It is currently permitted for bundles with crlf line endings to
462
446
            # make read_bundle raise a BadBundle, but this should be fixed.
463
447
            # Anything else, especially NotABundle, is an error.
468
452
        """
469
453
 
470
454
        if checkout_dir is None:
471
 
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
 
455
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
472
456
        else:
473
457
            if not os.path.exists(checkout_dir):
474
458
                os.mkdir(checkout_dir)
477
461
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
478
462
                                 format=self.format)
479
463
        s.seek(0)
480
 
        self.assertIsInstance(s.getvalue(), str)
 
464
        assert isinstance(s.getvalue(), str), (
 
465
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
481
466
        install_bundle(tree.branch.repository, read_bundle(s))
482
467
        for ancestor in ancestors:
483
468
            old = self.b1.repository.revision_tree(ancestor)
484
469
            new = tree.branch.repository.revision_tree(ancestor)
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()
 
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())
507
487
        if not _mod_revision.is_null(rev_id):
508
488
            rh = self.b1.revision_history()
509
489
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
531
511
        self.assertIs(repository.has_revision(base_rev_id), True)
532
512
        for rev in info.real_revisions:
533
513
            self.assert_(not repository.has_revision(rev.revision_id),
534
 
                'Revision {%s} present before applying bundle'
 
514
                'Revision {%s} present before applying bundle' 
535
515
                % rev.revision_id)
536
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
516
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
537
517
 
538
518
        for rev in info.real_revisions:
539
519
            self.assert_(repository.has_revision(rev.revision_id),
540
 
                'Missing revision {%s} after applying bundle'
 
520
                'Missing revision {%s} after applying bundle' 
541
521
                % rev.revision_id)
542
522
 
543
523
        self.assert_(to_tree.branch.repository.has_revision(info.target))
549
529
        rev = info.real_revisions[-1]
550
530
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
551
531
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
552
 
 
 
532
        
553
533
        # TODO: make sure the target tree is identical to base tree
554
534
        #       we might also check the working tree.
555
535
 
574
554
        self.tree1 = self.make_branch_and_tree('b1')
575
555
        self.b1 = self.tree1.branch
576
556
 
577
 
        self.build_tree_contents([('b1/one', 'one\n')])
578
 
        self.tree1.add('one', 'one-id')
579
 
        self.tree1.set_root_id('root-id')
 
557
        open('b1/one', 'wb').write('one\n')
 
558
        self.tree1.add('one')
580
559
        self.tree1.commit('add one', rev_id='a@cset-0-1')
581
560
 
582
561
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
593
572
                , 'b1/sub/sub/'
594
573
                , 'b1/sub/sub/nonempty.txt'
595
574
                ])
596
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
597
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
575
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
576
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
598
577
        tt = TreeTransform(self.tree1)
599
578
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
600
579
        tt.apply()
616
595
 
617
596
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
618
597
 
619
 
        # Check a rollup bundle
 
598
        # Check a rollup bundle 
620
599
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
621
600
 
622
601
        # Now delete entries
630
609
        tt.set_executability(False, trans_id)
631
610
        tt.apply()
632
611
        self.tree1.commit('removed', rev_id='a@cset-0-3')
633
 
 
 
612
        
634
613
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
635
 
        self.assertRaises((errors.TestamentMismatch,
636
 
            errors.VersionedFileInvalidChecksum,
637
 
            errors.BadBundle), self.get_invalid_bundle,
 
614
        self.assertRaises((TestamentMismatch,
 
615
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
638
616
            'a@cset-0-2', 'a@cset-0-3')
639
 
        # Check a rollup bundle
 
617
        # Check a rollup bundle 
640
618
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
641
619
 
642
620
        # Now move the directory
644
622
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
645
623
 
646
624
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
647
 
        # Check a rollup bundle
 
625
        # Check a rollup bundle 
648
626
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
649
627
 
650
628
        # Modified files
652
630
        open('b1/sub/dir/ pre space', 'ab').write(
653
631
             '\r\nAdding some\r\nDOS format lines\r\n')
654
632
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
655
 
        self.tree1.rename_one('sub/dir/ pre space',
 
633
        self.tree1.rename_one('sub/dir/ pre space', 
656
634
                              'sub/ start space')
657
635
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
658
636
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
664
642
                          verbose=False)
665
643
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
666
644
        other = self.get_checkout('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')
 
645
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
 
646
            'a@cset-0-5')
 
647
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
671
648
        self.assertEqualDiff(tree1_inv, tree2_inv)
672
649
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
673
650
        other.commit('rename file', rev_id='a@cset-0-6b')
676
653
                          verbose=False)
677
654
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
678
655
 
679
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
680
 
        link_id = 'link-1'
681
 
 
682
 
        self.requireFeature(tests.SymlinkFeature)
 
656
    def test_symlink_bundle(self):
 
657
        self.requireFeature(SymlinkFeature)
683
658
        self.tree1 = self.make_branch_and_tree('b1')
684
659
        self.b1 = self.tree1.branch
685
 
 
686
660
        tt = TreeTransform(self.tree1)
687
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
661
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
688
662
        tt.apply()
689
663
        self.tree1.commit('add symlink', rev_id='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
 
 
 
664
        self.get_valid_bundle('null:', 'l@cset-0-1')
696
665
        tt = TreeTransform(self.tree1)
697
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
666
        trans_id = tt.trans_id_tree_file_id('link-1')
698
667
        tt.adjust_path('link2', tt.root, trans_id)
699
668
        tt.delete_contents(trans_id)
700
 
        tt.create_symlink(new_link_target, trans_id)
 
669
        tt.create_symlink('mars', trans_id)
701
670
        tt.apply()
702
671
        self.tree1.commit('rename and change symlink', rev_id='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
 
 
 
672
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
710
673
        tt = TreeTransform(self.tree1)
711
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
674
        trans_id = tt.trans_id_tree_file_id('link-1')
712
675
        tt.delete_contents(trans_id)
713
676
        tt.create_symlink('jupiter', trans_id)
714
677
        tt.apply()
715
678
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
716
 
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
717
 
 
 
679
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
718
680
        tt = TreeTransform(self.tree1)
719
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
681
        trans_id = tt.trans_id_tree_file_id('link-1')
720
682
        tt.delete_contents(trans_id)
721
683
        tt.apply()
722
684
        self.tree1.commit('Delete symlink', rev_id='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}')
 
685
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
733
686
 
734
687
    def test_binary_bundle(self):
735
688
        self.tree1 = self.make_branch_and_tree('b1')
736
689
        self.b1 = self.tree1.branch
737
690
        tt = TreeTransform(self.tree1)
738
 
 
 
691
        
739
692
        # Add
740
693
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
741
694
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
833
786
        return bundle_file.getvalue()
834
787
 
835
788
    def test_unicode_bundle(self):
836
 
        self.requireFeature(tests.UnicodeFilenameFeature)
837
789
        # Handle international characters
838
790
        os.mkdir('b1')
839
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
791
        try:
 
792
            f = open(u'b1/with Dod\xe9', 'wb')
 
793
        except UnicodeEncodeError:
 
794
            raise TestSkipped("Filesystem doesn't support unicode")
840
795
 
841
796
        self.tree1 = self.make_branch_and_tree('b1')
842
797
        self.b1 = self.tree1.branch
846
801
            u'William Dod\xe9\n').encode('utf-8'))
847
802
        f.close()
848
803
 
849
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
804
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
850
805
        self.tree1.commit(u'i18n commit from William Dod\xe9',
851
806
                          rev_id='i18n-1', committer=u'William Dod\xe9')
852
807
 
 
808
        if sys.platform == 'darwin':
 
809
            from bzrlib.workingtree import WorkingTree3
 
810
            if type(self.tree1) is WorkingTree3:
 
811
                self.knownFailure("Bug #141438: fails for WorkingTree3 on OSX")
 
812
 
 
813
            # On Mac the '\xe9' gets changed to 'e\u0301'
 
814
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
 
815
                             sorted(os.listdir(u'b1')))
 
816
            delta = self.tree1.changes_from(self.tree1.basis_tree())
 
817
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
 
818
                             delta.removed)
 
819
            self.knownFailure("Mac OSX doesn't preserve unicode"
 
820
                              " combining characters.")
 
821
 
853
822
        # Add
854
823
        bundle = self.get_valid_bundle('null:', 'i18n-1')
855
824
 
856
825
        # Modified
857
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
826
        f = open(u'b1/with Dod\xe9', 'wb')
858
827
        f.write(u'Modified \xb5\n'.encode('utf8'))
859
828
        f.close()
860
829
        self.tree1.commit(u'modified', rev_id='i18n-2')
861
830
 
862
831
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
863
 
 
 
832
        
864
833
        # Renamed
865
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
834
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
866
835
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
867
836
                          committer=u'Erik B\xe5gfors')
868
837
 
869
838
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
870
839
 
871
840
        # Removed
872
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
841
        self.tree1.remove([u'B\xe5gfors'])
873
842
        self.tree1.commit(u'removed', rev_id='i18n-4')
874
843
 
875
844
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
880
849
 
881
850
    def test_whitespace_bundle(self):
882
851
        if sys.platform in ('win32', 'cygwin'):
883
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
884
 
                                    ' with tabs or trailing spaces')
 
852
            raise TestSkipped('Windows doesn\'t support filenames'
 
853
                              ' with tabs or trailing spaces')
885
854
        self.tree1 = self.make_branch_and_tree('b1')
886
855
        self.b1 = self.tree1.branch
887
856
 
912
881
        self.tree1.commit('removed', rev_id='white-4')
913
882
 
914
883
        bundle = self.get_valid_bundle('white-3', 'white-4')
915
 
 
 
884
        
916
885
        # Now test a complet roll-up
917
886
        bundle = self.get_valid_bundle('null:', 'white-4')
918
887
 
931
900
                          timezone=19800, timestamp=1152544886.0)
932
901
 
933
902
        bundle = self.get_valid_bundle('null:', 'tz-1')
934
 
 
 
903
        
935
904
        rev = bundle.revisions[0]
936
905
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
937
906
        self.assertEqual(19800, rev.timezone)
1039
1008
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1040
1009
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1041
1010
        bundle.install_revisions(repo)
1042
 
        inv_text = repo._get_inventory_xml('rev2')
 
1011
        inv_text = repo.get_inventory_xml('rev2')
1043
1012
        self.assertNotContainsRe(inv_text, 'format="5"')
1044
1013
        self.assertContainsRe(inv_text, 'format="7"')
1045
1014
 
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
 
 
1055
1015
    def test_across_models(self):
1056
 
        repo = self.make_repo_with_installed_revisions()
 
1016
        tree = self.make_simple_tree('knit')
 
1017
        tree.commit('hello', rev_id='rev1')
 
1018
        tree.commit('hello', rev_id='rev2')
 
1019
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1020
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1021
        bundle.install_revisions(repo)
1057
1022
        inv = repo.get_inventory('rev2')
1058
1023
        self.assertEqual('rev2', inv.root.revision)
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)
 
1024
        root_vf = repo.weave_store.get_weave(inv.root.file_id,
 
1025
                                             repo.get_transaction())
 
1026
        self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
1071
1027
 
1072
1028
    def test_across_models_incompatible(self):
1073
1029
        tree = self.make_simple_tree('dirstate-with-subtree')
1076
1032
        try:
1077
1033
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1078
1034
        except errors.IncompatibleBundleFormat:
1079
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1035
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1080
1036
        repo = self.make_repository('repo', format='knit')
1081
1037
        bundle.install_revisions(repo)
1082
1038
 
1103
1059
        try:
1104
1060
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1105
1061
        except errors.IncompatibleBundleFormat:
1106
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1062
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1107
1063
        if isinstance(bundle, v09.BundleInfo09):
1108
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
 
1064
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
1109
1065
        repo = self.make_repository('repo', format='knit')
1110
1066
        self.assertRaises(errors.IncompatibleRevision,
1111
1067
                          bundle.install_revisions, repo)
1118
1074
        try:
1119
1075
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1120
1076
        except ValueError:
1121
 
            raise tests.TestSkipped(
1122
 
                "Repository doesn't support revision ids with slashes")
 
1077
            raise TestSkipped("Repository doesn't support revision ids with"
 
1078
                              " slashes")
1123
1079
        bundle = self.get_valid_bundle('null:', 'rev/id')
1124
1080
 
1125
1081
    def test_skip_file(self):
1141
1097
        self.tree1.commit('rev3', rev_id='rev3')
1142
1098
        bundle = self.get_valid_bundle('reva', 'rev3')
1143
1099
        if getattr(bundle, 'get_bundle_reader', None) is None:
1144
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
 
1100
            raise TestSkipped('Bundle format cannot provide reader')
1145
1101
        # be sure that file1 comes before file2
1146
1102
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1147
1103
            if f == 'file3-id':
1150
1106
        bundle.install_revisions(target.branch.repository)
1151
1107
 
1152
1108
 
1153
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1109
class V08BundleTester(BundleTester, TestCaseWithTransport):
1154
1110
 
1155
1111
    format = '0.8'
1156
1112
 
1289
1245
        return format
1290
1246
 
1291
1247
 
1292
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1248
class V4BundleTester(BundleTester, TestCaseWithTransport):
1293
1249
 
1294
1250
    format = '4'
1295
1251
 
1297
1253
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1298
1254
        Make sure that the text generated is valid, and that it
1299
1255
        can be applied against the base, and generate the same information.
1300
 
 
1301
 
        :return: The in-memory bundle
 
1256
        
 
1257
        :return: The in-memory bundle 
1302
1258
        """
1303
1259
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1304
1260
 
1305
 
        # This should also validate the generated bundle
 
1261
        # This should also validate the generated bundle 
1306
1262
        bundle = read_bundle(bundle_txt)
1307
1263
        repository = self.b1.repository
1308
1264
        for bundle_rev in bundle.real_revisions:
1312
1268
            # it
1313
1269
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1314
1270
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1315
 
                      'timestamp', 'timezone', 'message', 'committer',
 
1271
                      'timestamp', 'timezone', 'message', 'committer', 
1316
1272
                      'parent_ids', 'properties'):
1317
 
                self.assertEqual(getattr(branch_rev, a),
 
1273
                self.assertEqual(getattr(branch_rev, a), 
1318
1274
                                 getattr(bundle_rev, a))
1319
 
            self.assertEqual(len(branch_rev.parent_ids),
 
1275
            self.assertEqual(len(branch_rev.parent_ids), 
1320
1276
                             len(bundle_rev.parent_ids))
1321
1277
        self.assertEqual(set(rev_ids),
1322
1278
                         set([r.revision_id for r in bundle.real_revisions]))
1336
1292
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1337
1293
        new_text = new_text.replace('<file file_id="exe-1"',
1338
1294
                                    '<file executable="y" file_id="exe-1"')
1339
 
        new_text = new_text.replace('B260', 'B275')
 
1295
        new_text = new_text.replace('B222', 'B237')
1340
1296
        bundle_txt = StringIO()
1341
1297
        bundle_txt.write(serializer._get_bundle_header('4'))
1342
1298
        bundle_txt.write('\n')
1348
1304
 
1349
1305
    def create_bundle_text(self, base_rev_id, rev_id):
1350
1306
        bundle_txt = StringIO()
1351
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
1307
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
1352
1308
                               bundle_txt, format=self.format)
1353
1309
        bundle_txt.seek(0)
1354
 
        self.assertEqual(bundle_txt.readline(),
 
1310
        self.assertEqual(bundle_txt.readline(), 
1355
1311
                         '# Bazaar revision bundle v%s\n' % self.format)
1356
1312
        self.assertEqual(bundle_txt.readline(), '#\n')
1357
1313
        rev = self.b1.repository.get_revision(rev_id)
1377
1333
        tree2 = self.make_branch_and_tree('target')
1378
1334
        target_repo = tree2.branch.repository
1379
1335
        install_bundle(target_repo, serializer.read(s))
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)
 
1336
        vf = target_repo.weave_store.get_weave('fileid-2',
 
1337
            target_repo.get_transaction())
 
1338
        self.assertEqual('contents1\nstatic\n', vf.get_text('rev1'))
 
1339
        self.assertEqual('contents2\nstatic\n', vf.get_text('rev2'))
1390
1340
        rtree = target_repo.revision_tree('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',),)])
 
1341
        inventory_vf = target_repo.get_inventory_weave()
 
1342
        self.assertEqual(['rev1'], inventory_vf.get_parents('rev2'))
1396
1343
        self.assertEqual('changed file',
1397
1344
                         target_repo.get_revision('rev2').message)
1398
1345
 
1448
1395
        return 'metaweave'
1449
1396
 
1450
1397
 
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
 
 
1645
1398
class MungedBundleTester(object):
1646
1399
 
1647
1400
    def build_test_bundle(self):
1694
1447
        self.check_valid(bundle)
1695
1448
 
1696
1449
 
1697
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
 
1450
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
1698
1451
 
1699
1452
    format = '0.9'
1700
1453
 
1732
1485
        self.check_valid(bundle)
1733
1486
 
1734
1487
 
1735
 
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
 
1488
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
1736
1489
 
1737
1490
    format = '4'
1738
1491
 
1739
1492
 
1740
 
class TestBundleWriterReader(tests.TestCase):
 
1493
class TestBundleWriterReader(TestCase):
1741
1494
 
1742
1495
    def test_roundtrip_record(self):
1743
1496
        fileobj = StringIO()
1805
1558
        record = record_iter.next()
1806
1559
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1807
1560
            'info', None, None), record)
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()
 
1561
        self.assertRaises(BadBundle, record_iter.next)