/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Robert Collins
  • Date: 2008-08-20 02:07:36 UTC
  • mfrom: (3640 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3682.
  • Revision ID: robertc@robertcollins.net-20080820020736-g2xe4921zzxtymle
Merge bzr.dev

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)
392
 
            for inventory_id in old:
393
 
                try:
394
 
                    old_file = old.get_file(inventory_id)
395
 
                except:
396
 
                    continue
397
 
                if old_file is None:
398
 
                    continue
399
 
                self.assertEqual(old_file.read(),
400
 
                                 new.get_file(inventory_id).read())
401
 
        if rev_id is not None:
 
472
            old.lock_read()
 
473
            new.lock_read()
 
474
            try:
 
475
                # Check that there aren't any inventory level changes
 
476
                delta = new.changes_from(old)
 
477
                self.assertFalse(delta.has_changed(),
 
478
                                 'Revision %s not copied correctly.'
 
479
                                 % (ancestor,))
 
480
 
 
481
                # Now check that the file contents are all correct
 
482
                for inventory_id in old:
 
483
                    try:
 
484
                        old_file = old.get_file(inventory_id)
 
485
                    except NoSuchFile:
 
486
                        continue
 
487
                    if old_file is None:
 
488
                        continue
 
489
                    self.assertEqual(old_file.read(),
 
490
                                     new.get_file(inventory_id).read())
 
491
            finally:
 
492
                new.unlock()
 
493
                old.unlock()
 
494
        if not _mod_revision.is_null(rev_id):
402
495
            rh = self.b1.revision_history()
403
496
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
404
497
            tree.update()
 
498
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
 
499
            self.assertFalse(delta.has_changed(),
 
500
                             'Working tree has modifications: %s' % delta)
405
501
        return tree
406
502
 
407
 
    def valid_apply_bundle(self, base_rev_id, reader, checkout_dir=None):
 
503
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
408
504
        """Get the base revision, apply the changes, and make
409
505
        sure everything matches the builtin branch.
410
506
        """
411
507
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
 
508
        to_tree.lock_write()
 
509
        try:
 
510
            self._valid_apply_bundle(base_rev_id, info, to_tree)
 
511
        finally:
 
512
            to_tree.unlock()
 
513
 
 
514
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
 
515
        original_parents = to_tree.get_parent_ids()
412
516
        repository = to_tree.branch.repository
 
517
        original_parents = to_tree.get_parent_ids()
413
518
        self.assertIs(repository.has_revision(base_rev_id), True)
414
 
        info = reader.info
415
519
        for rev in info.real_revisions:
416
520
            self.assert_(not repository.has_revision(rev.revision_id),
417
521
                'Revision {%s} present before applying bundle' 
418
522
                % rev.revision_id)
419
 
        merge_bundle(reader, to_tree, True, Merge3Merger, False, False)
 
523
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
420
524
 
421
525
        for rev in info.real_revisions:
422
526
            self.assert_(repository.has_revision(rev.revision_id),
426
530
        self.assert_(to_tree.branch.repository.has_revision(info.target))
427
531
        # Do we also want to verify that all the texts have been added?
428
532
 
429
 
        self.assert_(info.target in to_tree.pending_merges())
430
 
 
 
533
        self.assertEqual(original_parents + [info.target],
 
534
            to_tree.get_parent_ids())
431
535
 
432
536
        rev = info.real_revisions[-1]
433
537
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
454
558
            #         to_tree.get_file(fileid).read())
455
559
 
456
560
    def test_bundle(self):
457
 
 
458
 
        import os, sys
459
 
        pjoin = os.path.join
460
 
 
461
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
561
        self.tree1 = self.make_branch_and_tree('b1')
462
562
        self.b1 = self.tree1.branch
463
563
 
464
 
        open(pjoin('b1/one'), 'wb').write('one\n')
 
564
        open('b1/one', 'wb').write('one\n')
465
565
        self.tree1.add('one')
466
566
        self.tree1.commit('add one', rev_id='a@cset-0-1')
467
567
 
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')
 
568
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
471
569
 
472
570
        # Make sure we can handle files with spaces, tabs, other
473
571
        # bogus characters
476
574
                , 'b1/dir/'
477
575
                , 'b1/dir/filein subdir.c'
478
576
                , 'b1/dir/WithCaps.txt'
479
 
                , 'b1/dir/trailing space '
 
577
                , 'b1/dir/ pre space'
480
578
                , 'b1/sub/'
481
579
                , 'b1/sub/sub/'
482
580
                , 'b1/sub/sub/nonempty.txt'
483
 
                # Tabs are not valid in filenames on windows
484
 
                #'b1/with\ttab.txt'
485
581
                ])
486
582
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
487
583
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
488
584
        tt = TreeTransform(self.tree1)
489
585
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
490
586
        tt.apply()
 
587
        # have to fix length of file-id so that we can predictably rewrite
 
588
        # a (length-prefixed) record containing it later.
 
589
        self.tree1.add('with space.txt', 'withspace-id')
