~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Andrew Bennetts
  • Date: 2008-05-21 11:58:09 UTC
  • mto: (3452.2.9 inter-remote-pack)
  • mto: This revision was merged to the branch mainline in revision 3511.
  • Revision ID: andrew.bennetts@canonical.com-20080521115809-6cw3t8gn4qm0bpg9
Remove a bit more debugging cruft.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004-2006 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
from StringIO import StringIO
 
17
from cStringIO import StringIO
 
18
import os
 
19
import sys
 
20
import tempfile
18
21
 
19
 
from bzrlib.builtins import merge
 
22
from bzrlib import (
 
23
    bzrdir,
 
24
    errors,
 
25
    inventory,
 
26
    repository,
 
27
    revision as _mod_revision,
 
28
    treebuilder,
 
29
    )
20
30
from bzrlib.bzrdir import BzrDir
 
31
from bzrlib.bundle import read_mergeable_from_url
21
32
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
22
 
from bzrlib.bundle.read_bundle import BundleTree, BundleReader
23
 
from bzrlib.bundle.serializer import write_bundle
 
33
from bzrlib.bundle.bundle_data import BundleTree
 
34
from bzrlib.directory_service import directories
 
35
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
 
36
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
 
37
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
 
38
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
 
39
from bzrlib.branch import Branch
24
40
from bzrlib.diff import internal_diff
25
 
from bzrlib.errors import BzrError, TestamentMismatch, NotABundle
 
41
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
 
42
                           NoSuchFile,)
26
43
from bzrlib.merge import Merge3Merger
27
 
from bzrlib.osutils import has_symlinks, sha_file
28
 
from bzrlib.tests import TestCaseInTempDir, TestCase, TestSkipped
 
44
from bzrlib.repofmt import knitrepo
 
45
from bzrlib.osutils import sha_file, sha_string
 
46
from bzrlib.tests import (
 
47
    SymlinkFeature,
 
48
    TestCase,
 
49
    TestCaseInTempDir,
 
50
    TestCaseWithTransport,
 
51
    TestSkipped,
 
52
    test_read_bundle,
 
53
    test_commit,
 
54
    )
29
55
from bzrlib.transform import TreeTransform
30
 
from bzrlib.workingtree import WorkingTree
31
56
 
32
57
 
33
58
class MockTree(object):
34
59
    def __init__(self):
35
 
        from bzrlib.inventory import RootEntry, ROOT_ID
 
60
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
36
61
        object.__init__(self)
37
62
        self.paths = {ROOT_ID: ""}
38
63
        self.ids = {"": ROOT_ID}
39
64
        self.contents = {}
40
 
        self.root = RootEntry(ROOT_ID)
 
65
        self.root = InventoryDirectory(ROOT_ID, '', None)
41
66
 
42
67
    inventory = property(lambda x:x)
43
68
 
51
76
            return self.make_entry(file_id, self.paths[file_id])
52
77
 
53
78
    def parent_id(self, file_id):
54
 
        from os.path import dirname
55
 
        parent_dir = dirname(self.paths[file_id])
 
79
        parent_dir = os.path.dirname(self.paths[file_id])
56
80
        if parent_dir == "":
57
81
            return None
58
82
        return self.ids[parent_dir]
69
93
        return kind
70
94
 
71
95
    def make_entry(self, file_id, path):
72
 
        from os.path import basename
73
96
        from bzrlib.inventory import (InventoryEntry, InventoryFile
74
97
                                    , InventoryDirectory, InventoryLink)
75
 
        name = basename(path)
 
98
        name = os.path.basename(path)
76
99
        kind = self.get_file_kind(file_id)
77
100
        parent_id = self.parent_id(file_id)
78
101
        text_sha_1, text_size = self.contents_stats(file_id)
146
169
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
147
170
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
148
171
 
149
 
        assert btree.path2id("grandparent2") is None
150
 
        assert btree.path2id("grandparent2/parent") is None
151
 
        assert btree.path2id("grandparent2/parent/file") is None
 
172
        self.assertTrue(btree.path2id("grandparent2") is None)
 
173
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
174
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
152
175
 
153
176
        btree.note_rename("grandparent", "grandparent2")
154
 
        assert btree.old_path("grandparent") is None
155
 
        assert btree.old_path("grandparent/parent") is None
156
 
        assert btree.old_path("grandparent/parent/file") is None
 
177
        self.assertTrue(btree.old_path("grandparent") is None)
 
178
        self.assertTrue(btree.old_path("grandparent/parent") is None)
 
179
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
157
180
 
158
181
        self.assertEqual(btree.id2path("a"), "grandparent2")
159
182
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
163
186
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
164
187
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
165
188
 
166
 
        assert btree.path2id("grandparent") is None
167
 
        assert btree.path2id("grandparent/parent") is None
168
 
        assert btree.path2id("grandparent/parent/file") is None
 
189
        self.assertTrue(btree.path2id("grandparent") is None)
 
190
        self.assertTrue(btree.path2id("grandparent/parent") is None)
 
191
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
169
192
 
170
193
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
171
194
        self.assertEqual(btree.id2path("a"), "grandparent2")
176
199
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
177
200
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
178
201
 
179
 
        assert btree.path2id("grandparent2/parent") is None
180
 
        assert btree.path2id("grandparent2/parent/file") is None
 
202
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
203
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
181
204
 
182
205
        btree.note_rename("grandparent/parent/file", 
183
206
                          "grandparent2/parent2/file2")
189
212
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
190
213
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
191
214
 
192
 
        assert btree.path2id("grandparent2/parent2/file") is None
 
215
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
193
216
 
194
217
    def test_moves(self):
195
218
        """Ensure that file moves have the proper effect on children"""
198
221
                          "grandparent/alt_parent/file")
199
222
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
200
223
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
201
 
        assert btree.path2id("grandparent/parent/file") is None
 
224
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
202
225
 
203
226
    def unified_diff(self, old, new):
204
227
        out = StringIO()
210
233
        btree = self.make_tree_1()[0]
211
234
        btree.note_rename("grandparent/parent/file", 
212
235
                          "grandparent/alt_parent/file")
213
 
        assert btree.id2path("e") is None
214
 
        assert btree.path2id("grandparent/parent/file") is None
 
236
        self.assertTrue(btree.id2path("e") is None)
 
237
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
215
238
        btree.note_id("e", "grandparent/parent/file")
216
239
        return btree
217
240
 
275
298
        btree = self.make_tree_1()[0]
276
299
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
277
300
        btree.note_deletion("grandparent/parent/file")
278
 
        assert btree.id2path("c") is None
279
 
        assert btree.path2id("grandparent/parent/file") is None
 
301
        self.assertTrue(btree.id2path("c") is None)
 
302
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
280
303
 
281
304
    def sorted_ids(self, tree):
282
305
        ids = list(tree)
286
309
    def test_iteration(self):
287
310
        """Ensure that iteration through ids works properly"""
288
311
        btree = self.make_tree_1()[0]
289
 
        self.assertEqual(self.sorted_ids(btree), ['a', 'b', 'c', 'd'])
 
312
        self.assertEqual(self.sorted_ids(btree),
 
313
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
290
314
        btree.note_deletion("grandparent/parent/file")
291
315
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
292
316
        btree.note_last_changed("grandparent/alt_parent/fool", 
293
317
                                "revisionidiguess")
294
 
        self.assertEqual(self.sorted_ids(btree), ['a', 'b', 'd', 'e'])
295
 
 
296
 
 
297
 
class CSetTester(TestCaseInTempDir):
298
 
 
299
 
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None,
300
 
                         message=None):
301
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
302
 
        Make sure that the text generated is valid, and that it
303
 
        can be applied against the base, and generate the same information.
304
 
        
305
 
        :return: The in-memory bundle 
