~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Vincent Ladeuil
  • Date: 2007-11-24 14:20:59 UTC
  • mto: (3928.1.1 bzr.integration)
  • mto: This revision was merged to the branch mainline in revision 3929.
  • Revision ID: v.ladeuil+lp@free.fr-20071124142059-2114qtsgfdv8g9p1
Ssl files needed for the test https server.

* bzrlib/tests/ssl_certs/create_ssls.py: 
Script to create the ssl keys and certificates.

* bzrlib/tests/ssl_certs/server.crt: 
Server certificate signed by the certificate authority.

* bzrlib/tests/ssl_certs/server.csr: 
Server certificate signing request.

* bzrlib/tests/ssl_certs/server_without_pass.key: 
Server key usable without password.

* bzrlib/tests/ssl_certs/server_with_pass.key: 
Server key.

* bzrlib/tests/ssl_certs/ca.key: 
Certificate authority private key.

* bzrlib/tests/ssl_certs/ca.crt: 
Certificate authority certificate.

* bzrlib/tests/ssl_certs/__init__.py: 
Provide access to ssl files (keys and certificates). 

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
117
103
        elif kind == 'symlink':
118
104
            ie = InventoryLink(file_id, name, parent_id)
119
105
        else:
120
 
            raise errors.BzrError('unknown kind %r' % kind)
 
106
            raise BzrError('unknown kind %r' % kind)
121
107
        ie.text_sha1 = text_sha_1
122
108
        ie.text_size = text_size
123
109
        return ie
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])
518
498
        sure everything matches the builtin branch.