491
590
        self.tree1.add([
492
 
                'with space.txt'
493
 
                , 'dir'
 
591
                  'dir'
494
592
                , 'dir/filein subdir.c'
495
593
                , 'dir/WithCaps.txt'
496
 
                , 'dir/trailing space '
 
594
                , 'dir/ pre space'
497
595
                , 'dir/nolastnewline.txt'
498
596
                , 'sub'
499
597
                , 'sub/sub'
505
603
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
506
604
 
507
605
        # Check a rollup bundle 
508
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
 
606
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
509
607
 
510
608
        # Now delete entries
511
609
        self.tree1.remove(
520
618
        self.tree1.commit('removed', rev_id='a@cset-0-3')
521
619
        
522
620
        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')
 
621
        self.assertRaises((TestamentMismatch,
 
622
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
623
            'a@cset-0-2', 'a@cset-0-3')
525
624
        # Check a rollup bundle 
526
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
527
 
 
 
625
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
528
626
 
529
627
        # Now move the directory
530
628
        self.tree1.rename_one('dir', 'sub/dir')
532
630
 
533
631
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
534
632
        # Check a rollup bundle 
535
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
 
633
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
536
634
 
537
635
        # Modified files
538
636
        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')
 
637
        open('b1/sub/dir/ pre space', 'ab').write(
 
638
             '\r\nAdding some\r\nDOS format lines\r\n')
540
639
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
541
 
        self.tree1.rename_one('sub/dir/trailing space ', 
542
 
                              'sub/ start and end space ')
 
640
        self.tree1.rename_one('sub/dir/ pre space', 
 
641
                              'sub/ start space')
543
642
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
544
643
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
545
644
 
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
645
        self.tree1.rename_one('sub/dir/WithCaps.txt', 'temp')
562
646
        self.tree1.rename_one('with space.txt', 'WithCaps.txt')
563
647
        self.tree1.rename_one('temp', 'with space.txt')
564
 
        self.tree1.commit(u'swap filenames', rev_id='a@cset-0-7',
 
648
        self.tree1.commit(u'swap filenames', rev_id='a@cset-0-6',
 
649
                          verbose=False)
 
650
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
 
651
        other = self.get_checkout('a@cset-0-5')
 
652
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
 
653
            'a@cset-0-5')
 
654
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
655
        self.assertEqualDiff(tree1_inv, tree2_inv)
 
656
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
 
657
        other.commit('rename file', rev_id='a@cset-0-6b')
 
658
        self.tree1.merge_from_branch(other.branch)
 
659
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
565
660
                          verbose=False)
566
661
        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
662
 
575
663
    def test_symlink_bundle(self):
576
 
        if not has_symlinks():
577
 
            raise TestSkipped("No symlink support")
578
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
664
        self.requireFeature(SymlinkFeature)
 
665
        self.tree1 = self.make_branch_and_tree('b1')
579
666
        self.b1 = self.tree1.branch
580
667
        tt = TreeTransform(self.tree1)
581
668
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
582
669
        tt.apply()
583
670
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
584
 
        self.get_valid_bundle(None, 'l@cset-0-1')
 
671
        self.get_valid_bundle('null:', 'l@cset-0-1')
585
672
        tt = TreeTransform(self.tree1)
586
673
        trans_id = tt.trans_id_tree_file_id('link-1')
587
674
        tt.adjust_path('link2', tt.root, trans_id)
605
692
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
606
693
 
607
694
    def test_binary_bundle(self):
608
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
695
        self.tree1 = self.make_branch_and_tree('b1')
609
696
        self.b1 = self.tree1.branch
610
697
        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')
 
698
        
 
699
        # Add
 
700
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
 
701
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
 
702
            'binary-2')
613
703
        tt.apply()
614
704
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
615
 
        self.get_valid_bundle(None, 'b@cset-0-1')
 
705
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
706
 
 
707
        # Delete
616
708
        tt = TreeTransform(self.tree1)
617
709
        trans_id = tt.trans_id_tree_file_id('binary-1')
618
710
        tt.delete_contents(trans_id)
619
711
        tt.apply()
620
712
        self.tree1.commit('delete binary', rev_id='b@cset-0-2')
621
713
        self.get_valid_bundle('b@cset-0-1', 'b@cset-0-2')
 
714
 
 
715
        # Rename & modify
622
716
        tt = TreeTransform(self.tree1)
623
717
        trans_id = tt.trans_id_tree_file_id('binary-2')
624
718
        tt.adjust_path('file3', tt.root, trans_id)
625
719
        tt.delete_contents(trans_id)
626
 
        tt.create_file('filecontents\x00', trans_id)
 
720
        tt.create_file('file\rcontents\x00\n\x00', trans_id)
627
721
        tt.apply()
628
722
        self.tree1.commit('rename and modify binary', rev_id='b@cset-0-3')
629
723
        self.get_valid_bundle('b@cset-0-2', 'b@cset-0-3')
 
724
 
 
725
        # Modify
630
726
        tt = TreeTransform(self.tree1)
631
727
        trans_id = tt.trans_id_tree_file_id('binary-2')
632
728
        tt.delete_contents(trans_id)
633
 
        tt.create_file('\x00filecontents', trans_id)
 
729
        tt.create_file('\x00file\rcontents', trans_id)
634
730
        tt.apply()
635
731
        self.tree1.commit('just modify binary', rev_id='b@cset-0-4')
636
732
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
637
733
 
 
734
        # Rollup
 
735
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
736
 
638
737
    def test_last_modified(self):
639
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
738
        self.tree1 = self.make_branch_and_tree('b1')
640
739
        self.b1 = self.tree1.branch
641
740
        tt = TreeTransform(self.tree1)
642
741
        tt.new_file('file', tt.root, 'file', 'file')
657
756
        tt.create_file('file2', trans_id)
658
757
        tt.apply()
659
758
        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)
 
759
        self.tree1.merge_from_branch(other.branch)
661
760
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
662
761
                          verbose=False)
663
762
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
664
763
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
 
764
 
 
765
    def test_hide_history(self):
 
766
        self.tree1 = self.make_branch_and_tree('b1')
 
767
        self.b1 = self.tree1.branch
 
768
 
 
769
        open('b1/one', 'wb').write('one\n')
 
770
        self.tree1.add('one')
 
771
        self.tree1.commit('add file', rev_id='a@cset-0-1')
 
772
        open('b1/one', 'wb').write('two\n')
 
773
        self.tree1.commit('modify', rev_id='a@cset-0-2')
 
774
        open('b1/one', 'wb').write('three\n')
 
775
        self.tree1.commit('modify', rev_id='a@cset-0-3')
 
776
        bundle_file = StringIO()
 
777
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
 
778
                               'a@cset-0-1', bundle_file, format=self.format)
 
779
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
 
780
        self.assertContainsRe(self.get_raw(bundle_file), 'one')
 
781
        self.assertContainsRe(self.get_raw(bundle_file), 'three')
 
782
 
 
783
    def test_bundle_same_basis(self):
 
784
        """Ensure using the basis as the target doesn't cause an error"""
 
785
        self.tree1 = self.make_branch_and_tree('b1')
 
786
        self.tree1.commit('add file', rev_id='a@cset-0-1')
 
787
        bundle_file = StringIO()
 
788
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-1',
 