306
 
        """
307
 
        from cStringIO import StringIO
308
 
 
 
318
        self.assertEqual(self.sorted_ids(btree),
 
319
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
 
320
 
 
321
 
 
322
class BundleTester1(TestCaseWithTransport):
 
323
 
 
324
    def test_mismatched_bundle(self):
 
325
        format = bzrdir.BzrDirMetaFormat1()
 
326
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
327
        serializer = BundleSerializerV08('0.8')
 
328
        b = self.make_branch('.', format=format)
 
329
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
 
330
                          b.repository, [], {}, StringIO())
 
331
 
 
332
    def test_matched_bundle(self):
 
333
        """Don't raise IncompatibleBundleFormat for knit2 and bundle0.9"""
 
334
        format = bzrdir.BzrDirMetaFormat1()
 
335
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
336
        serializer = BundleSerializerV09('0.9')
 
337
        b = self.make_branch('.', format=format)
 
338
        serializer.write(b.repository, [], {}, StringIO())
 
339
 
 
340
    def test_mismatched_model(self):
 
341
        """Try copying a bundle from knit2 to knit1"""
 
342
        format = bzrdir.BzrDirMetaFormat1()
 
343
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
344
        source = self.make_branch_and_tree('source', format=format)
 
345
        source.commit('one', rev_id='one-id')
 
346
        source.commit('two', rev_id='two-id')
 
347
        text = StringIO()
 
348
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
 
349
                     format='0.9')
 
350
        text.seek(0)
 
351
 
 
352
        format = bzrdir.BzrDirMetaFormat1()
 
353
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
354
        target = self.make_branch('target', format=format)
 
355
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
356
                          target.repository, read_bundle(text))
 
357
 
 
358
 
 
359
class BundleTester(object):
 
360
 
 
361
    def bzrdir_format(self):
 
362
        format = bzrdir.BzrDirMetaFormat1()
 
363
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
364
        return format
 
365
 
 
366
    def make_branch_and_tree(self, path, format=None):
 
367
        if format is None:
 
368
            format = self.bzrdir_format()
 
369
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
370
 
 
371
    def make_branch(self, path, format=None):
 
372
        if format is None:
 
373
            format = self.bzrdir_format()
 
374
        return TestCaseWithTransport.make_branch(self, path, format)
 
375
 
 
376
    def create_bundle_text(self, base_rev_id, rev_id):
309
377
        bundle_txt = StringIO()
310
378
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
311
 
                               bundle_txt)
 
379
                               bundle_txt, format=self.format)
312
380
        bundle_txt.seek(0)
313
381
        self.assertEqual(bundle_txt.readline(), 
314
 
                         '# Bazaar revision bundle v0.7\n')
 
382
                         '# Bazaar revision bundle v%s\n' % self.format)
315
383
        self.assertEqual(bundle_txt.readline(), '#\n')
316
384
 
317
385
        rev = self.b1.repository.get_revision(rev_id)
318
386
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
319
387
                         u'# message:\n')
320
 
 
321
 
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
322
388
        bundle_txt.seek(0)
 
389
        return bundle_txt, rev_ids
 
390
 
 
391
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
 
392
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
393
        Make sure that the text generated is valid, and that it
 
394
        can be applied against the base, and generate the same information.
 
395
        
 
396
        :return: The in-memory bundle 
 
397
        """
 
398
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
399
 
323
400
        # This should also validate the generated bundle 
324
 
        bundle = BundleReader(bundle_txt)
 
401
        bundle = read_bundle(bundle_txt)
325
402
        repository = self.b1.repository
326
 
        for bundle_rev in bundle.info.real_revisions:
 
403
        for bundle_rev in bundle.real_revisions:
327
404
            # These really should have already been checked when we read the
328
405
            # bundle, since it computes the sha1 hash for the revision, which
329
406
            # only will match if everything is okay, but lets be explicit about
337
414
            self.assertEqual(len(branch_rev.parent_ids), 
338
415
                             len(bundle_rev.parent_ids))
339
416
        self.assertEqual(rev_ids, 
340
 
                         [r.revision_id for r in bundle.info.real_revisions])
 
417
                         [r.revision_id for r in bundle.real_revisions])
341
418
        self.valid_apply_bundle(base_rev_id, bundle,
342
419
                                   checkout_dir=checkout_dir)
343
420
 
349
426
        
350
427
        :return: The in-memory bundle
351
428
        """
352
 
        from cStringIO import StringIO
353
 
 
354
 
        bundle_txt = StringIO()
355
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
356
 
                               bundle_txt)
357
 
        bundle_txt.seek(0)
358
 
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
359
 
        bundle_txt.seek(0)
 
429
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
360
430
        new_text = bundle_txt.getvalue().replace('executable:no', 
361
431
                                               'executable:yes')
362
432
        bundle_txt = StringIO(new_text)
363
 
        bundle = BundleReader(bundle_txt)
 
433
        bundle = read_bundle(bundle_txt)
364
434
        self.valid_apply_bundle(base_rev_id, bundle)
365
435
        return bundle 
366
436
 
367
437
    def test_non_bundle(self):
368
 
        self.assertRaises(NotABundle, BundleReader, StringIO('#!/bin/sh\n'))
 
438
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
 
439
 
 
440
    def test_malformed(self):
 
441
        self.assertRaises(BadBundle, read_bundle, 
 
442
                          StringIO('# Bazaar revision bundle v'))
 
443
 
 
444
    def test_crlf_bundle(self):
 
445
        try:
 
446
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
 
447
        except BadBundle:
 
448
            # It is currently permitted for bundles with crlf line endings to
 
449
            # make read_bundle raise a BadBundle, but this should be fixed.
 
450
            # Anything else, especially NotABundle, is an error.
 
451
            pass
369
452
 
370
453
    def get_checkout(self, rev_id, checkout_dir=None):
371
454
        """Get a new tree, with the specified revision in it.
372
455
        """
373
 
        from bzrlib.branch import Branch
374
 
        import tempfile
375
456
 
376
457
        if checkout_dir is None:
377
458
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
378
459
        else:
379
 
            import os
380
460
            if not os.path.exists(checkout_dir):
381
461
                os.mkdir(checkout_dir)
382
 
        tree = BzrDir.create_standalone_workingtree(checkout_dir)
 
462
        tree = self.make_branch_and_tree(checkout_dir)
383
463
        s = StringIO()
384
 
        ancestors = write_bundle(self.b1.repository, rev_id, None, s)
 
464
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
 
465
                                 format=self.format)
385
466
        s.seek(0)