519
499
        """
520
500
        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):
528
501
        original_parents = to_tree.get_parent_ids()
529
502
        repository = to_tree.branch.repository
530
503
        original_parents = to_tree.get_parent_ids()
531
504
        self.assertIs(repository.has_revision(base_rev_id), True)
532
505
        for rev in info.real_revisions:
533
506
            self.assert_(not repository.has_revision(rev.revision_id),
534
 
                'Revision {%s} present before applying bundle'
 
507
                'Revision {%s} present before applying bundle' 
535
508
                % rev.revision_id)
536
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
509
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
537
510
 
538
511
        for rev in info.real_revisions:
539
512
            self.assert_(repository.has_revision(rev.revision_id),
540
 
                'Missing revision {%s} after applying bundle'
 
513
                'Missing revision {%s} after applying bundle' 
541
514
                % rev.revision_id)
542
515
 
543
516
        self.assert_(to_tree.branch.repository.has_revision(info.target))
549
522
        rev = info.real_revisions[-1]
550
523
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
551
524
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
552
 
 
 
525
        
553
526
        # TODO: make sure the target tree is identical to base tree
554
527
        #       we might also check the working tree.
555
528
 
574
547
        self.tree1 = self.make_branch_and_tree('b1')
575
548
        self.b1 = self.tree1.branch
576
549
 
577
 
        self.build_tree_contents([('b1/one', 'one\n')])
578
 
        self.tree1.add('one', 'one-id')
579
 
        self.tree1.set_root_id('root-id')
 
550
        open('b1/one', 'wb').write('one\n')
 
551
        self.tree1.add('one')
580
552
        self.tree1.commit('add one', rev_id='a@cset-0-1')
581
553
 
582
554
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
593
565
                , 'b1/sub/sub/'
594
566
                , 'b1/sub/sub/nonempty.txt'
595
567
                ])
596
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
597
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
568
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
569
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
598
570
        tt = TreeTransform(self.tree1)
599
571
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
600
572
        tt.apply()
616
588
 
617
589
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
618
590
 
619
 
        # Check a rollup bundle
 
591
        # Check a rollup bundle 
620
592
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
621
593
 
622
594
        # Now delete entries
630
602
        tt.set_executability(False, trans_id)
631
603
        tt.apply()
632
604
        self.tree1.commit('removed', rev_id='a@cset-0-3')
633
 
 
 
605
        
634
606
        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,
 
607
        self.assertRaises((TestamentMismatch,
 
608
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
638
609
            'a@cset-0-2', 'a@cset-0-3')
639
 
        # Check a rollup bundle
 
610
        # Check a rollup bundle 
640
611
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
641
612
 
642
613
        # Now move the directory
644
615
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
645
616
 
646
617
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
647
 
        # Check a rollup bundle
 
618
        # Check a rollup bundle 
648
619
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
649
620
 
650
621
        # Modified files
652
623
        open('b1/sub/dir/ pre space', 'ab').write(
653
624
             '\r\nAdding some\r\nDOS format lines\r\n')
654
625
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
655
 
        self.tree1.rename_one('sub/dir/ pre space',
 
626
        self.tree1.rename_one('sub/dir/ pre space', 
656
627
                              'sub/ start space')
657
628
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
658
629
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
664
635
                          verbose=False)
665
636
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
666
637
        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')
 
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')
671
641
        self.assertEqualDiff(tree1_inv, tree2_inv)
672
642
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
673
643
        other.commit('rename file', rev_id='a@cset-0-6b')
676
646
                          verbose=False)
677
647
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
678
648
 
679
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
680
 
        link_id = 'link-1'
681
 
 
682
 
        self.requireFeature(tests.SymlinkFeature)
 
649
    def test_symlink_bundle(self):
 
650
        self.requireFeature(SymlinkFeature)
683
651
        self.tree1 = self.make_branch_and_tree('b1')
684
652
        self.b1 = self.tree1.branch
685
 
 
686
653
        tt = TreeTransform(self.tree1)
687
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
654
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
688
655
        tt.apply()
689
656
        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
 
 
 
657
        self.get_valid_bundle('null:', 'l@cset-0-1')
696
658
        tt = TreeTransform(self.tree1)
697
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
659
        trans_id = tt.trans_id_tree_file_id('link-1')
698
660
        tt.adjust_path('link2', tt.root, trans_id)
699
661
        tt.delete_contents(trans_id)
700
 
        tt.create_symlink(new_link_target, trans_id)
 
662
        tt.create_symlink('mars', trans_id)
701
663
        tt.apply()
702
664
        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
 
 
 
665
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
710
666
        tt = TreeTransform(self.tree1)
711
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
667
        trans_id = tt.trans_id_tree_file_id('link-1')
712
668
        tt.delete_contents(trans_id)
713
669
        tt.create_symlink('jupiter', trans_id)
714
670
        tt.apply()
715
671
        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
 
 
 
672
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
718
673
        tt = TreeTransform(self.tree1)
719
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
674
        trans_id = tt.trans_id_tree_file_id('link-1')
720
675
        tt.delete_contents(trans_id)
721
676
        tt.apply()
722
677
        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}')
 
678
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
733
679
 
734
680
    def test_binary_bundle(self):
735
681
        self.tree1 = self.make_branch_and_tree('b1')
736
682
        self.b1 = self.tree1.branch
737
683
        tt = TreeTransform(self.tree1)
738
 
 
 
684
        
739
685
        # Add
740
686
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
741
687
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
833
779
        return bundle_file.getvalue()
834
780
 
835
781
    def test_unicode_bundle(self):
836
 
        self.requireFeature(tests.UnicodeFilenameFeature)
837
782
        # Handle international characters
838
783
        os.mkdir('b1')
839
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
784
        try:
 
785
            f = open(u'b1/with Dod\xe9', 'wb')
 
786
        except UnicodeEncodeError:
 
787
            raise TestSkipped("Filesystem doesn't support unicode")
840
788
 
841
789
        self.tree1 = self.make_branch_and_tree('b1')
842
790
        self.b1 = self.tree1.branch
846
794
            u'William Dod\xe9\n').encode('utf-8'))
847
795
        f.close()
848
796
 
849
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
797
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
850
798
        self.tree1.commit(u'i18n commit from William Dod\xe9',
851
799
                          rev_id='i18n-1', committer=u'William Dod\xe9')
852
800
 
 
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
 
853
815
        # Add
854
816
        bundle = self.get_valid_bundle('null:', 'i18n-1')
855
817
 
856
818
        # Modified
857
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
819
        f = open(u'b1/with Dod\xe9', 'wb')
858
820
        f.write(u'Modified \xb5\n'.encode('utf8'))
859
821
        f.close()
860
822
        self.tree1.commit(u'modified', rev_id='i18n-2')
861
823
 
862
824
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
863
 
 
 
825
        
864
826
        # Renamed
865
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
827
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
866
828
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
867
829
                          committer=u'Erik B\xe5gfors')
868
830
 
869
831
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
870
832
 
871
833
        # Removed
872
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
834
        self.tree1.remove([u'B\xe5gfors'])
873
835
        self.tree1.commit(u'removed', rev_id='i18n-4')
874
836
 
875
837
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
880
842
 
881
843
    def test_whitespace_bundle(self):
882
844
        if sys.platform in ('win32', 'cygwin'):
883
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
884
 
                                    ' with tabs or trailing spaces')
 
845
            raise TestSkipped('Windows doesn\'t support filenames'
 
846
                              ' with tabs or trailing spaces')
885
847
        self.tree1 = self.make_branch_and_tree('b1')
886
848
        self.b1 = self.tree1.branch
887
849
 
912
874
        self.tree1.commit('removed', rev_id='white-4')
913
875
 
914
876
        bundle = self.get_valid_bundle('white-3', 'white-4')
915
 
 
 
877
        
916
878
        # Now test a complet roll-up
917
879
        bundle = self.get_valid_bundle('null:', 'white-4')
918
880
 
931
893
                          timezone=19800, timestamp=1152544886.0)
932
894
 
933
895
        bundle = self.get_valid_bundle('null:', 'tz-1')
934
 
 
 
896
        
935
897
        rev = bundle.revisions[0]
936
898
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
937
899
        self.assertEqual(19800, rev.timezone)
1039
1001
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1040
1002
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1041
1003
        bundle.install_revisions(repo)
1042
 
        inv_text = repo._get_inventory_xml('rev2')
 
1004
        inv_text = repo.get_inventory_xml('rev2')
1043
1005
        self.assertNotContainsRe(inv_text, 'format="5"')
1044
1006
        self.assertContainsRe(inv_text, 'format="7"')
1045
1007
 
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
1008
    def test_across_models(self):
1056
 
        repo = self.make_repo_with_installed_revisions()
 
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)
1057
1015
        inv = repo.get_inventory('rev2')
1058
1016
        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)
 
1017
        root_vf = repo.weave_store.get_weave(inv.root.file_id,
 
1018
                                             repo.get_transaction())
 
1019
        self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
1071
1020
 
1072
1021
    def test_across_models_incompatible(self):
1073
1022
        tree = self.make_simple_tree('dirstate-with-subtree')
1076
1025
        try:
1077
1026
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1078
1027
        except errors.IncompatibleBundleFormat:
1079
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1028
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1080
1029
        repo = self.make_repository('repo', format='knit')
1081
1030
        bundle.install_revisions(repo)
1082
1031
 
1103
1052
        try:
1104
1053
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1105
1054
        except errors.IncompatibleBundleFormat:
1106
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1055
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1107
1056
        if isinstance(bundle, v09.BundleInfo09):
1108
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
 
1057
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
1109
1058
        repo = self.make_repository('repo', format='knit')
1110
1059
        self.assertRaises(errors.IncompatibleRevision,
1111
1060
                          bundle.install_revisions, repo)
1118
1067
        try:
1119
1068
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1120
1069
        except ValueError:
1121
 
            raise tests.TestSkipped(
1122
 
                "Repository doesn't support revision ids with slashes")
 
1070
            raise TestSkipped("Repository doesn't support revision ids with"
 
1071
                              " slashes")
1123
1072
        bundle = self.get_valid_bundle('null:', 'rev/id')
1124
1073
 
1125
1074
    def test_skip_file(self):
1141
1090
        self.tree1.commit('rev3', rev_id='rev3')
1142
1091
        bundle = self.get_valid_bundle('reva', 'rev3')
1143
1092
        if getattr(bundle, 'get_bundle_reader', None) is None:
1144
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
 
1093
            raise TestSkipped('Bundle format cannot provide reader')
1145
1094
        # be sure that file1 comes before file2
1146
1095
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1147
1096
            if f == 'file3-id':
1150
1099
        bundle.install_revisions(target.branch.repository)
1151
1100
 
1152
1101
 
1153
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1102
class V08BundleTester(BundleTester, TestCaseWithTransport):
1154
1103
 
1155
1104
    format = '0.8'
1156
1105
 
1289
1238
        return format
1290
1239
 
1291
1240
 
1292
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1241
class V4BundleTester(BundleTester, TestCaseWithTransport):
1293
1242
 
1294
1243
    format = '4'
1295
1244
 
1297
1246
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1298
1247
        Make sure that the text generated is valid, and that it
1299
1248
        can be applied against the base, and generate the same information.
1300
 
 
1301
 
        :return: The in-memory bundle
 
1249
        
 
1250
        :return: The in-memory bundle 
1302
1251
        """