789
                               'a@cset-0-1', bundle_file)
 
790
 
 
791
    @staticmethod
 
792
    def get_raw(bundle_file):
 
793
        return bundle_file.getvalue()
 
794
 
 
795
    def test_unicode_bundle(self):
 
796
        # Handle international characters
 
797
        os.mkdir('b1')
 
798
        try:
 
799
            f = open(u'b1/with Dod\xe9', 'wb')
 
800
        except UnicodeEncodeError:
 
801
            raise TestSkipped("Filesystem doesn't support unicode")
 
802
 
 
803
        self.tree1 = self.make_branch_and_tree('b1')
 
804
        self.b1 = self.tree1.branch
 
805
 
 
806
        f.write((u'A file\n'
 
807
            u'With international man of mystery\n'
 
808
            u'William Dod\xe9\n').encode('utf-8'))
 
809
        f.close()
 
810
 
 
811
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
 
812
        self.tree1.commit(u'i18n commit from William Dod\xe9',
 
813
                          rev_id='i18n-1', committer=u'William Dod\xe9')
 
814
 
 
815
        if sys.platform == 'darwin':
 
816
            from bzrlib.workingtree import WorkingTree3
 
817
            if type(self.tree1) is WorkingTree3:
 
818
                self.knownFailure("Bug #141438: fails for WorkingTree3 on OSX")
 
819
 
 
820
            # On Mac the '\xe9' gets changed to 'e\u0301'
 
821
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
 
822
                             sorted(os.listdir(u'b1')))
 
823
            delta = self.tree1.changes_from(self.tree1.basis_tree())
 
824
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
 
825
                             delta.removed)
 
826
            self.knownFailure("Mac OSX doesn't preserve unicode"
 
827
                              " combining characters.")
 
828
 
 
829
        # Add
 
830
        bundle = self.get_valid_bundle('null:', 'i18n-1')
 
831
 
 
832
        # Modified
 
833
        f = open(u'b1/with Dod\xe9', 'wb')
 
834
        f.write(u'Modified \xb5\n'.encode('utf8'))
 
835
        f.close()
 
836
        self.tree1.commit(u'modified', rev_id='i18n-2')
 
837
 
 
838
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
 
839
        
 
840
        # Renamed
 
841
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
 
842
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
 
843
                          committer=u'Erik B\xe5gfors')
 
844
 
 
845
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
 
846
 
 
847
        # Removed
 
848
        self.tree1.remove([u'B\xe5gfors'])
 
849
        self.tree1.commit(u'removed', rev_id='i18n-4')
 
850
 
 
851
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
 
852
 
 
853
        # Rollup
 
854
        bundle = self.get_valid_bundle('null:', 'i18n-4')
 
855
 
 
856
 
 
857
    def test_whitespace_bundle(self):
 
858
        if sys.platform in ('win32', 'cygwin'):
 
859
            raise TestSkipped('Windows doesn\'t support filenames'
 
860
                              ' with tabs or trailing spaces')
 
861
        self.tree1 = self.make_branch_and_tree('b1')
 
862
        self.b1 = self.tree1.branch
 
863
 
 
864
        self.build_tree(['b1/trailing space '])
 
865
        self.tree1.add(['trailing space '])
 
866
        # TODO: jam 20060701 Check for handling files with '\t' characters
 
867
        #       once we actually support them
 
868
 
 
869
        # Added
 
870
        self.tree1.commit('funky whitespace', rev_id='white-1')
 
871
 
 
872
        bundle = self.get_valid_bundle('null:', 'white-1')
 
873
 
 
874
        # Modified
 
875
        open('b1/trailing space ', 'ab').write('add some text\n')
 
876
        self.tree1.commit('add text', rev_id='white-2')
 
877
 
 
878
        bundle = self.get_valid_bundle('white-1', 'white-2')
 
879
 
 
880
        # Renamed
 
881
        self.tree1.rename_one('trailing space ', ' start and end space ')
 
882
        self.tree1.commit('rename', rev_id='white-3')
 
883
 
 
884
        bundle = self.get_valid_bundle('white-2', 'white-3')
 
885
 
 
886
        # Removed
 
887
        self.tree1.remove([' start and end space '])
 
888
        self.tree1.commit('removed', rev_id='white-4')
 
889
 
 
890
        bundle = self.get_valid_bundle('white-3', 'white-4')
 
891
        
 
892
        # Now test a complet roll-up
 
893
        bundle = self.get_valid_bundle('null:', 'white-4')
 
894
 
 
895
    def test_alt_timezone_bundle(self):
 
896
        self.tree1 = self.make_branch_and_memory_tree('b1')
 
897
        self.b1 = self.tree1.branch
 
898
        builder = treebuilder.TreeBuilder()
 
899
 
 
900
        self.tree1.lock_write()
 
901
        builder.start_tree(self.tree1)
 
902
        builder.build(['newfile'])
 
903
        builder.finish_tree()
 
904
 
 
905
        # Asia/Colombo offset = 5 hours 30 minutes
 
906
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
 
907
                          timezone=19800, timestamp=1152544886.0)
 
908
 
 
909
        bundle = self.get_valid_bundle('null:', 'tz-1')
 
910
        
 
911
        rev = bundle.revisions[0]
 
912
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
 
913
        self.assertEqual(19800, rev.timezone)
 
914
        self.assertEqual(1152544886.0, rev.timestamp)
 
915
        self.tree1.unlock()
 
916
 
 
917
    def test_bundle_root_id(self):
 
918
        self.tree1 = self.make_branch_and_tree('b1')
 
919
        self.b1 = self.tree1.branch
 
920
        self.tree1.commit('message', rev_id='revid1')
 
921
        bundle = self.get_valid_bundle('null:', 'revid1')
 
922
        tree = self.get_bundle_tree(bundle, 'revid1')
 
923
        self.assertEqual('revid1', tree.inventory.root.revision)
 
924
 
 
925
    def test_install_revisions(self):
 
926
        self.tree1 = self.make_branch_and_tree('b1')
 
927
        self.b1 = self.tree1.branch
 
928
        self.tree1.commit('message', rev_id='rev2a')
 
929
        bundle = self.get_valid_bundle('null:', 'rev2a')
 