386
 
        assert isinstance(s.getvalue(), str), (
387
 
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
388
 
        install_bundle(tree.branch.repository, BundleReader(s))
 
467
        self.assertIsInstance(s.getvalue(), str)
 
468
        install_bundle(tree.branch.repository, read_bundle(s))
389
469
        for ancestor in ancestors:
390
470
            old = self.b1.repository.revision_tree(ancestor)
391
471
            new = tree.branch.repository.revision_tree(ancestor)
 
472
 
 
473
            # Check that there aren't any inventory level changes
 
474
            delta = new.changes_from(old)
 
475
            self.assertFalse(delta.has_changed(),
 
476
                             'Revision %s not copied correctly.'
 
477
                             % (ancestor,))
 
478
 
 
479
            # Now check that the file contents are all correct
392
480
            for inventory_id in old:
393
481
                try:
394
482
                    old_file = old.get_file(inventory_id)
395
 
                except:
 
483
                except NoSuchFile:
396
484
                    continue
397
485
                if old_file is None:
398
486
                    continue
399
487
                self.assertEqual(old_file.read(),
400
488
                                 new.get_file(inventory_id).read())
401
 
        if rev_id is not None:
 
489
        if not _mod_revision.is_null(rev_id):
402
490
            rh = self.b1.revision_history()
403
491
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
404
492
            tree.update()
 
493
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
 
494
            self.assertFalse(delta.has_changed(),
 
495
                             'Working tree has modifications: %s' % delta)
405
496
        return tree
406
497
 
407
 
    def valid_apply_bundle(self, base_rev_id, reader, checkout_dir=None):
 
498
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
408
499
        """Get the base revision, apply the changes, and make
409
500
        sure everything matches the builtin branch.
410
501
        """
411
502
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
 
503
        to_tree.lock_write()
 
504
        try:
 
505
            self._valid_apply_bundle(base_rev_id, info, to_tree)
 
506
        finally:
 
507
            to_tree.unlock()
 
508
 
 
509
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
 
510
        original_parents = to_tree.get_parent_ids()
412
511
        repository = to_tree.branch.repository
 
512
        original_parents = to_tree.get_parent_ids()
413
513
        self.assertIs(repository.has_revision(base_rev_id), True)
414
 
        info = reader.info
415
514
        for rev in info.real_revisions:
416
515
            self.assert_(not repository.has_revision(rev.revision_id),
417
516
                'Revision {%s} present before applying bundle' 
418
517
                % rev.revision_id)
419
 
        merge_bundle(reader, to_tree, True, Merge3Merger, False, False)
 
518
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
420
519
 
421
520
        for rev in info.real_revisions:
422
521
            self.assert_(repository.has_revision(rev.revision_id),
426
525
        self.assert_(to_tree.branch.repository.has_revision(info.target))
427
526
        # Do we also want to verify that all the texts have been added?
428
527
 
429
 
        self.assert_(info.target in to_tree.pending_merges())
430
 
 
 
528
        self.assertEqual(original_parents + [info.target],
 
529
            to_tree.get_parent_ids())
431
530
 
432
531
        rev = info.real_revisions[-1]
433
532
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
454
553
            #         to_tree.get_file(fileid).read())
455
554
 
456
555
    def test_bundle(self):
457
 
 
458
 
        import os, sys
459
 
        pjoin = os.path.join
460
 
 
461
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
556
        self.tree1 = self.make_branch_and_tree('b1')
462
557
        self.b1 = self.tree1.branch
463
558
 
464
 
        open(pjoin('b1/one'), 'wb').write('one\n')
 
559
        open('b1/one', 'wb').write('one\n')
465
560
        self.tree1.add('one')
466
561
        self.tree1.commit('add one', rev_id='a@cset-0-1')
467
562
 
468
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
469
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-1',
470
 
                message='With a specialized message')
 
563
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
471
564
 
472
565
        # Make sure we can handle files with spaces, tabs, other
473
566
        # bogus characters
476
569
                , 'b1/dir/'
477
570
                , 'b1/dir/filein subdir.c'
478
571
                , 'b1/dir/WithCaps.txt'
479
 
                , 'b1/dir/trailing space '
 
572
                , 'b1/dir/ pre space'
480
573
                , 'b1/sub/'
481
574
                , 'b1/sub/sub/'
482
575
                , 'b1/sub/sub/nonempty.txt'
483
 
                # Tabs are not valid in filenames on windows
484
 
                #'b1/with\ttab.txt'
485
576
                ])
486
577
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
487
578
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
488
579
        tt = TreeTransform(self.tree1)
489
580
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
490
581
        tt.apply()
 
582
        # have to fix length of file-id so that we can predictably rewrite
 
583
        # a (length-prefixed) record containing it later.
 
584
        self.tree1.add('with space.txt', 'withspace-id')
491
585
        self.tree1.add([
492
 
                'with space.txt'
493
 
                , 'dir'
 
586
                  'dir'
494
587
                , 'dir/filein subdir.c'
495
588
                , 'dir/WithCaps.txt'
496
 
                , 'dir/trailing space '
 
589
                , 'dir/ pre space'
497
590
                , 'dir/nolastnewline.txt'
498
591
                , 'sub'
499
592
                , 'sub/sub'
505
598
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
506
599
 
507
600
        # Check a rollup bundle 
508
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
 
601
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
509
602
 
510
603
        # Now delete entries
511
604
        self.tree1.remove(
520
613
        self.tree1.commit('removed', rev_id='a@cset-0-3')
521
614
        
522
615
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
523
 
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
524
 
                          'a@cset-0-2', 'a@cset-0-3')
 
616
        self.assertRaises((TestamentMismatch,
 
617
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
618
            'a@cset-0-2', 'a@cset-0-3')
525
619
        # Check a rollup bundle 
526
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
527
 
 
 
620
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
528
621
 
529
622
        # Now move the directory
530
623
        self.tree1.rename_one('dir', 'sub/dir')
532
625
 
533
626
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
534
627
        # Check a rollup bundle 
535
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
 
628
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
536
629
 
537
630
        # Modified files
538
631
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
539
 
        open('b1/sub/dir/trailing space ', 'ab').write('\nAdding some\nDOS format lines\n')
 
632
        open('b1/sub/dir/ pre space', 'ab').write(
 
633
             '\r\nAdding some\r\nDOS format lines\r\n')
540
634
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
541
 
        self.tree1.rename_one('sub/dir/trailing space ', 
542
 
                              'sub/ start and end space ')
 
635
        self.tree1.rename_one('sub/dir/ pre space', 
 
636
                              'sub/ start space')
543
637
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
544
638
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
545
639
 
546
 
        # Handle international characters
547
 
        try:
548
 
            f = open(u'b1/with Dod\xe9', 'wb')
549
 
        except UnicodeEncodeError:
550
 
            raise TestSkipped("Filesystem doesn't support unicode")
551
 
        f.write((u'A file\n'
552
 
            u'With international man of mystery\n'
553
 
            u'William Dod\xe9\n').encode('utf-8'))
554
 
        self.tree1.add([u'with Dod\xe9'])
555
 
        # BUG: (sort of) You must set verbose=False, so that python doesn't try
556
 
        #       and print the name of William Dode as part of the commit
557
 
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
558
 
                          rev_id='a@cset-0-6', committer=u'William Dod\xe9',
559
 
                          verbose=False)
560
 
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
561
640
        self.tree1.rename_one('sub/dir/WithCaps.txt', 'temp')
562
641
        self.tree1.rename_one('with space.txt', 'WithCaps.txt')
563
642
        self.tree1.rename_one('temp', 'with space.txt')
564
 
        self.tree1.commit(u'swap filenames', rev_id='a@cset-0-7',
 
643
        self.tree1.commit(u'swap filenames', rev_id='a@cset-0-6',
 
644
                          verbose=False)
 
645
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
 
646
        other = self.get_checkout('a@cset-0-5')
 
647
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
 
648
            'a@cset-0-5')
 
649
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
650
        self.assertEqualDiff(tree1_inv, tree2_inv)
 
651
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
 
652
        other.commit('rename file', rev_id='a@cset-0-6b')
 
653
        self.tree1.merge_from_branch(other.branch)
 
654
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
565
655
                          verbose=False)
566
656
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
567
 
        other = self.get_checkout('a@cset-0-6')
568
 
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
569
 
        other.commit('rename file', rev_id='a@cset-0-7b')
570
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
571
 
        self.tree1.commit(u'Merge', rev_id='a@cset-0-8',
572
 
                          verbose=False)
573
 
        bundle = self.get_valid_bundle('a@cset-0-7', 'a@cset-0-8')
574
657
 
575
658
    def test_symlink_bundle(self):
576
 
        if not has_symlinks():
577
 
            raise TestSkipped("No symlink support")
578
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
659
        self.requireFeature(SymlinkFeature)
 
660
        self.tree1 = self.make_branch_and_tree('b1')
579
661
        self.b1 = self.tree1.branch
580
662
        tt = TreeTransform(self.tree1)
581
663
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
582
664
        tt.apply()
583
665
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
584
 
        self.get_valid_bundle(None, 'l@cset-0-1')
 
666
        self.get_valid_bundle('null:', 'l@cset-0-1')
585
667
        tt = TreeTransform(self.tree1)
586
668
        trans_id = tt.trans_id_tree_file_id('link-1')
587
669
        tt.adjust_path('link2', tt.root, trans_id)
605
687
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
606
688
 
607
689
    def test_binary_bundle(self):
608
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
690
        self.tree1 = self.make_branch_and_tree('b1')
609
691
        self.b1 = self.tree1.branch
610
692
        tt = TreeTransform(self.tree1)
611
 
        tt.new_file('file', tt.root, '\x00\xff', 'binary-1')
612
 
        tt.new_file('file2', tt.root, '\x00\xff', 'binary-2')
 
693
        
 
694
        # Add
 
695
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
 
696
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
 
697
            'binary-2')
613
698
        tt.apply()