1303
1252
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1304
1253
 
1305
 
        # This should also validate the generated bundle
 
1254
        # This should also validate the generated bundle 
1306
1255
        bundle = read_bundle(bundle_txt)
1307
1256
        repository = self.b1.repository
1308
1257
        for bundle_rev in bundle.real_revisions:
1312
1261
            # it
1313
1262
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1314
1263
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1315
 
                      'timestamp', 'timezone', 'message', 'committer',
 
1264
                      'timestamp', 'timezone', 'message', 'committer', 
1316
1265
                      'parent_ids', 'properties'):
1317
 
                self.assertEqual(getattr(branch_rev, a),
 
1266
                self.assertEqual(getattr(branch_rev, a), 
1318
1267
                                 getattr(bundle_rev, a))
1319
 
            self.assertEqual(len(branch_rev.parent_ids),
 
1268
            self.assertEqual(len(branch_rev.parent_ids), 
1320
1269
                             len(bundle_rev.parent_ids))
1321
1270
        self.assertEqual(set(rev_ids),
1322
1271
                         set([r.revision_id for r in bundle.real_revisions]))
1336
1285
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1337
1286
        new_text = new_text.replace('<file file_id="exe-1"',
1338
1287
                                    '<file executable="y" file_id="exe-1"')
1339
 
        new_text = new_text.replace('B260', 'B275')
 
1288
        new_text = new_text.replace('B222', 'B237')
1340
1289
        bundle_txt = StringIO()
1341
1290
        bundle_txt.write(serializer._get_bundle_header('4'))
1342
1291
        bundle_txt.write('\n')
1348
1297
 
1349
1298
    def create_bundle_text(self, base_rev_id, rev_id):
1350
1299
        bundle_txt = StringIO()
1351
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
1300
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
1352
1301
                               bundle_txt, format=self.format)
1353
1302
        bundle_txt.seek(0)
1354
 
        self.assertEqual(bundle_txt.readline(),
 
1303
        self.assertEqual(bundle_txt.readline(), 
1355
1304
                         '# Bazaar revision bundle v%s\n' % self.format)
1356
1305
        self.assertEqual(bundle_txt.readline(), '#\n')
1357
1306
        rev = self.b1.repository.get_revision(rev_id)
1377
1326
        tree2 = self.make_branch_and_tree('target')
1378
1327
        target_repo = tree2.branch.repository
1379
1328
        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)
 
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'))
1390
1333
        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',),)])
 
1334
        inventory_vf = target_repo.get_inventory_weave()
 
1335
        self.assertEqual(['rev1'], inventory_vf.get_parents('rev2'))
1396
1336
        self.assertEqual('changed file',
1397
1337
                         target_repo.get_revision('rev2').message)
1398
1338
 
1448
1388
        return 'metaweave'
1449
1389
 
1450
1390
 
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
1391
class MungedBundleTester(object):
1646
1392
 
1647
1393
    def build_test_bundle(self):
1694
1440
        self.check_valid(bundle)
1695
1441
 
1696
1442
 
1697
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
 
1443
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
1698
1444
 
1699
1445
    format = '0.9'
1700
1446
 
1732
1478
        self.check_valid(bundle)
1733
1479
 
1734
1480
 
1735
 
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
 
1481
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
1736
1482
 
1737
1483
    format = '4'
1738
1484
 
1739
1485
 
1740
 
class TestBundleWriterReader(tests.TestCase):
 
1486
class TestBundleWriterReader(TestCase):
1741
1487
 
1742
1488
    def test_roundtrip_record(self):
1743
1489
        fileobj = StringIO()
1805
1551
        record = record_iter.next()
1806
1552
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1807
1553
            '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()
 
1554
        self.assertRaises(BadBundle, record_iter.next)