930
        branch2 = self.make_branch('b2')
 
931
        self.assertFalse(branch2.repository.has_revision('rev2a'))
 
932
        target_revision = bundle.install_revisions(branch2.repository)
 
933
        self.assertTrue(branch2.repository.has_revision('rev2a'))
 
934
        self.assertEqual('rev2a', target_revision)
 
935
 
 
936
    def test_bundle_empty_property(self):
 
937
        """Test serializing revision properties with an empty value."""
 
938
        tree = self.make_branch_and_memory_tree('tree')
 
939
        tree.lock_write()
 
940
        self.addCleanup(tree.unlock)
 
941
        tree.add([''], ['TREE_ROOT'])
 
942
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
943
        self.b1 = tree.branch
 
944
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
945
        bundle = read_bundle(bundle_sio)
 
946
        revision_info = bundle.revisions[0]
 
947
        self.assertEqual('rev1', revision_info.revision_id)
 
948
        rev = revision_info.as_revision()
 
949
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
950
                         rev.properties)
 
951
 
 
952
    def test_bundle_sorted_properties(self):
 
953
        """For stability the writer should write properties in sorted order."""
 
954
        tree = self.make_branch_and_memory_tree('tree')
 
955
        tree.lock_write()
 
956
        self.addCleanup(tree.unlock)
 
957
 
 
958
        tree.add([''], ['TREE_ROOT'])
 
959
        tree.commit('One', rev_id='rev1',
 
960
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
961
        self.b1 = tree.branch
 
962
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
963
        bundle = read_bundle(bundle_sio)
 
964
        revision_info = bundle.revisions[0]
 
965
        self.assertEqual('rev1', revision_info.revision_id)
 
966
        rev = revision_info.as_revision()
 
967
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
 
968
                          'd':'1'}, rev.properties)
 
969
 
 
970
    def test_bundle_unicode_properties(self):
 
971
        """We should be able to round trip a non-ascii property."""
 
972
        tree = self.make_branch_and_memory_tree('tree')
 
973
        tree.lock_write()
 
974
        self.addCleanup(tree.unlock)
 
975
 
 
976
        tree.add([''], ['TREE_ROOT'])
 
977
        # Revisions themselves do not require anything about revision property
 
978
        # keys, other than that they are a basestring, and do not contain
 
979
        # whitespace.
 
980
        # However, Testaments assert than they are str(), and thus should not
 
981
        # be Unicode.
 
982
        tree.commit('One', rev_id='rev1',
 
983
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
984
        self.b1 = tree.branch
 
985
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
986
        bundle = read_bundle(bundle_sio)
 
987
        revision_info = bundle.revisions[0]
 
988
        self.assertEqual('rev1', revision_info.revision_id)
 
989
        rev = revision_info.as_revision()
 
990
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
 
991
                          'alpha':u'\u03b1'}, rev.properties)
 
992
 
 
993
    def test_bundle_with_ghosts(self):
 
994
        tree = self.make_branch_and_tree('tree')
 
995
        self.b1 = tree.branch
 
996
        self.build_tree_contents([('tree/file', 'content1')])
 
997
        tree.add(['file'])
 
998
        tree.commit('rev1')
 
999
        self.build_tree_contents([('tree/file', 'content2')])
 
1000
        tree.add_parent_tree_id('ghost')
 
1001
        tree.commit('rev2', rev_id='rev2')
 
1002
        bundle = self.get_valid_bundle('null:', 'rev2')
 
1003
 
 
1004
    def make_simple_tree(self, format=None):
 
1005
        tree = self.make_branch_and_tree('b1', format=format)
 
1006
        self.b1 = tree.branch
 
1007
        self.build_tree(['b1/file'])
 
1008
        tree.add('file')
 
1009
        return tree
 
1010
 
 
1011
    def test_across_serializers(self):
 
1012
        tree = self.make_simple_tree('knit')
 
1013
        tree.commit('hello', rev_id='rev1')
 
1014
        tree.commit('hello', rev_id='rev2')
 
1015
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1016
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1017
        bundle.install_revisions(repo)
 
1018
        inv_text = repo.get_inventory_xml('rev2')
 
1019
        self.assertNotContainsRe(inv_text, 'format="5"')
 
1020
        self.assertContainsRe(inv_text, 'format="7"')
 
1021
 
 
1022
    def make_repo_with_installed_revisions(self):
 
1023
        tree = self.make_simple_tree('knit')
 
1024
        tree.commit('hello', rev_id='rev1')
 
1025
        tree.commit('hello', rev_id='rev2')
 
1026
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1027
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1028
        bundle.install_revisions(repo)
 
1029
        return repo
 
1030
 
 
1031
    def test_across_models(self):
 
1032
        repo = self.make_repo_with_installed_revisions()
 
1033
        inv = repo.get_inventory('rev2')
 
1034
        self.assertEqual('rev2', inv.root.revision)
 
1035
        root_id = inv.root.file_id
 
1036
        repo.lock_read()
 
1037
        self.addCleanup(repo.unlock)
 