614
699
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
615
 
        self.get_valid_bundle(None, 'b@cset-0-1')
 
700
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
701
 
 
702
        # Delete
616
703
        tt = TreeTransform(self.tree1)
617
704
        trans_id = tt.trans_id_tree_file_id('binary-1')
618
705
        tt.delete_contents(trans_id)
619
706
        tt.apply()
620
707
        self.tree1.commit('delete binary', rev_id='b@cset-0-2')
621
708
        self.get_valid_bundle('b@cset-0-1', 'b@cset-0-2')
 
709
 
 
710
        # Rename & modify
622
711
        tt = TreeTransform(self.tree1)
623
712
        trans_id = tt.trans_id_tree_file_id('binary-2')
624
713
        tt.adjust_path('file3', tt.root, trans_id)
625
714
        tt.delete_contents(trans_id)
626
 
        tt.create_file('filecontents\x00', trans_id)
 
715
        tt.create_file('file\rcontents\x00\n\x00', trans_id)
627
716
        tt.apply()
628
717
        self.tree1.commit('rename and modify binary', rev_id='b@cset-0-3')
629
718
        self.get_valid_bundle('b@cset-0-2', 'b@cset-0-3')
 
719
 
 
720
        # Modify
630
721
        tt = TreeTransform(self.tree1)
631
722
        trans_id = tt.trans_id_tree_file_id('binary-2')
632
723
        tt.delete_contents(trans_id)
633
 
        tt.create_file('\x00filecontents', trans_id)
 
724
        tt.create_file('\x00file\rcontents', trans_id)
634
725
        tt.apply()
635
726
        self.tree1.commit('just modify binary', rev_id='b@cset-0-4')
636
727
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
637
728
 
 
729
        # Rollup
 
730
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
731
 
638
732
    def test_last_modified(self):
639
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
733
        self.tree1 = self.make_branch_and_tree('b1')
640
734
        self.b1 = self.tree1.branch
641
735
        tt = TreeTransform(self.tree1)
642
736
        tt.new_file('file', tt.root, 'file', 'file')
657
751
        tt.create_file('file2', trans_id)
658
752
        tt.apply()
659
753
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
660
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
 
754
        self.tree1.merge_from_branch(other.branch)
661
755
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
662
756
                          verbose=False)
663
757
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
664
758
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
665
759
 
666
760
    def test_hide_history(self):
667
 
        import os, sys
668
 
        pjoin = os.path.join
669
 
 
670
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
761
        self.tree1 = self.make_branch_and_tree('b1')
671
762
        self.b1 = self.tree1.branch
672
763
 
673
 
        open(pjoin('b1/one'), 'wb').write('one\n')
 
764
        open('b1/one', 'wb').write('one\n')
674
765
        self.tree1.add('one')
675
766
        self.tree1.commit('add file', rev_id='a@cset-0-1')
676
 
        open(pjoin('b1/one'), 'wb').write('two\n')
 
767
        open('b1/one', 'wb').write('two\n')
677
768
        self.tree1.commit('modify', rev_id='a@cset-0-2')
678
 
        open(pjoin('b1/one'), 'wb').write('three\n')
 
769
        open('b1/one', 'wb').write('three\n')
679
770
        self.tree1.commit('modify', rev_id='a@cset-0-3')
680
771
        bundle_file = StringIO()
681
772
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
 
773
                               'a@cset-0-1', bundle_file, format=self.format)
 
774
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
 
775
        self.assertContainsRe(self.get_raw(bundle_file), 'one')
 
776
        self.assertContainsRe(self.get_raw(bundle_file), 'three')
 
777
 
 
778
    def test_bundle_same_basis(self):
 
779
        """Ensure using the basis as the target doesn't cause an error"""
 
780
        self.tree1 = self.make_branch_and_tree('b1')
 
781
        self.tree1.commit('add file', rev_id='a@cset-0-1')
 
782
        bundle_file = StringIO()
 
783
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-1',
682
784
                               'a@cset-0-1', bundle_file)
683
 
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
684
 
        self.assertContainsRe(bundle_file.getvalue(), 'one')
685
 
        self.assertContainsRe(bundle_file.getvalue(), 'three')
 
785
 
 
786
    @staticmethod
 
787
    def get_raw(bundle_file):
 
788
        return bundle_file.getvalue()
 
789
 
 
790
    def test_unicode_bundle(self):
 
791
        # Handle international characters
 
792
        os.mkdir('b1')
 
793
        try:
 
794
            f = open(u'b1/with Dod\xe9', 'wb')
 
795
        except UnicodeEncodeError:
 
796
            raise TestSkipped("Filesystem doesn't support unicode")
 
797
 
 
798
        self.tree1 = self.make_branch_and_tree('b1')
 
799
        self.b1 = self.tree1.branch
 
800
 
 
801
        f.write((u'A file\n'
 
802
            u'With international man of mystery\n'
 
803
            u'William Dod\xe9\n').encode('utf-8'))
 
804
        f.close()
 
805
 
 
806
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
 
807
        self.tree1.commit(u'i18n commit from William Dod\xe9',
 
808
                          rev_id='i18n-1', committer=u'William Dod\xe9')
 
809
 
 
810
        if sys.platform == 'darwin':
 
811
            from bzrlib.workingtree import WorkingTree3
 
812
            if type(self.tree1) is WorkingTree3:
 
813
                self.knownFailure("Bug #141438: fails for WorkingTree3 on OSX")
 
814
 
 
815
            # On Mac the '\xe9' gets changed to 'e\u0301'
 
816
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
 
817
                             sorted(os.listdir(u'b1')))
 
818
            delta = self.tree1.changes_from(self.tree1.basis_tree())
 
819
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
 
820
                             delta.removed)
 
821
            self.knownFailure("Mac OSX doesn't preserve unicode"
 
822
                              " combining characters.")
 
823
 
 
824
        # Add
 
825
        bundle = self.get_valid_bundle('null:', 'i18n-1')
 
826
 
 
827
        # Modified
 
828
        f = open(u'b1/with Dod\xe9', 'wb')
 
829
        f.write(u'Modified \xb5\n'.encode('utf8'))
 
830
        f.close()
 
831
        self.tree1.commit(u'modified', rev_id='i18n-2')
 
832
 
 
833
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
 
834
        
 
835
        # Renamed
 
836
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
 
837
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
 
838
                          committer=u'Erik B\xe5gfors')
 
839
 
 
840
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
 
841
 
 
842
        # Removed
 
843
        self.tree1.remove([u'B\xe5gfors'])
 
844
        self.tree1.commit(u'removed', rev_id='i18n-4')
 
845
 
 
846
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
 
847
 
 
848
        # Rollup
 
849
        bundle = self.get_valid_bundle('null:', 'i18n-4')
 
850
 
 
851
 
 
852
    def test_whitespace_bundle(self):
 
853
        if sys.platform in ('win32', 'cygwin'):
 
854
            raise TestSkipped('Windows doesn\'t support filenames'
 
855
                              ' with tabs or trailing spaces')
 
856
        self.tree1 = self.make_branch_and_tree('b1')
 
857
        self.b1 = self.tree1.branch
 
858
 
 
859
        self.build_tree(['b1/trailing space '])
 
860
        self.tree1.add(['trailing space '])
 
861
        # TODO: jam 20060701 Check for handling files with '\t' characters
 
862
        #       once we actually support them
 
863
 
 
864
        # Added
 
865
        self.tree1.commit('funky whitespace', rev_id='white-1')
 
866
 
 
867
        bundle = self.get_valid_bundle('null:', 'white-1')
 
868
 
 
869
        # Modified
 
870
        open('b1/trailing space ', 'ab').write('add some text\n')
 
871
        self.tree1.commit('add text', rev_id='white-2')
 
872
 
 
873
        bundle = self.get_valid_bundle('white-1', 'white-2')
 
874
 
 
875
        # Renamed
 
876
        self.tree1.rename_one('trailing space ', ' start and end space ')
 
877
        self.tree1.commit('rename', rev_id='white-3')
 
878
 
 
879
        bundle = self.get_valid_bundle('white-2', 'white-3')
 
880
 
 
881
        # Removed
 
