~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Wouter van Heyst
  • Date: 2006-06-06 22:37:30 UTC
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: larstiq@larstiq.dyndns.org-20060606223730-a308c5429fc6c617
change branch.{get,set}_parent to store a relative path but return full urls

Show diffs side-by-side

added added

removed removed

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