1038
        self.assertEqual({(root_id, 'rev1'):(),
 
1039
            (root_id, 'rev2'):((root_id, 'rev1'),)},
 
1040
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
 
1041
 
 
1042
    def test_inv_hash_across_serializers(self):
 
1043
        repo = self.make_repo_with_installed_revisions()
 
1044
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
 
1045
        xml = repo.get_inventory_xml('rev2')
 
1046
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
 
1047
 
 
1048
    def test_across_models_incompatible(self):
 
1049
        tree = self.make_simple_tree('dirstate-with-subtree')
 
1050
        tree.commit('hello', rev_id='rev1')
 
1051
        tree.commit('hello', rev_id='rev2')
 
1052
        try:
 
1053
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1054
        except errors.IncompatibleBundleFormat:
 
1055
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1056
        repo = self.make_repository('repo', format='knit')
 
1057
        bundle.install_revisions(repo)
 
1058
 
 
1059
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1060
        self.assertRaises(errors.IncompatibleRevision,
 
1061
                          bundle.install_revisions, repo)
 
1062
 
 
1063
    def test_get_merge_request(self):
 
1064
        tree = self.make_simple_tree()
 
1065
        tree.commit('hello', rev_id='rev1')
 
1066
        tree.commit('hello', rev_id='rev2')
 
1067
        bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1068
        result = bundle.get_merge_request(tree.branch.repository)
 
1069
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
 
1070
 
 
1071
    def test_with_subtree(self):
 
1072
        tree = self.make_branch_and_tree('tree',
 
1073
                                         format='dirstate-with-subtree')
 
1074
        self.b1 = tree.branch
 
1075
        subtree = self.make_branch_and_tree('tree/subtree',
 
1076
                                            format='dirstate-with-subtree')
 
1077
        tree.add('subtree')
 
1078
        tree.commit('hello', rev_id='rev1')
 
1079
        try:
 
1080
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1081
        except errors.IncompatibleBundleFormat:
 
1082
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1083
        if isinstance(bundle, v09.BundleInfo09):
 
1084
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
 
1085
        repo = self.make_repository('repo', format='knit')
 
1086
        self.assertRaises(errors.IncompatibleRevision,
 
1087
                          bundle.install_revisions, repo)
 
1088
        repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
 
1089
        bundle.install_revisions(repo2)
 
1090
 
 
1091
    def test_revision_id_with_slash(self):
 
1092
        self.tree1 = self.make_branch_and_tree('tree')
 
1093
        self.b1 = self.tree1.branch
 
1094
        try:
 
1095
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
 
1096
        except ValueError:
 
1097
            raise TestSkipped("Repository doesn't support revision ids with"
 
1098
                              " slashes")
 
1099
        bundle = self.get_valid_bundle('null:', 'rev/id')
 
1100
 
 
1101
    def test_skip_file(self):
 
1102
        """Make sure we don't accidentally write to the wrong versionedfile"""
 
1103
        self.tree1 = self.make_branch_and_tree('tree')
 
1104
        self.b1 = self.tree1.branch
 
1105
        # rev1 is not present in bundle, done by fetch
 
1106
        self.build_tree_contents([('tree/file2', 'contents1')])
 
1107
        self.tree1.add('file2', 'file2-id')
 
1108
        self.tree1.commit('rev1', rev_id='reva')
 
1109
        self.build_tree_contents([('tree/file3', 'contents2')])
 
1110
        # rev2 is present in bundle, and done by fetch
 
1111
        # having file1 in the bunle causes file1's versionedfile to be opened.
 
1112
        self.tree1.add('file3', 'file3-id')
 
1113
        self.tree1.commit('rev2')
 
1114
        # Updating file2 should not cause an attempt to add to file1's vf
 
1115
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
 
1116
        self.build_tree_contents([('tree/file2', 'contents3')])
 
1117
        self.tree1.commit('rev3', rev_id='rev3')
 
1118
        bundle = self.get_valid_bundle('reva', 'rev3')
 
1119
        if getattr(bundle, 'get_bundle_reader', None) is None:
 
1120
            raise TestSkipped('Bundle format cannot provide reader')
 
1121
        # be sure that file1 comes before file2
 
1122
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
 
1123
            if f == 'file3-id':
 
1124
                break
 
1125
            self.assertNotEqual(f, 'file2-id')
 
1126
        bundle.install_revisions(target.branch.repository)
 
1127
 
 
1128
 
 
1129
class V08BundleTester(BundleTester, TestCaseWithTransport):
 
1130
 
 
1131
    format = '0.8'
 
1132
 
 
1133
    def test_bundle_empty_property(self):
 
1134
        """Test serializing revision properties with an empty value."""
 
1135
        tree = self.make_branch_and_memory_tree('tree')
 
1136
        tree.lock_write()
 
1137
        self.addCleanup(tree.unlock)
 
1138
        tree.add([''], ['TREE_ROOT'])
 
1139
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1140
        self.b1 = tree.branch
 
1141
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1142
        self.assertContainsRe(bundle_sio.getvalue(),
 
1143
                              '# properties:\n'
 
1144
                              '#   branch-nick: tree\n'
 
1145
                              '#   empty: \n'
 
1146
                              '#   one: two\n'
 
1147
                             )
 
1148
        bundle = read_bundle(bundle_sio)
 
1149
        revision_info = bundle.revisions[0]
 
1150
        self.assertEqual('rev1', revision_info.revision_id)
 
1151
        rev = revision_info.as_revision()
 
1152
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1153
                         rev.properties)
 
1154
 
 
1155
    def get_bundle_tree(self, bundle, revision_id):
 
1156
        repository = self.make_repository('repo')
 
1157
        return bundle.revision_tree(repository, 'revid1')
 
1158
 
 
1159
    def test_bundle_empty_property_alt(self):
 
1160
        """Test serializing revision properties with an empty value.
 
1161
 
 
1162
        Older readers had a bug when reading an empty property.
 
1163
        They assumed that all keys ended in ': \n'. However they would write an
 
1164
        empty value as ':\n'. This tests make sure that all newer bzr versions
 
1165
        can handle th second form.
 
1166
        """
 
1167
        tree = self.make_branch_and_memory_tree('tree')
 
1168
        tree.lock_write()
 
1169
        self.addCleanup(tree.unlock)
 
1170
        tree.add([''], ['TREE_ROOT'])
 
1171
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1172
        self.b1 = tree.branch
 
1173
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1174
        txt = bundle_sio.getvalue()
 
1175
        loc = txt.find('#   empty: ') + len('#   empty:')
 
1176
        # Create a new bundle, which strips the trailing space after empty
 
1177
        bundle_sio = StringIO(txt[:loc] + txt[loc+1:])
 
1178
 
 
1179
        self.assertContainsRe(bundle_sio.getvalue(),
 
1180
                              '# properties:\n'
 
1181
                              '#   branch-nick: tree\n'
 
1182
                              '#   empty:\n'
 
1183
                              '#   one: two\n'
 
1184
                             )
 
1185
        bundle = read_bundle(bundle_sio)
 
1186
        revision_info = bundle.revisions[0]
 
1187
        self.assertEqual('rev1', revision_info.revision_id)
 
1188
        rev = revision_info.as_revision()
 
1189
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1190
                         rev.properties)
 