882
        self.tree1.remove([' start and end space '])
 
883
        self.tree1.commit('removed', rev_id='white-4')
 
884
 
 
885
        bundle = self.get_valid_bundle('white-3', 'white-4')
 
886
        
 
887
        # Now test a complet roll-up
 
888
        bundle = self.get_valid_bundle('null:', 'white-4')
 
889
 
 
890
    def test_alt_timezone_bundle(self):
 
891
        self.tree1 = self.make_branch_and_memory_tree('b1')
 
892
        self.b1 = self.tree1.branch
 
893
        builder = treebuilder.TreeBuilder()
 
894
 
 
895
        self.tree1.lock_write()
 
896
        builder.start_tree(self.tree1)
 
897
        builder.build(['newfile'])
 
898
        builder.finish_tree()
 
899
 
 
900
        # Asia/Colombo offset = 5 hours 30 minutes
 
901
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
 
902
                          timezone=19800, timestamp=1152544886.0)
 
903
 
 
904
        bundle = self.get_valid_bundle('null:', 'tz-1')
 
905
        
 
906
        rev = bundle.revisions[0]
 
907
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
 
908
        self.assertEqual(19800, rev.timezone)
 
909
        self.assertEqual(1152544886.0, rev.timestamp)
 
910
        self.tree1.unlock()
 
911
 
 
912
    def test_bundle_root_id(self):
 
913
        self.tree1 = self.make_branch_and_tree('b1')
 
914
        self.b1 = self.tree1.branch
 
915
        self.tree1.commit('message', rev_id='revid1')
 
916
        bundle = self.get_valid_bundle('null:', 'revid1')
 
917
        tree = self.get_bundle_tree(bundle, 'revid1')
 
918
        self.assertEqual('revid1', tree.inventory.root.revision)
 
919
 
 
920
    def test_install_revisions(self):
 
921
        self.tree1 = self.make_branch_and_tree('b1')
 
922
        self.b1 = self.tree1.branch
 
923
        self.tree1.commit('message', rev_id='rev2a')
 
924
        bundle = self.get_valid_bundle('null:', 'rev2a')
 
925
        branch2 = self.make_branch('b2')
 
926
        self.assertFalse(branch2.repository.has_revision('rev2a'))
 
927
        target_revision = bundle.install_revisions(branch2.repository)
 
928
        self.assertTrue(branch2.repository.has_revision('rev2a'))
 
929
        self.assertEqual('rev2a', target_revision)
 
930
 
 
931
    def test_bundle_empty_property(self):
 
932
        """Test serializing revision properties with an empty value."""
 
933
        tree = self.make_branch_and_memory_tree('tree')
 
934
        tree.lock_write()
 
935
        self.addCleanup(tree.unlock)
 
936
        tree.add([''], ['TREE_ROOT'])
 
937
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
938
        self.b1 = tree.branch
 
939
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
940
        bundle = read_bundle(bundle_sio)
 
941
        revision_info = bundle.revisions[0]
 
942
        self.assertEqual('rev1', revision_info.revision_id)
 
943
        rev = revision_info.as_revision()
 
944
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
945
                         rev.properties)
 
946
 
 
947
    def test_bundle_sorted_properties(self):
 
948
        """For stability the writer should write properties in sorted order."""
 
949
        tree = self.make_branch_and_memory_tree('tree')
 
950
        tree.lock_write()
 
951
        self.addCleanup(tree.unlock)
 
952
 
 
953
        tree.add([''], ['TREE_ROOT'])
 
954
        tree.commit('One', rev_id='rev1',
 
955
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
956
        self.b1 = tree.branch
 
957
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
958
        bundle = read_bundle(bundle_sio)
 
959
        revision_info = bundle.revisions[0]
 
960
        self.assertEqual('rev1', revision_info.revision_id)
 
961
        rev = revision_info.as_revision()
 
962
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
 
963
                          'd':'1'}, rev.properties)
 
964
 
 
965
    def test_bundle_unicode_properties(self):
 
966
        """We should be able to round trip a non-ascii property."""
 
967
        tree = self.make_branch_and_memory_tree('tree')
 
968
        tree.lock_write()
 
969
        self.addCleanup(tree.unlock)
 
970
 
 
971
        tree.add([''], ['TREE_ROOT'])
 
972
        # Revisions themselves do not require anything about revision property
 
973
        # keys, other than that they are a basestring, and do not contain
 
974
        # whitespace.
 
975
        # However, Testaments assert than they are str(), and thus should not
 
976
        # be Unicode.
 
977
        tree.commit('One', rev_id='rev1',
 
978
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
979
        self.b1 = tree.branch
 
980
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
981
        bundle = read_bundle(bundle_sio)
 
982
        revision_info = bundle.revisions[0]
 
983
        self.assertEqual('rev1', revision_info.revision_id)
 
984
        rev = revision_info.as_revision()
 
985
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
 
986
                          'alpha':u'\u03b1'}, rev.properties)
 
987
 
 
988
    def test_bundle_with_ghosts(self):
 
989
        tree = self.make_branch_and_tree('tree')
 
990
        self.b1 = tree.branch
 
991
        self.build_tree_contents([('tree/file', 'content1')])
 
992
        tree.add(['file'])
 
993
        tree.commit('rev1')
 
994
        self.build_tree_contents([('tree/file', 'content2')])
 
995
        tree.add_parent_tree_id('ghost')
 
996
        tree.commit('rev2', rev_id='rev2')
 
997
        bundle = self.get_valid_bundle('null:', 'rev2')
 
998
 
 
999
    def make_simple_tree(self, format=None):
 
1000
        tree = self.make_branch_and_tree('b1', format=format)
 
1001
        self.b1 = tree.branch
 
1002
        self.build_tree(['b1/file'])
 
1003
        tree.add('file')
 
1004
        return tree
 
1005
 
 
1006
    def test_across_serializers(self):
 
1007
        tree = self.make_simple_tree('knit')
 
1008
        tree.commit('hello', rev_id='rev1')
 
1009
        tree.commit('hello', rev_id='rev2')
 
1010
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1011
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1012
        bundle.install_revisions(repo)
 
1013
        inv_text = repo.get_inventory_xml('rev2')
 
1014
        self.assertNotContainsRe(inv_text, 'format="5"')
 
1015
        self.assertContainsRe(inv_text, 'format="7"')
 
1016
 
 
1017
    def make_repo_with_installed_revisions(self):
 
1018
        tree = self.make_simple_tree('knit')
 
1019
        tree.commit('hello', rev_id='rev1')
 
1020
        tree.commit('hello', rev_id='rev2')
 
1021
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1022
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1023
        bundle.install_revisions(repo)
 
1024
        return repo
 
1025
 
 
1026
    def test_across_models(self):
 
1027
        repo = self.make_repo_with_installed_revisions()
 
1028
        inv = repo.get_inventory('rev2')
 
1029
        self.assertEqual('rev2', inv.root.revision)
 
1030
        root_vf = repo.weave_store.get_weave(inv.root.file_id,
 
1031
                                             repo.get_transaction())
 
1032
        self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
 
1033
 
 
1034
    def test_inv_hash_across_serializers(self):
 
1035
        repo = self.make_repo_with_installed_revisions()
 
1036
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
 
1037
        xml = repo.get_inventory_xml('rev2')
 
1038
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
 
1039
 
 
1040
    def test_across_models_incompatible(self):
 
1041
        tree = self.make_simple_tree('dirstate-with-subtree')
 
1042
        tree.commit('hello', rev_id='rev1')
 
1043
        tree.commit('hello', rev_id='rev2')
 
1044
        try:
 
1045
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1046
        except errors.IncompatibleBundleFormat:
 
1047
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1048
        repo = self.make_repository('repo', format='knit')
 
1049
        bundle.install_revisions(repo)
 
1050
 
 
1051
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1052
        self.assertRaises(errors.IncompatibleRevision,
 
1053
                          bundle.install_revisions, repo)
 
1054
 
 
1055
    def test_get_merge_request(self):
 
1056
        tree = self.make_simple_tree()
 
1057
        tree.commit('hello', rev_id='rev1')
 
1058
        tree.commit('hello', rev_id='rev2')
 
1059
        bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1060
        result = bundle.get_merge_request(tree.branch.repository)
 
1061
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
 
1062
 
 
1063
    def test_with_subtree(self):
 
1064
        tree = self.make_branch_and_tree('tree',
 
1065
                                         format='dirstate-with-subtree')
 
1066
        self.b1 = tree.branch
 
1067
        subtree = self.make_branch_and_tree('tree/subtree',
 
1068
                                            format='dirstate-with-subtree')
 
1069
        tree.add('subtree')
 
1070
        tree.commit('hello', rev_id='rev1')
 
1071
        try:
 
1072
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1073
        except errors.IncompatibleBundleFormat:
 
1074
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1075
        if isinstance(bundle, v09.BundleInfo09):
 
1076
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
 
1077
        repo = self.make_repository('repo', format='knit')
 
1078
        self.assertRaises(errors.IncompatibleRevision,
 
1079
                          bundle.install_revisions, repo)
 
1080
        repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
 
1081
        bundle.install_revisions(repo2)
 
1082
 
 
1083
    def test_revision_id_with_slash(self):
 
1084
        self.tree1 = self.make_branch_and_tree('tree')
 
1085
        self.b1 = self.tree1.branch
 
1086
        try:
 
1087
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
 
1088
        except ValueError:
 
1089
            raise TestSkipped("Repository doesn't support revision ids with"
 
1090
                              " slashes")
 
1091
        bundle = self.get_valid_bundle('null:', 'rev/id')
 
1092
 
 
1093
    def test_skip_file(self):
 
1094
        """Make sure we don't accidentally write to the wrong versionedfile"""
 
1095
        self.tree1 = self.make_branch_and_tree('tree')
 
1096
        self.b1 = self.tree1.branch
 
1097
        # rev1 is not present in bundle, done by fetch
 
1098
        self.build_tree_contents([('tree/file2', 'contents1')])
 
1099
        self.tree1.add('file2', 'file2-id')
 
1100
        self.tree1.commit('rev1', rev_id='reva')
 
1101
        self.build_tree_contents([('tree/file3', 'contents2')])
 
1102
        # rev2 is present in bundle, and done by fetch
 
1103
        # having file1 in the bunle causes file1's versionedfile to be opened.
 
1104
        self.tree1.add('file3', 'file3-id')
 
1105
        self.tree1.commit('rev2')
 
1106
        # Updating file2 should not cause an attempt to add to file1's vf
 
1107
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
 
1108
        self.build_tree_contents([('tree/file2', 'contents3')])
 
1109
        self.tree1.commit('rev3', rev_id='rev3')
 
1110
        bundle = self.get_valid_bundle('reva', 'rev3')
 
1111
        if getattr(bundle, 'get_bundle_reader', None) is None:
 
1112
            raise TestSkipped('Bundle format cannot provide reader')
 
1113
        # be sure that file1 comes before file2
 
1114
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
 
1115
            if f == 'file3-id':
 
1116
                break
 
1117
            self.assertNotEqual(f, 'file2-id')
 
1118
        bundle.install_revisions(target.branch.repository)
 
1119
 
 
1120
 
 
1121
class V08BundleTester(BundleTester, TestCaseWithTransport):
 
1122
 
 
1123
    format = '0.8'
 
1124
 
 
1125
    def test_bundle_empty_property(self):
 
1126
        """Test serializing revision properties with an empty value."""
 
1127
        tree = self.make_branch_and_memory_tree('tree')
 
1128
        tree.lock_write()
 
1129
        self.addCleanup(tree.unlock)
 
1130
        tree.add([''], ['TREE_ROOT'])
 
1131
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1132
        self.b1 = tree.branch
 
1133
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1134
        self.assertContainsRe(bundle_sio.getvalue(),
 
1135
                              '# properties:\n'
 
1136
                              '#   branch-nick: tree\n'
 
1137
                              '#   empty: \n'
 
1138
                              '#   one: two\n'
 
1139
                             )
 
1140
        bundle = read_bundle(bundle_sio)
 
1141
        revision_info = bundle.revisions[0]
 
1142
        self.assertEqual('rev1', revision_info.revision_id)
 
1143
        rev = revision_info.as_revision()
 
1144
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1145
                         rev.properties)
 
1146
 
 
1147
    def get_bundle_tree(self, bundle, revision_id):
 
1148
        repository = self.make_repository('repo')
 
1149
        return bundle.revision_tree(repository, 'revid1')
 
1150
 
 
1151
    def test_bundle_empty_property_alt(self):
 
1152
        """Test serializing revision properties with an empty value.
 
1153
 
 
1154
        Older readers had a bug when reading an empty property.
 
1155
        They assumed that all keys ended in ': \n'. However they would write an
 
1156
        empty value as ':\n'. This tests make sure that all newer bzr versions
 
1157
        can handle th second form.
 
1158
        """
 
1159
        tree = self.make_branch_and_memory_tree('tree')
 
1160
        tree.lock_write()
 
1161
        self.addCleanup(tree.unlock)
 
1162
        tree.add([''], ['TREE_ROOT'])
 
1163
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1164
        self.b1 = tree.branch
 
1165
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1166
        txt = bundle_sio.getvalue()
 
1167
        loc = txt.find('#   empty: ') + len('#   empty:')
 
1168
        # Create a new bundle, which strips the trailing space after empty
 
1169
        bundle_sio = StringIO(txt[:loc] + txt[loc+1:])
 
1170
 
 
1171
        self.assertContainsRe(bundle_sio.getvalue(),
 
1172
                              '# properties:\n'
 
1173
                              '#   branch-nick: tree\n'
 
1174
                              '#   empty:\n'
 
1175
                              '#   one: two\n'
 
1176
                             )
 
1177
        bundle = read_bundle(bundle_sio)
 
1178
        revision_info = bundle.revisions[0]
 
1179
        self.assertEqual('rev1', revision_info.revision_id)
 
1180
        rev = revision_info.as_revision()
 
1181
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1182
                         rev.properties)
 
1183
 
 
1184
    def test_bundle_sorted_properties(self):
 
1185
        """For stability the writer should write properties in sorted order."""
 
1186
        tree = self.make_branch_and_memory_tree('tree')
 
1187
        tree.lock_write()
 
1188
        self.addCleanup(tree.unlock)
 
1189
 
 
1190
        tree.add([''], ['TREE_ROOT'])
 
1191
        tree.commit('One', rev_id='rev1',
 
1192
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
1193
        self.b1 = tree.branch
 
1194
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1195
        self.assertContainsRe(bundle_sio.getvalue(),
 
1196
                              '# properties:\n'
 
1197
                              '#   a: 4\n'
 
1198
                              '#   b: 3\n'
 
1199
                              '#   branch-nick: tree\n'
 
1200
                              '#   c: 2\n'
 
1201
                              '#   d: 1\n'
 
1202
                             )
 
1203
        bundle = read_bundle(bundle_sio)
 
1204
        revision_info = bundle.revisions[0]
 
1205
        self.assertEqual('rev1', revision_info.revision_id)
 
1206
        rev = revision_info.as_revision()
 
1207
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
 
1208
                          'd':'1'}, rev.properties)
 
1209
 
 
1210
    def test_bundle_unicode_properties(self):
 
1211
        """We should be able to round trip a non-ascii property."""
 
1212
        tree = self.make_branch_and_memory_tree('tree')
 
1213
        tree.lock_write()
 
1214
        self.addCleanup(tree.unlock)
 
1215
 
 
1216
        tree.add([''], ['TREE_ROOT'])
 
1217
        # Revisions themselves do not require anything about revision property
 
1218
        # keys, other than that they are a basestring, and do not contain
 
1219
        # whitespace.
 
1220
        # However, Testaments assert than they are str(), and thus should not
 
1221
        # be Unicode.
 