1191
 
 
1192
    def test_bundle_sorted_properties(self):
 
1193
        """For stability the writer should write properties in sorted order."""
 
1194
        tree = self.make_branch_and_memory_tree('tree')
 
1195
        tree.lock_write()
 
1196
        self.addCleanup(tree.unlock)
 
1197
 
 
1198
        tree.add([''], ['TREE_ROOT'])
 
1199
        tree.commit('One', rev_id='rev1',
 
1200
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
1201
        self.b1 = tree.branch
 
1202
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1203
        self.assertContainsRe(bundle_sio.getvalue(),
 
1204
                              '# properties:\n'
 
1205
                              '#   a: 4\n'
 
1206
                              '#   b: 3\n'
 
1207
                              '#   branch-nick: tree\n'
 
1208
                              '#   c: 2\n'
 
1209
                              '#   d: 1\n'
 
1210
                             )
 
1211
        bundle = read_bundle(bundle_sio)
 
1212
        revision_info = bundle.revisions[0]
 
1213
        self.assertEqual('rev1', revision_info.revision_id)
 
1214
        rev = revision_info.as_revision()
 
1215
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
 
1216
                          'd':'1'}, rev.properties)
 
1217
 
 
1218
    def test_bundle_unicode_properties(self):
 
1219
        """We should be able to round trip a non-ascii property."""
 
1220
        tree = self.make_branch_and_memory_tree('tree')
 
1221
        tree.lock_write()
 
1222
        self.addCleanup(tree.unlock)
 
1223
 
 
1224
        tree.add([''], ['TREE_ROOT'])
 
1225
        # Revisions themselves do not require anything about revision property
 
1226
        # keys, other than that they are a basestring, and do not contain
 
1227
        # whitespace.
 
1228
        # However, Testaments assert than they are str(), and thus should not
 
1229
        # be Unicode.
 
1230
        tree.commit('One', rev_id='rev1',
 
1231
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
1232
        self.b1 = tree.branch
 
1233
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1234
        self.assertContainsRe(bundle_sio.getvalue(),
 
1235
                              '# properties:\n'
 
1236
                              '#   alpha: \xce\xb1\n'
 
1237
                              '#   branch-nick: tree\n'
 
1238
                              '#   omega: \xce\xa9\n'
 
1239
                             )
 
1240
        bundle = read_bundle(bundle_sio)
 
1241
        revision_info = bundle.revisions[0]
 
1242
        self.assertEqual('rev1', revision_info.revision_id)
 
1243
        rev = revision_info.as_revision()
 
1244
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
 
1245
                          'alpha':u'\u03b1'}, rev.properties)
 
1246
 
 
1247
 
 
1248
class V09BundleKnit2Tester(V08BundleTester):
 
1249
 
 
1250
    format = '0.9'
 
1251
 
 
1252
    def bzrdir_format(self):
 
1253
        format = bzrdir.BzrDirMetaFormat1()
 
1254
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
1255
        return format
 
1256
 
 
1257
 
 
1258
class V09BundleKnit1Tester(V08BundleTester):
 
1259
 
 
1260
    format = '0.9'
 
1261
 
 
1262
    def bzrdir_format(self):
 
1263
        format = bzrdir.BzrDirMetaFormat1()
 
1264
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
1265
        return format
 
1266
 
 
1267
 
 
1268
class V4BundleTester(BundleTester, TestCaseWithTransport):
 
1269
 
 
1270
    format = '4'
 
1271
 
 
1272
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
 
1273
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1274
        Make sure that the text generated is valid, and that it
 
1275
        can be applied against the base, and generate the same information.
 
1276
        
 
1277
        :return: The in-memory bundle 
 
1278
        """
 
1279
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1280
 
 
1281
        # This should also validate the generated bundle 
 
1282
        bundle = read_bundle(bundle_txt)
 
1283
        repository = self.b1.repository
 
1284
        for bundle_rev in bundle.real_revisions:
 
1285
            # These really should have already been checked when we read the
 
1286
            # bundle, since it computes the sha1 hash for the revision, which
 
1287
            # only will match if everything is okay, but lets be explicit about
 
1288
            # it
 
1289
            branch_rev = repository.get_revision(bundle_rev.revision_id)
 
1290
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
 
1291
                      'timestamp', 'timezone', 'message', 'committer', 
 
1292
                      'parent_ids', 'properties'):
 
1293
                self.assertEqual(getattr(branch_rev, a), 
 
1294
                                 getattr(bundle_rev, a))
 
1295
            self.assertEqual(len(branch_rev.parent_ids), 
 
1296
                             len(bundle_rev.parent_ids))
 
1297
        self.assertEqual(set(rev_ids),
 
1298
                         set([r.revision_id for r in bundle.real_revisions]))
 
1299
        self.valid_apply_bundle(base_rev_id, bundle,
 
1300
                                   checkout_dir=checkout_dir)
 
1301
 
 
1302
        return bundle
 
1303
 
 
1304
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
1305
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1306
        Munge the text so that it's invalid.
 
1307
 
 
1308
        :return: The in-memory bundle
 
1309
        """
 
1310
        from bzrlib.bundle import serializer
 
1311
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1312
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1313
        new_text = new_text.replace('<file file_id="exe-1"',
 
1314
                                    '<file executable="y" file_id="exe-1"')
 
1315
        new_text = new_text.replace('B222', 'B237')
 
1316
        bundle_txt = StringIO()
 
1317
        bundle_txt.write(serializer._get_bundle_header('4'))
 
1318
        bundle_txt.write('\n')
 
1319
        bundle_txt.write(new_text.encode('bz2'))
 
1320
        bundle_txt.seek(0)
 
1321
        bundle = read_bundle(bundle_txt)
 
1322
        self.valid_apply_bundle(base_rev_id, bundle)
 
1323
        return bundle
 
1324
 
 
1325
    def create_bundle_text(self, base_rev_id, rev_id):
 
1326
        bundle_txt = StringIO()
 
1327
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
1328
                               bundle_txt, format=self.format)
 
1329
        bundle_txt.seek(0)
 
1330
        self.assertEqual(bundle_txt.readline(), 
 
1331
                         '# Bazaar revision bundle v%s\n' % self.format)
 
1332
        self.assertEqual(bundle_txt.readline(), '#\n')
 
1333
        rev = self.b1.repository.get_revision(rev_id)
 
1334
        bundle_txt.seek(0)
 
1335
        return bundle_txt, rev_ids
 
1336
 
 
1337
    def get_bundle_tree(self, bundle, revision_id):
 
1338
        repository = self.make_repository('repo')
 
1339
        bundle.install_revisions(repository)
 
1340
        return repository.revision_tree(revision_id)
 
1341
 
 
1342
    def test_creation(self):
 
1343
        tree = self.make_branch_and_tree('tree')
 
1344
        self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
 
1345
        tree.add('file', 'fileid-2')
 
1346
        tree.commit('added file', rev_id='rev1')
 
1347
        self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
 
1348
        tree.commit('changed file', rev_id='rev2')
 
1349
        s = StringIO()
 
1350
        serializer = BundleSerializerV4('1.0')
 
1351
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
 
1352
        s.seek(0)
 
1353
        tree2 = self.make_branch_and_tree('target')
 
1354
        target_repo = tree2.branch.repository
 
1355
        install_bundle(target_repo, serializer.read(s))
 
1356
        target_repo.lock_read()
 
1357
        self.addCleanup(target_repo.unlock)
 
1358
        self.assertEqual({'1':'contents1\nstatic\n',
 
1359
            '2':'contents2\nstatic\n'},
 
1360
            dict(target_repo.iter_files_bytes(
 
1361
                [('fileid-2', 'rev1', '1'), ('fileid-2', 'rev2', '2')])))
 
1362
        rtree = target_repo.revision_tree('rev2')
 
1363
        inventory_vf = target_repo.inventories
 
1364
        # If the inventory store has a graph, it must match the revision graph.
 
1365
        self.assertSubset(
 
1366
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
 
1367
            [None, (('rev1',),)])
 
1368
        self.assertEqual('changed file',
 
1369
                         target_repo.get_revision('rev2').message)
 
1370
 
 
1371
    @staticmethod
 
1372
    def get_raw(bundle_file):
 
1373
        bundle_file.seek(0)
 
1374
        line = bundle_file.readline()
 
1375
        line = bundle_file.readline()
 
1376
        lines = bundle_file.readlines()
 
1377
        return ''.join(lines).decode('bz2')
 
1378
 
 
1379
    def test_copy_signatures(self):
 
1380
        tree_a = self.make_branch_and_tree('tree_a')
 
1381
        import bzrlib.gpg
 
1382
        import bzrlib.commit as commit
 
1383
        oldstrategy = bzrlib.gpg.GPGStrategy
 
1384
        branch = tree_a.branch
 
1385
        repo_a = branch.repository
 
1386
        tree_a.commit("base", allow_pointless=True, rev_id='A')
 
1387
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1388
        try:
 
1389
            from bzrlib.testament import Testament
 
1390
            # monkey patch gpg signing mechanism
 
1391
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
 
1392
            new_config = test_commit.MustSignConfig(branch)
 
1393
            commit.Commit(config=new_config).commit(message="base",
 
1394
                                                    allow_pointless=True,
 
1395
                                                    rev_id='B',
 
1396
                                                    working_tree=tree_a)
 
1397
            def sign(text):
 
1398
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
 
1399
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
 
1400
        finally:
 
1401
            bzrlib.gpg.GPGStrategy = oldstrategy
 
1402
        tree_b = self.make_branch_and_tree('tree_b')
 
1403
        repo_b = tree_b.branch.repository
 
1404
        s = StringIO()
 
1405
        serializer = BundleSerializerV4('4')
 
1406
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
 
1407
        s.seek(0)
 
1408
        install_bundle(repo_b, serializer.read(s))
 
1409
        self.assertTrue(repo_b.has_signature_for_revision_id('B'))
 
1410
        self.assertEqual(repo_b.get_signature_text('B'),
 
1411
                         repo_a.get_signature_text('B'))
 
1412
        s.seek(0)
 
1413
        # ensure repeat installs are harmless
 
1414
        install_bundle(repo_b, serializer.read(s))
 
1415
 
 
1416
 
 
1417
class V4WeaveBundleTester(V4BundleTester):
 
1418
 
 
1419
    def bzrdir_format(self):
 
1420
        return 'metaweave'
 
1421
 
 
1422
 
 
1423
class MungedBundleTester(object):
 
1424
 
 
1425
    def build_test_bundle(self):
 
1426
        wt = self.make_branch_and_tree('b1')
 
1427
 
 
1428
        self.build_tree(['b1/one'])
 
1429
        wt.add('one')
 
1430
        wt.commit('add one', rev_id='a@cset-0-1')
 
1431
        self.build_tree(['b1/two'])
 
1432
        wt.add('two')
 
1433
        wt.commit('add two', rev_id='a@cset-0-2',
 
1434
                  revprops={'branch-nick':'test'})
 
1435
 
 
1436
        bundle_txt = StringIO()
 
1437
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
 
1438
                               'a@cset-0-1', bundle_txt, self.format)
 
1439
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
 
1440
        bundle_txt.seek(0, 0)
 
1441
        return bundle_txt
 
1442
 
 
1443
    def check_valid(self, bundle):
 
1444
        """Check that after whatever munging, the final object is valid."""
 
1445
        self.assertEqual(['a@cset-0-2'],
 
1446
            [r.revision_id for r in bundle.real_revisions])
 
1447
 
 
1448
    def test_extra_whitespace(self):
 
1449
        bundle_txt = self.build_test_bundle()
 
1450
 
 
1451
        # Seek to the end of the file
 
1452
        # Adding one extra newline used to give us
 
1453
        # TypeError: float() argument must be a string or a number
 
1454
        bundle_txt.seek(0, 2)
 
1455
        bundle_txt.write('\n')
 
1456
        bundle_txt.seek(0)
 
1457
 
 
1458
        bundle = read_bundle(bundle_txt)
 
1459
        self.check_valid(bundle)
 
1460
 
 
1461
    def test_extra_whitespace_2(self):
 