1222
        tree.commit('One', rev_id='rev1',
 
1223
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
1224
        self.b1 = tree.branch
 
1225
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1226
        self.assertContainsRe(bundle_sio.getvalue(),
 
1227
                              '# properties:\n'
 
1228
                              '#   alpha: \xce\xb1\n'
 
1229
                              '#   branch-nick: tree\n'
 
1230
                              '#   omega: \xce\xa9\n'
 
1231
                             )
 
1232
        bundle = read_bundle(bundle_sio)
 
1233
        revision_info = bundle.revisions[0]
 
1234
        self.assertEqual('rev1', revision_info.revision_id)
 
1235
        rev = revision_info.as_revision()
 
1236
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
 
1237
                          'alpha':u'\u03b1'}, rev.properties)
 
1238
 
 
1239
 
 
1240
class V09BundleKnit2Tester(V08BundleTester):
 
1241
 
 
1242
    format = '0.9'
 
1243
 
 
1244
    def bzrdir_format(self):
 
1245
        format = bzrdir.BzrDirMetaFormat1()
 
1246
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
1247
        return format
 
1248
 
 
1249
 
 
1250
class V09BundleKnit1Tester(V08BundleTester):
 
1251
 
 
1252
    format = '0.9'
 
1253
 
 
1254
    def bzrdir_format(self):
 
1255
        format = bzrdir.BzrDirMetaFormat1()
 
1256
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
1257
        return format
 
1258
 
 
1259
 
 
1260
class V4BundleTester(BundleTester, TestCaseWithTransport):
 
1261
 
 
1262
    format = '4'
 
1263
 
 
1264
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
 
1265
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1266
        Make sure that the text generated is valid, and that it
 
1267
        can be applied against the base, and generate the same information.
 
1268
        
 
1269
        :return: The in-memory bundle 
 
1270
        """
 
1271
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1272
 
 
1273
        # This should also validate the generated bundle 
 
1274
        bundle = read_bundle(bundle_txt)
 
1275
        repository = self.b1.repository
 
1276
        for bundle_rev in bundle.real_revisions:
 
1277
            # These really should have already been checked when we read the
 
1278
            # bundle, since it computes the sha1 hash for the revision, which
 
1279
            # only will match if everything is okay, but lets be explicit about
 
1280
            # it
 
1281
            branch_rev = repository.get_revision(bundle_rev.revision_id)
 
1282
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
 
1283
                      'timestamp', 'timezone', 'message', 'committer', 
 
1284
                      'parent_ids', 'properties'):
 
1285
                self.assertEqual(getattr(branch_rev, a), 
 
1286
                                 getattr(bundle_rev, a))
 
1287
            self.assertEqual(len(branch_rev.parent_ids), 
 
1288
                             len(bundle_rev.parent_ids))
 
1289
        self.assertEqual(set(rev_ids),
 
1290
                         set([r.revision_id for r in bundle.real_revisions]))
 
1291
        self.valid_apply_bundle(base_rev_id, bundle,
 
1292
                                   checkout_dir=checkout_dir)
 
1293
 
 
1294
        return bundle
 
1295
 
 
1296
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
1297
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1298
        Munge the text so that it's invalid.
 
1299
 
 
1300
        :return: The in-memory bundle
 
1301
        """
 
1302
        from bzrlib.bundle import serializer
 
1303
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1304
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1305
        new_text = new_text.replace('<file file_id="exe-1"',
 
1306
                                    '<file executable="y" file_id="exe-1"')
 
1307
        new_text = new_text.replace('B222', 'B237')
 
1308
        bundle_txt = StringIO()
 
1309
        bundle_txt.write(serializer._get_bundle_header('4'))
 
1310
        bundle_txt.write('\n')
 
1311
        bundle_txt.write(new_text.encode('bz2'))
 
1312
        bundle_txt.seek(0)
 
1313
        bundle = read_bundle(bundle_txt)
 
1314
        self.valid_apply_bundle(base_rev_id, bundle)
 
1315
        return bundle
 
1316
 
 
1317
    def create_bundle_text(self, base_rev_id, rev_id):
 
1318
        bundle_txt = StringIO()
 
1319
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
1320
                               bundle_txt, format=self.format)
 
1321
        bundle_txt.seek(0)
 
1322
        self.assertEqual(bundle_txt.readline(), 
 
1323
                         '# Bazaar revision bundle v%s\n' % self.format)
 
1324
        self.assertEqual(bundle_txt.readline(), '#\n')
 
1325
        rev = self.b1.repository.get_revision(rev_id)
 
1326
        bundle_txt.seek(0)
 
1327
        return bundle_txt, rev_ids
 
1328
 
 
1329
    def get_bundle_tree(self, bundle, revision_id):
 
1330
        repository = self.make_repository('repo')
 
1331
        bundle.install_revisions(repository)
 
1332
        return repository.revision_tree(revision_id)
 
1333
 
 
1334
    def test_creation(self):
 
1335
        tree = self.make_branch_and_tree('tree')
 
1336
        self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
 
1337
        tree.add('file', 'fileid-2')
 
1338
        tree.commit('added file', rev_id='rev1')
 
1339
        self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
 
1340
        tree.commit('changed file', rev_id='rev2')
 
1341
        s = StringIO()
 
1342
        serializer = BundleSerializerV4('1.0')
 
1343
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
 
1344
        s.seek(0)
 
1345
        tree2 = self.make_branch_and_tree('target')
 
1346
        target_repo = tree2.branch.repository
 
1347
        install_bundle(target_repo, serializer.read(s))
 
1348
        vf = target_repo.weave_store.get_weave('fileid-2',
 
1349
            target_repo.get_transaction())
 
1350
        self.assertEqual('contents1\nstatic\n', vf.get_text('rev1'))
 
1351
        self.assertEqual('contents2\nstatic\n', vf.get_text('rev2'))
 
1352
        rtree = target_repo.revision_tree('rev2')
 
1353
        inventory_vf = target_repo.get_inventory_weave()
 
1354
        self.assertEqual({'rev2':('rev1',)},
 
1355
            inventory_vf.get_parent_map(['rev2']))
 
1356
        self.assertEqual('changed file',
 
1357
                         target_repo.get_revision('rev2').message)
 
1358
 
 
1359
    @staticmethod
 
1360
    def get_raw(bundle_file):
 
1361
        bundle_file.seek(0)
 
1362
        line = bundle_file.readline()
 
1363
        line = bundle_file.readline()
 
1364
        lines = bundle_file.readlines()
 
1365
        return ''.join(lines).decode('bz2')
 
1366
 
 
1367
    def test_copy_signatures(self):
 
1368
        tree_a = self.make_branch_and_tree('tree_a')
 
1369
        import bzrlib.gpg
 
1370
        import bzrlib.commit as commit
 
1371
        oldstrategy = bzrlib.gpg.GPGStrategy
 
1372
        branch = tree_a.branch
 
1373
        repo_a = branch.repository
 
1374
        tree_a.commit("base", allow_pointless=True, rev_id='A')
 
1375
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1376
        try:
 
1377
            from bzrlib.testament import Testament
 
1378
            # monkey patch gpg signing mechanism
 
1379
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
 
1380
            new_config = test_commit.MustSignConfig(branch)
 
1381
            commit.Commit(config=new_config).commit(message="base",
 
1382
                                                    allow_pointless=True,
 
1383
                                                    rev_id='B',
 
1384
                                                    working_tree=tree_a)
 
1385
            def sign(text):
 
1386
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
 
1387
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
 
1388
        finally:
 
1389
            bzrlib.gpg.GPGStrategy = oldstrategy
 
1390
        tree_b = self.make_branch_and_tree('tree_b')
 
1391
        repo_b = tree_b.branch.repository
 
1392
        s = StringIO()
 
1393
        serializer = BundleSerializerV4('4')
 
1394
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
 
1395
        s.seek(0)
 
1396
        install_bundle(repo_b, serializer.read(s))
 
1397
        self.assertTrue(repo_b.has_signature_for_revision_id('B'))
 
1398
        self.assertEqual(repo_b.get_signature_text('B'),
 
1399
                         repo_a.get_signature_text('B'))
 
1400
        s.seek(0)
 
1401
        # ensure repeat installs are harmless
 
1402
        install_bundle(repo_b, serializer.read(s))
 