1462
        bundle_txt = self.build_test_bundle()
 
1463
 
 
1464
        # Seek to the end of the file
 
1465
        # Adding two extra newlines used to give us
 
1466
        # MalformedPatches: The first line of all patches should be ...
 
1467
        bundle_txt.seek(0, 2)
 
1468
        bundle_txt.write('\n\n')
 
1469
        bundle_txt.seek(0)
 
1470
 
 
1471
        bundle = read_bundle(bundle_txt)
 
1472
        self.check_valid(bundle)
 
1473
 
 
1474
 
 
1475
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
 
1476
 
 
1477
    format = '0.9'
 
1478
 
 
1479
    def test_missing_trailing_whitespace(self):
 
1480
        bundle_txt = self.build_test_bundle()
 
1481
 
 
1482
        # Remove a trailing newline, it shouldn't kill the parser
 
1483
        raw = bundle_txt.getvalue()
 
1484
        # The contents of the bundle don't have to be this, but this
 
1485
        # test is concerned with the exact case where the serializer
 
1486
        # creates a blank line at the end, and fails if that
 
1487
        # line is stripped
 
1488
        self.assertEqual('\n\n', raw[-2:])
 
1489
        bundle_txt = StringIO(raw[:-1])
 
1490
 
 
1491
        bundle = read_bundle(bundle_txt)
 
1492
        self.check_valid(bundle)
 
1493
 
 
1494
    def test_opening_text(self):
 
1495
        bundle_txt = self.build_test_bundle()
 
1496
 
 
1497
        bundle_txt = StringIO("Some random\nemail comments\n"
 
1498
                              + bundle_txt.getvalue())
 
1499
 
 
1500
        bundle = read_bundle(bundle_txt)
 
1501
        self.check_valid(bundle)
 
1502
 
 
1503
    def test_trailing_text(self):
 
1504
        bundle_txt = self.build_test_bundle()
 
1505
 
 
1506
        bundle_txt = StringIO(bundle_txt.getvalue() +
 
1507
                              "Some trailing\nrandom\ntext\n")
 
1508
 
 
1509
        bundle = read_bundle(bundle_txt)
 
1510
        self.check_valid(bundle)
 
1511
 
 
1512
 
 
1513
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
 
1514
 
 
1515
    format = '4'
 
1516
 
 
1517
 
 
1518
class TestBundleWriterReader(TestCase):
 
1519
 
 
1520
    def test_roundtrip_record(self):
 
1521
        fileobj = StringIO()
 
1522
        writer = v4.BundleWriter(fileobj)
 
1523
        writer.begin()
 
1524
        writer.add_info_record(foo='bar')
 
1525
        writer._add_record("Record body", {'parents': ['1', '3'],
 
1526
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1527
        writer.end()
 
1528
        fileobj.seek(0)
 
1529
        reader = v4.BundleReader(fileobj, stream_input=True)
 
1530
        record_iter = reader.iter_records()
 
1531
        record = record_iter.next()
 
1532
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1533
            'info', None, None), record)
 
1534
        record = record_iter.next()
 
1535
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
 
1536
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
 
1537
                          record)
 
1538
 
 
1539
    def test_roundtrip_record_memory_hungry(self):
 
1540
        fileobj = StringIO()
 
1541
        writer = v4.BundleWriter(fileobj)
 
1542
        writer.begin()
 
1543
        writer.add_info_record(foo='bar')
 
1544
        writer._add_record("Record body", {'parents': ['1', '3'],
 
1545
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1546
        writer.end()
 
1547
        fileobj.seek(0)
 
1548
        reader = v4.BundleReader(fileobj, stream_input=False)
 
1549
        record_iter = reader.iter_records()
 
1550
        record = record_iter.next()
 
1551
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1552
            'info', None, None), record)
 
1553
        record = record_iter.next()
 
1554
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
 
1555
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
 
1556
                          record)
 
1557
 
 
1558
    def test_encode_name(self):
 
1559
        self.assertEqual('revision/rev1',
 
1560
            v4.BundleWriter.encode_name('revision', 'rev1'))
 
1561
        self.assertEqual('file/rev//1/file-id-1',
 
1562
            v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
 
1563
        self.assertEqual('info',
 
1564
            v4.BundleWriter.encode_name('info', None, None))
 
1565
 
 
1566
    def test_decode_name(self):
 
1567
        self.assertEqual(('revision', 'rev1', None),
 
1568
            v4.BundleReader.decode_name('revision/rev1'))
 
1569
        self.assertEqual(('file', 'rev/1', 'file-id-1'),
 
1570
            v4.BundleReader.decode_name('file/rev//1/file-id-1'))
 
1571
        self.assertEqual(('info', None, None),
 
1572
                         v4.BundleReader.decode_name('info'))
 
1573
 
 
1574
    def test_too_many_names(self):
 
1575
        fileobj = StringIO()
 
1576
        writer = v4.BundleWriter(fileobj)
 
1577
        writer.begin()
 
1578
        writer.add_info_record(foo='bar')
 
1579
        writer._container.add_bytes_record('blah', ['two', 'names'])
 
1580
        writer.end()
 
1581
        fileobj.seek(0)
 
1582
        record_iter = v4.BundleReader(fileobj).iter_records()
 
1583
        record = record_iter.next()
 
1584
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1585
            'info', None, None), record)
 
1586
        self.assertRaises(BadBundle, record_iter.next)
 
1587
 
 
1588
 
 
1589
class TestReadMergeableFromUrl(TestCaseWithTransport):
 
1590
 
 
1591
    def test_read_mergeable_skips_local(self):
 
1592
        """A local bundle named like the URL should not be read.
 
1593
        """
 
1594
        out, wt = test_read_bundle.create_bundle_file(self)
 
1595
        class FooService(object):
 
1596
            """A directory service that always returns source"""
 
1597
 
 
1598
            def look_up(self, name, url):
 
1599
                return 'source'
 
1600
        directories.register('foo:', FooService, 'Testing directory service')
 
1601
        self.addCleanup(lambda: directories.remove('foo:'))
 
1602
        self.build_tree_contents([('./foo:bar', out.getvalue())])
 
1603
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
 
1604
                          'foo:bar')