1403
 
 
1404
 
 
1405
class V4WeaveBundleTester(V4BundleTester):
 
1406
 
 
1407
    def bzrdir_format(self):
 
1408
        return 'metaweave'
 
1409
 
 
1410
 
 
1411
class MungedBundleTester(object):
 
1412
 
 
1413
    def build_test_bundle(self):
 
1414
        wt = self.make_branch_and_tree('b1')
 
1415
 
 
1416
        self.build_tree(['b1/one'])
 
1417
        wt.add('one')
 
1418
        wt.commit('add one', rev_id='a@cset-0-1')
 
1419
        self.build_tree(['b1/two'])
 
1420
        wt.add('two')
 
1421
        wt.commit('add two', rev_id='a@cset-0-2',
 
1422
                  revprops={'branch-nick':'test'})
 
1423
 
 
1424
        bundle_txt = StringIO()
 
1425
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
 
1426
                               'a@cset-0-1', bundle_txt, self.format)
 
1427
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
 
1428
        bundle_txt.seek(0, 0)
 
1429
        return bundle_txt
 
1430
 
 
1431
    def check_valid(self, bundle):
 
1432
        """Check that after whatever munging, the final object is valid."""
 
1433
        self.assertEqual(['a@cset-0-2'],
 
1434
            [r.revision_id for r in bundle.real_revisions])
 
1435
 
 
1436
    def test_extra_whitespace(self):
 
1437
        bundle_txt = self.build_test_bundle()
 
1438
 
 
1439
        # Seek to the end of the file
 
1440
        # Adding one extra newline used to give us
 
1441
        # TypeError: float() argument must be a string or a number
 
1442
        bundle_txt.seek(0, 2)
 
1443
        bundle_txt.write('\n')
 
1444
        bundle_txt.seek(0)
 
1445
 
 
1446
        bundle = read_bundle(bundle_txt)
 
1447
        self.check_valid(bundle)
 
1448
 
 
1449
    def test_extra_whitespace_2(self):
 
1450
        bundle_txt = self.build_test_bundle()
 
1451
 
 
1452
        # Seek to the end of the file
 
1453
        # Adding two extra newlines used to give us
 
1454
        # MalformedPatches: The first line of all patches should be ...
 
1455
        bundle_txt.seek(0, 2)
 
1456
        bundle_txt.write('\n\n')
 
1457
        bundle_txt.seek(0)
 
1458
 
 
1459
        bundle = read_bundle(bundle_txt)
 
1460
        self.check_valid(bundle)
 
1461
 
 
1462
 
 
1463
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
 
1464
 
 
1465
    format = '0.9'
 
1466
 
 
1467
    def test_missing_trailing_whitespace(self):
 
1468
        bundle_txt = self.build_test_bundle()
 
1469
 
 
1470
        # Remove a trailing newline, it shouldn't kill the parser
 
1471
        raw = bundle_txt.getvalue()
 
1472
        # The contents of the bundle don't have to be this, but this
 
1473
        # test is concerned with the exact case where the serializer
 
1474
        # creates a blank line at the end, and fails if that
 
1475
        # line is stripped
 
1476
        self.assertEqual('\n\n', raw[-2:])
 
1477
        bundle_txt = StringIO(raw[:-1])
 
1478
 
 
1479
        bundle = read_bundle(bundle_txt)
 
1480
        self.check_valid(bundle)
 
1481
 
 
1482
    def test_opening_text(self):
 
1483
        bundle_txt = self.build_test_bundle()
 
1484
 
 
1485
        bundle_txt = StringIO("Some random\nemail comments\n"
 
1486
                              + bundle_txt.getvalue())
 
1487
 
 
1488
        bundle = read_bundle(bundle_txt)
 
1489
        self.check_valid(bundle)
 
1490
 
 
1491
    def test_trailing_text(self):
 
1492
        bundle_txt = self.build_test_bundle()
 
1493
 
 
1494
        bundle_txt = StringIO(bundle_txt.getvalue() +
 
1495
                              "Some trailing\nrandom\ntext\n")
 
1496
 
 
1497
        bundle = read_bundle(bundle_txt)
 
1498
        self.check_valid(bundle)
 
1499
 
 
1500
 
 
1501
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
 
1502
 
 
1503
    format = '4'
 
1504
 
 
1505
 
 
1506
class TestBundleWriterReader(TestCase):
 
1507
 
 
1508
    def test_roundtrip_record(self):
 
1509
        fileobj = StringIO()
 
1510
        writer = v4.BundleWriter(fileobj)
 
1511
        writer.begin()
 
1512
        writer.add_info_record(foo='bar')
 
1513
        writer._add_record("Record body", {'parents': ['1', '3'],
 
1514
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1515
        writer.end()
 
1516
        fileobj.seek(0)
 
1517
        reader = v4.BundleReader(fileobj, stream_input=True)
 
1518
        record_iter = reader.iter_records()
 
1519
        record = record_iter.next()
 
1520
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1521
            'info', None, None), record)
 
1522
        record = record_iter.next()
 
1523
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
 
1524
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
 
1525
                          record)
 
1526
 
 
1527
    def test_roundtrip_record_memory_hungry(self):
 
1528
        fileobj = StringIO()
 
1529
        writer = v4.BundleWriter(fileobj)
 
1530
        writer.begin()
 
1531
        writer.add_info_record(foo='bar')
 
1532
        writer._add_record("Record body", {'parents': ['1', '3'],
 
1533
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1534
        writer.end()
 
1535
        fileobj.seek(0)
 
1536
        reader = v4.BundleReader(fileobj, stream_input=False)
 
1537
        record_iter = reader.iter_records()
 
1538
        record = record_iter.next()
 
1539
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1540
            'info', None, None), record)
 
1541
        record = record_iter.next()
 
1542
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
 
1543
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
 
1544
                          record)
 
1545
 
 
1546
    def test_encode_name(self):
 
1547
        self.assertEqual('revision/rev1',
 
1548
            v4.BundleWriter.encode_name('revision', 'rev1'))
 
1549
        self.assertEqual('file/rev//1/file-id-1',
 
1550
            v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
 
1551
        self.assertEqual('info',
 
1552
            v4.BundleWriter.encode_name('info', None, None))
 
1553
 
 
1554
    def test_decode_name(self):
 
1555
        self.assertEqual(('revision', 'rev1', None),
 
1556
            v4.BundleReader.decode_name('revision/rev1'))
 
1557
        self.assertEqual(('file', 'rev/1', 'file-id-1'),
 
1558
            v4.BundleReader.decode_name('file/rev//1/file-id-1'))
 
1559
        self.assertEqual(('info', None, None),
 
1560
                         v4.BundleReader.decode_name('info'))
 
1561
 
 
1562
    def test_too_many_names(self):
 
1563
        fileobj = StringIO()
 
1564
        writer = v4.BundleWriter(fileobj)
 
1565
        writer.begin()
 
1566
        writer.add_info_record(foo='bar')
 
1567
        writer._container.add_bytes_record('blah', ['two', 'names'])
 
1568
        writer.end()
 
1569
        fileobj.seek(0)
 
1570
        record_iter = v4.BundleReader(fileobj).iter_records()
 
1571
        record = record_iter.next()
 
1572
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1573
            'info', None, None), record)
 
1574
        self.assertRaises(BadBundle, record_iter.next)
 
1575
 
 
1576
 
 
1577
class TestReadMergeableFromUrl(TestCaseWithTransport):
 
1578
 
 
1579
    def test_read_mergeable_skips_local(self):
 
1580
        """A local bundle named like the URL should not be read.
 
1581
        """
 
1582
        out, wt = test_read_bundle.create_bundle_file(self)
 
1583
        class FooService(object):
 
1584
            """A directory service that always returns source"""
 
1585
 
 
1586
            def look_up(self, name, url):
 
1587
                return 'source'
 
1588
        directories.register('foo:', FooService, 'Testing directory service')
 
1589
        self.addCleanup(lambda: directories.remove('foo:'))
 
1590
        self.build_tree_contents([('./foo:bar', out.getvalue())])
 
1591
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
 
1592
                          'foo:bar')