~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-09-16 14:03:54 UTC
  • mfrom: (2017.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060916140354-1a9932f525bb7182
(robertc) Add MemoryTree and TreeBuilder test helpers. Also test behavior of transport.has('/') which caused failures in this when merging, and as a result cleanup the sftp path normalisation logic.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2004-2006 by Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
from cStringIO import StringIO
18
18
import os
19
 
import socket
20
19
import sys
21
 
import threading
 
20
import tempfile
22
21
 
23
 
from bzrlib import (
24
 
    bzrdir,
25
 
    diff,
26
 
    errors,
27
 
    inventory,
28
 
    merge,
29
 
    osutils,
30
 
    revision as _mod_revision,
31
 
    symbol_versioning,
32
 
    tests,
33
 
    treebuilder,
34
 
    )
35
 
from bzrlib.bundle import read_mergeable_from_url
 
22
from bzrlib import inventory, treebuilder
 
23
from bzrlib.builtins import merge
 
24
from bzrlib.bzrdir import BzrDir
36
25
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
37
26
from bzrlib.bundle.bundle_data import BundleTree
38
 
from bzrlib.directory_service import directories
39
 
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
40
 
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
41
 
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
42
 
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
43
 
from bzrlib.repofmt import knitrepo
44
 
from bzrlib.tests import (
45
 
    test_read_bundle,
46
 
    test_commit,
47
 
    )
 
27
from bzrlib.bundle.serializer import write_bundle, read_bundle
 
28
from bzrlib.branch import Branch
 
29
from bzrlib.diff import internal_diff
 
30
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
 
31
                           NoSuchFile,)
 
32
from bzrlib.merge import Merge3Merger
 
33
from bzrlib.osutils import has_symlinks, sha_file
 
34
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
 
35
                          TestCase, TestSkipped)
48
36
from bzrlib.transform import TreeTransform
49
 
 
50
 
 
51
 
def get_text(vf, key):
52
 
    """Get the fulltext for a given revision id that is present in the vf"""
53
 
    stream = vf.get_record_stream([key], 'unordered', True)
54
 
    record = stream.next()
55
 
    return record.get_bytes_as('fulltext')
56
 
 
57
 
 
58
 
def get_inventory_text(repo, revision_id):
59
 
    """Get the fulltext for the inventory at revision id"""
60
 
    repo.lock_read()
61
 
    try:
62
 
        return get_text(repo.inventories, (revision_id,))
63
 
    finally:
64
 
        repo.unlock()
 
37
from bzrlib.workingtree import WorkingTree
65
38
 
66
39
 
67
40
class MockTree(object):
68
 
 
69
41
    def __init__(self):
70
42
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
71
43
        object.__init__(self)
76
48
 
77
49
    inventory = property(lambda x:x)
78
50
 
79
 
    def all_file_ids(self):
80
 
        return set(self.paths.keys())
 
51
    def __iter__(self):
 
52
        return self.paths.iterkeys()
81
53
 
82
54
    def __getitem__(self, file_id):
83
55
        if file_id == self.root.file_id:
113
85
            ie = InventoryDirectory(file_id, name, parent_id)
114
86
        elif kind == 'file':
115
87
            ie = InventoryFile(file_id, name, parent_id)
116
 
            ie.text_sha1 = text_sha_1
117
 
            ie.text_size = text_size
118
88
        elif kind == 'symlink':
119
89
            ie = InventoryLink(file_id, name, parent_id)
120
90
        else:
121
 
            raise errors.BzrError('unknown kind %r' % kind)
 
91
            raise BzrError('unknown kind %r' % kind)
 
92
        ie.text_sha1 = text_sha_1
 
93
        ie.text_size = text_size
122
94
        return ie
123
95
 
124
96
    def add_dir(self, file_id, path):
125
97
        self.paths[file_id] = path
126
98
        self.ids[path] = file_id
127
 
 
 
99
    
128
100
    def add_file(self, file_id, path, contents):
129
101
        self.add_dir(file_id, path)
130
102
        self.contents[file_id] = contents
144
116
        result.seek(0,0)
145
117
        return result
146
118
 
147
 
    def get_file_revision(self, file_id):
148
 
        return self.inventory[file_id].revision
149
 
 
150
119
    def contents_stats(self, file_id):
151
120
        if file_id not in self.contents:
152
121
            return None, None
153
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
122
        text_sha1 = sha_file(self.get_file(file_id))
154
123
        return text_sha1, len(self.contents[file_id])
155
124
 
156
125
 
157
 
class BTreeTester(tests.TestCase):
 
126
class BTreeTester(TestCase):
158
127
    """A simple unittest tester for the BundleTree class."""
159
128
 
160
129
    def make_tree_1(self):
164
133
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
165
134
        mtree.add_dir("d", "grandparent/alt_parent")
166
135
        return BundleTree(mtree, ''), mtree
167
 
 
 
136
        
168
137
    def test_renames(self):
169
138
        """Ensure that file renames have the proper effect on children"""
170
139
        btree = self.make_tree_1()[0]
171
140
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
172
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
141
        self.assertEqual(btree.old_path("grandparent/parent"), 
173
142
                         "grandparent/parent")
174
143
        self.assertEqual(btree.old_path("grandparent/parent/file"),
175
144
                         "grandparent/parent/file")
182
151
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
183
152
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
184
153
 
185
 
        self.assertTrue(btree.path2id("grandparent2") is None)
186
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
187
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
154
        assert btree.path2id("grandparent2") is None
 
155
        assert btree.path2id("grandparent2/parent") is None
 
156
        assert btree.path2id("grandparent2/parent/file") is None
188
157
 
189
158
        btree.note_rename("grandparent", "grandparent2")
190
 
        self.assertTrue(btree.old_path("grandparent") is None)
191
 
        self.assertTrue(btree.old_path("grandparent/parent") is None)
192
 
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
 
159
        assert btree.old_path("grandparent") is None
 
160
        assert btree.old_path("grandparent/parent") is None
 
161
        assert btree.old_path("grandparent/parent/file") is None
193
162
 
194
163
        self.assertEqual(btree.id2path("a"), "grandparent2")
195
164
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
199
168
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
200
169
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
201
170
 
202
 
        self.assertTrue(btree.path2id("grandparent") is None)
203
 
        self.assertTrue(btree.path2id("grandparent/parent") is None)
204
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
171
        assert btree.path2id("grandparent") is None
 
172
        assert btree.path2id("grandparent/parent") is None
 
173
        assert btree.path2id("grandparent/parent/file") is None
205
174
 
206
175
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
207
176
        self.assertEqual(btree.id2path("a"), "grandparent2")
212
181
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
213
182
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
214
183
 
215
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
216
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
184
        assert btree.path2id("grandparent2/parent") is None
 
185
        assert btree.path2id("grandparent2/parent/file") is None
217
186
 
218
 
        btree.note_rename("grandparent/parent/file",
 
187
        btree.note_rename("grandparent/parent/file", 
219
188
                          "grandparent2/parent2/file2")
220
189
        self.assertEqual(btree.id2path("a"), "grandparent2")
221
190
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
225
194
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
226
195
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
227
196
 
228
 
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
 
197
        assert btree.path2id("grandparent2/parent2/file") is None
229
198
 
230
199
    def test_moves(self):
231
200
        """Ensure that file moves have the proper effect on children"""
232
201
        btree = self.make_tree_1()[0]
233
 
        btree.note_rename("grandparent/parent/file",
 
202
        btree.note_rename("grandparent/parent/file", 
234
203
                          "grandparent/alt_parent/file")
235
204
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
236
205
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
237
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
206
        assert btree.path2id("grandparent/parent/file") is None
238
207
 
239
208
    def unified_diff(self, old, new):
240
209
        out = StringIO()
241
 
        diff.internal_diff("old", old, "new", new, out)
 
210
        internal_diff("old", old, "new", new, out)
242
211
        out.seek(0,0)
243
212
        return out.read()
244
213
 
245
214
    def make_tree_2(self):
246
215
        btree = self.make_tree_1()[0]
247
 
        btree.note_rename("grandparent/parent/file",
 
216
        btree.note_rename("grandparent/parent/file", 
248
217
                          "grandparent/alt_parent/file")
249
 
        self.assertTrue(btree.id2path("e") is None)
250
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
218
        assert btree.id2path("e") is None
 
219
        assert btree.path2id("grandparent/parent/file") is None
251
220
        btree.note_id("e", "grandparent/parent/file")
252
221
        return btree
253
222
 
279
248
    def make_tree_3(self):
280
249
        btree, mtree = self.make_tree_1()
281
250
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
282
 
        btree.note_rename("grandparent/parent/file",
 
251
        btree.note_rename("grandparent/parent/file", 
283
252
                          "grandparent/alt_parent/file")
284
 
        btree.note_rename("grandparent/parent/topping",
 
253
        btree.note_rename("grandparent/parent/topping", 
285
254
                          "grandparent/alt_parent/stopping")
286
255
        return btree
287
256
 
311
280
        btree = self.make_tree_1()[0]
312
281
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
313
282
        btree.note_deletion("grandparent/parent/file")
314
 
        self.assertTrue(btree.id2path("c") is None)
315
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
283
        assert btree.id2path("c") is None
 
284
        assert btree.path2id("grandparent/parent/file") is None
316
285
 
317
286
    def sorted_ids(self, tree):
318
287
        ids = list(tree)
326
295
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
327
296
        btree.note_deletion("grandparent/parent/file")
328
297
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
329
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
298
        btree.note_last_changed("grandparent/alt_parent/fool", 
330
299
                                "revisionidiguess")
331
300
        self.assertEqual(self.sorted_ids(btree),
332
301
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
333
302
 
334
303
 
335
 
class BundleTester1(tests.TestCaseWithTransport):
336
 
 
337
 
    def test_mismatched_bundle(self):
338
 
        format = bzrdir.BzrDirMetaFormat1()
339
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
340
 
        serializer = BundleSerializerV08('0.8')
341
 
        b = self.make_branch('.', format=format)
342
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
343
 
                          b.repository, [], {}, StringIO())
344
 
 
345
 
    def test_matched_bundle(self):
346
 
        """Don't raise IncompatibleBundleFormat for knit2 and bundle0.9"""
347
 
        format = bzrdir.BzrDirMetaFormat1()
348
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
349
 
        serializer = BundleSerializerV09('0.9')
350
 
        b = self.make_branch('.', format=format)
351
 
        serializer.write(b.repository, [], {}, StringIO())
352
 
 
353
 
    def test_mismatched_model(self):
354
 
        """Try copying a bundle from knit2 to knit1"""
355
 
        format = bzrdir.BzrDirMetaFormat1()
356
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
357
 
        source = self.make_branch_and_tree('source', format=format)
358
 
        source.commit('one', rev_id='one-id')
359
 
        source.commit('two', rev_id='two-id')
360
 
        text = StringIO()
361
 
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
362
 
                     format='0.9')
363
 
        text.seek(0)
364
 
 
365
 
        format = bzrdir.BzrDirMetaFormat1()
366
 
        format.repository_format = knitrepo.RepositoryFormatKnit1()
367
 
        target = self.make_branch('target', format=format)
368
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
369
 
                          target.repository, read_bundle(text))
370
 
 
371
 
 
372
 
class BundleTester(object):
373
 
 
374
 
    def bzrdir_format(self):
375
 
        format = bzrdir.BzrDirMetaFormat1()
376
 
        format.repository_format = knitrepo.RepositoryFormatKnit1()
377
 
        return format
378
 
 
379
 
    def make_branch_and_tree(self, path, format=None):
380
 
        if format is None:
381
 
            format = self.bzrdir_format()
382
 
        return tests.TestCaseWithTransport.make_branch_and_tree(
383
 
            self, path, format)
384
 
 
385
 
    def make_branch(self, path, format=None):
386
 
        if format is None:
387
 
            format = self.bzrdir_format()
388
 
        return tests.TestCaseWithTransport.make_branch(self, path, format)
 
304
class BundleTester(TestCaseWithTransport):
389
305
 
390
306
    def create_bundle_text(self, base_rev_id, rev_id):
391
307
        bundle_txt = StringIO()
392
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
393
 
                               bundle_txt, format=self.format)
 
308
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
309
                               bundle_txt)
394
310
        bundle_txt.seek(0)
395
 
        self.assertEqual(bundle_txt.readline(),
396
 
                         '# Bazaar revision bundle v%s\n' % self.format)
 
311
        self.assertEqual(bundle_txt.readline(), 
 
312
                         '# Bazaar revision bundle v0.8\n')
397
313
        self.assertEqual(bundle_txt.readline(), '#\n')
398
314
 
399
315
        rev = self.b1.repository.get_revision(rev_id)
400
316
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
401
317
                         u'# message:\n')
 
318
 
 
319
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
402
320
        bundle_txt.seek(0)
403
321
        return bundle_txt, rev_ids
404
322
 
406
324
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
407
325
        Make sure that the text generated is valid, and that it
408
326
        can be applied against the base, and generate the same information.
409
 
 
410
 
        :return: The in-memory bundle
 
327
        
 
328
        :return: The in-memory bundle 
411
329
        """
412
330
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
413
331
 
414
 
        # This should also validate the generated bundle
 
332
        # This should also validate the generated bundle 
415
333
        bundle = read_bundle(bundle_txt)
416
334
        repository = self.b1.repository
417
335
        for bundle_rev in bundle.real_revisions:
421
339
            # it
422
340
            branch_rev = repository.get_revision(bundle_rev.revision_id)
423
341
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
424
 
                      'timestamp', 'timezone', 'message', 'committer',
 
342
                      'timestamp', 'timezone', 'message', 'committer', 
425
343
                      'parent_ids', 'properties'):
426
 
                self.assertEqual(getattr(branch_rev, a),
 
344
                self.assertEqual(getattr(branch_rev, a), 
427
345
                                 getattr(bundle_rev, a))
428
 
            self.assertEqual(len(branch_rev.parent_ids),
 
346
            self.assertEqual(len(branch_rev.parent_ids), 
429
347
                             len(bundle_rev.parent_ids))
430
 
        self.assertEqual(rev_ids,
 
348
        self.assertEqual(rev_ids, 
431
349
                         [r.revision_id for r in bundle.real_revisions])
432
350
        self.valid_apply_bundle(base_rev_id, bundle,
433
351
                                   checkout_dir=checkout_dir)
437
355
    def get_invalid_bundle(self, base_rev_id, rev_id):
438
356
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
439
357
        Munge the text so that it's invalid.
440
 
 
 
358
        
441
359
        :return: The in-memory bundle
442
360
        """
443
361
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
444
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
362
        new_text = bundle_txt.getvalue().replace('executable:no', 
445
363
                                               'executable:yes')
446
364
        bundle_txt = StringIO(new_text)
447
365
        bundle = read_bundle(bundle_txt)
448
366
        self.valid_apply_bundle(base_rev_id, bundle)
449
 
        return bundle
 
367
        return bundle 
450
368
 
451
369
    def test_non_bundle(self):
452
 
        self.assertRaises(errors.NotABundle,
453
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
370
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
454
371
 
455
372
    def test_malformed(self):
456
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
373
        self.assertRaises(BadBundle, read_bundle, 
457
374
                          StringIO('# Bazaar revision bundle v'))
458
375
 
459
376
    def test_crlf_bundle(self):
460
377
        try:
461
378
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
462
 
        except errors.BadBundle:
 
379
        except BadBundle:
463
380
            # It is currently permitted for bundles with crlf line endings to
464
381
            # make read_bundle raise a BadBundle, but this should be fixed.
465
382
            # Anything else, especially NotABundle, is an error.
470
387
        """
471
388
 
472
389
        if checkout_dir is None:
473
 
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
 
390
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
474
391
        else:
475
392
            if not os.path.exists(checkout_dir):
476
393
                os.mkdir(checkout_dir)
477
 
        tree = self.make_branch_and_tree(checkout_dir)
 
394
        tree = BzrDir.create_standalone_workingtree(checkout_dir)
478
395
        s = StringIO()
479
 
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
480
 
                                 format=self.format)
 
396
        ancestors = write_bundle(self.b1.repository, rev_id, None, s)
481
397
        s.seek(0)
482
 
        self.assertIsInstance(s.getvalue(), str)
 
398
        assert isinstance(s.getvalue(), str), (
 
399
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
483
400
        install_bundle(tree.branch.repository, read_bundle(s))
484
401
        for ancestor in ancestors:
485
402
            old = self.b1.repository.revision_tree(ancestor)
486
403
            new = tree.branch.repository.revision_tree(ancestor)
487
 
            old.lock_read()
488
 
            new.lock_read()
489
 
            try:
490
 
                # Check that there aren't any inventory level changes
491
 
                delta = new.changes_from(old)
492
 
                self.assertFalse(delta.has_changed(),
493
 
                                 'Revision %s not copied correctly.'
494
 
                                 % (ancestor,))
495
 
 
496
 
                # Now check that the file contents are all correct
497
 
                for inventory_id in old.all_file_ids():
498
 
                    try:
499
 
                        old_file = old.get_file(inventory_id)
500
 
                    except errors.NoSuchFile:
501
 
                        continue
502
 
                    if old_file is None:
503
 
                        continue
504
 
                    self.assertEqual(old_file.read(),
505
 
                                     new.get_file(inventory_id).read())
506
 
            finally:
507
 
                new.unlock()
508
 
                old.unlock()
509
 
        if not _mod_revision.is_null(rev_id):
 
404
 
 
405
            # Check that there aren't any inventory level changes
 
406
            delta = new.changes_from(old)
 
407
            self.assertFalse(delta.has_changed(),
 
408
                             'Revision %s not copied correctly.'
 
409
                             % (ancestor,))
 
410
 
 
411
            # Now check that the file contents are all correct
 
412
            for inventory_id in old:
 
413
                try:
 
414
                    old_file = old.get_file(inventory_id)
 
415
                except NoSuchFile:
 
416
                    continue
 
417
                if old_file is None:
 
418
                    continue
 
419
                self.assertEqual(old_file.read(),
 
420
                                 new.get_file(inventory_id).read())
 
421
        if rev_id is not None:
510
422
            rh = self.b1.revision_history()
511
 
            self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
512
 
                tree.branch.set_revision_history, rh[:rh.index(rev_id)+1])
 
423
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
513
424
            tree.update()
514
425
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
515
426
            self.assertFalse(delta.has_changed(),
516
 
                             'Working tree has modifications: %s' % delta)
 
427
                             'Working tree has modifications')
517
428
        return tree
518
429
 
519
430
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
521
432
        sure everything matches the builtin branch.
522
433
        """
523
434
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
524
 
        to_tree.lock_write()
525
 
        try:
526
 
            self._valid_apply_bundle(base_rev_id, info, to_tree)
527
 
        finally:
528
 
            to_tree.unlock()
529
 
 
530
 
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
531
435
        original_parents = to_tree.get_parent_ids()
532
436
        repository = to_tree.branch.repository
533
437
        original_parents = to_tree.get_parent_ids()
534
438
        self.assertIs(repository.has_revision(base_rev_id), True)
535
439
        for rev in info.real_revisions:
536
440
            self.assert_(not repository.has_revision(rev.revision_id),
537
 
                'Revision {%s} present before applying bundle'
 
441
                'Revision {%s} present before applying bundle' 
538
442
                % rev.revision_id)
539
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
443
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
540
444
 
541
445
        for rev in info.real_revisions:
542
446
            self.assert_(repository.has_revision(rev.revision_id),
543
 
                'Missing revision {%s} after applying bundle'
 
447
                'Missing revision {%s} after applying bundle' 
544
448
                % rev.revision_id)
545
449
 
546
450
        self.assert_(to_tree.branch.repository.has_revision(info.target))
552
456
        rev = info.real_revisions[-1]
553
457
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
554
458
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
555
 
 
 
459
        
556
460
        # TODO: make sure the target tree is identical to base tree
557
461
        #       we might also check the working tree.
558
462
 
577
481
        self.tree1 = self.make_branch_and_tree('b1')
578
482
        self.b1 = self.tree1.branch
579
483
 
580
 
        self.build_tree_contents([('b1/one', 'one\n')])
581
 
        self.tree1.add('one', 'one-id')
582
 
        self.tree1.set_root_id('root-id')
 
484
        open('b1/one', 'wb').write('one\n')
 
485
        self.tree1.add('one')
583
486
        self.tree1.commit('add one', rev_id='a@cset-0-1')
584
487
 
585
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
 
488
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
 
489
        # FIXME: The current write_bundle api no longer supports
 
490
        #        setting a custom summary message
 
491
        #        We should re-introduce the ability, and update
 
492
        #        the tests to make sure it works.
 
493
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
 
494
        #         message='With a specialized message')
586
495
 
587
496
        # Make sure we can handle files with spaces, tabs, other
588
497
        # bogus characters
596
505
                , 'b1/sub/sub/'
597
506
                , 'b1/sub/sub/nonempty.txt'
598
507
                ])
599
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
600
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
508
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
509
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
601
510
        tt = TreeTransform(self.tree1)
602
511
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
603
512
        tt.apply()
604
 
        # have to fix length of file-id so that we can predictably rewrite
605
 
        # a (length-prefixed) record containing it later.
606
 
        self.tree1.add('with space.txt', 'withspace-id')
607
513
        self.tree1.add([
608
 
                  'dir'
 
514
                'with space.txt'
 
515
                , 'dir'
609
516
                , 'dir/filein subdir.c'
610
517
                , 'dir/WithCaps.txt'
611
518
                , 'dir/ pre space'
619
526
 
620
527
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
621
528
 
622
 
        # Check a rollup bundle
623
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
 
529
        # Check a rollup bundle 
 
530
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
624
531
 
625
532
        # Now delete entries
626
533
        self.tree1.remove(
633
540
        tt.set_executability(False, trans_id)
634
541
        tt.apply()
635
542
        self.tree1.commit('removed', rev_id='a@cset-0-3')
636
 
 
 
543
        
637
544
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
638
 
        self.assertRaises((errors.TestamentMismatch,
639
 
            errors.VersionedFileInvalidChecksum,
640
 
            errors.BadBundle), self.get_invalid_bundle,
641
 
            'a@cset-0-2', 'a@cset-0-3')
642
 
        # Check a rollup bundle
643
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
 
545
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
 
546
                          'a@cset-0-2', 'a@cset-0-3')
 
547
        # Check a rollup bundle 
 
548
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
644
549
 
645
550
        # Now move the directory
646
551
        self.tree1.rename_one('dir', 'sub/dir')
647
552
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
648
553
 
649
554
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
650
 
        # Check a rollup bundle
651
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
 
555
        # Check a rollup bundle 
 
556
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
652
557
 
653
558
        # Modified files
654
559
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
655
 
        open('b1/sub/dir/ pre space', 'ab').write(
656
 
             '\r\nAdding some\r\nDOS format lines\r\n')
 
560
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
657
561
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
658
 
        self.tree1.rename_one('sub/dir/ pre space',
 
562
        self.tree1.rename_one('sub/dir/ pre space', 
659
563
                              'sub/ start space')
660
564
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
661
565
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
667
571
                          verbose=False)
668
572
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
669
573
        other = self.get_checkout('a@cset-0-5')
670
 
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
671
 
                                       'a@cset-0-5')
672
 
        tree2_inv = get_inventory_text(other.branch.repository,
673
 
                                       'a@cset-0-5')
674
 
        self.assertEqualDiff(tree1_inv, tree2_inv)
675
574
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
676
575
        other.commit('rename file', rev_id='a@cset-0-6b')
677
 
        self.tree1.merge_from_branch(other.branch)
 
576
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
678
577
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
679
578
                          verbose=False)
680
579
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
681
580
 
682
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
683
 
        link_id = 'link-1'
684
 
 
685
 
        self.requireFeature(tests.SymlinkFeature)
686
 
        self.tree1 = self.make_branch_and_tree('b1')
 
581
    def test_symlink_bundle(self):
 
582
        if not has_symlinks():
 
583
            raise TestSkipped("No symlink support")
 
584
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
687
585
        self.b1 = self.tree1.branch
688
 
 
689
586
        tt = TreeTransform(self.tree1)
690
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
587
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
691
588
        tt.apply()
692
589
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
693
 
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
694
 
        if getattr(bundle ,'revision_tree', None) is not None:
695
 
            # Not all bundle formats supports revision_tree
696
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
697
 
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
698
 
 
 
590
        self.get_valid_bundle(None, 'l@cset-0-1')
699
591
        tt = TreeTransform(self.tree1)
700
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
592
        trans_id = tt.trans_id_tree_file_id('link-1')
701
593
        tt.adjust_path('link2', tt.root, trans_id)
702
594
        tt.delete_contents(trans_id)
703
 
        tt.create_symlink(new_link_target, trans_id)
 
595
        tt.create_symlink('mars', trans_id)
704
596
        tt.apply()
705
597
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
706
 
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
707
 
        if getattr(bundle ,'revision_tree', None) is not None:
708
 
            # Not all bundle formats supports revision_tree
709
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
710
 
            self.assertEqual(new_link_target,
711
 
                             bund_tree.get_symlink_target(link_id))
712
 
 
 
598
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
713
599
        tt = TreeTransform(self.tree1)
714
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
600
        trans_id = tt.trans_id_tree_file_id('link-1')
715
601
        tt.delete_contents(trans_id)
716
602
        tt.create_symlink('jupiter', trans_id)
717
603
        tt.apply()
718
604
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
719
 
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
720
 
 
 
605
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
721
606
        tt = TreeTransform(self.tree1)
722
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
607
        trans_id = tt.trans_id_tree_file_id('link-1')
723
608
        tt.delete_contents(trans_id)
724
609
        tt.apply()
725
610
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
726
 
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
727
 
 
728
 
    def test_symlink_bundle(self):
729
 
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
730
 
 
731
 
    def test_unicode_symlink_bundle(self):
732
 
        self.requireFeature(tests.UnicodeFilenameFeature)
733
 
        self._test_symlink_bundle(u'\N{Euro Sign}link',
734
 
                                  u'bar/\N{Euro Sign}foo',
735
 
                                  u'mars\N{Euro Sign}')
 
611
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
736
612
 
737
613
    def test_binary_bundle(self):
738
 
        self.tree1 = self.make_branch_and_tree('b1')
 
614
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
739
615
        self.b1 = self.tree1.branch
740
616
        tt = TreeTransform(self.tree1)
741
 
 
 
617
        
742
618
        # Add
743
619
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
744
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
745
 
            'binary-2')
 
620
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
746
621
        tt.apply()
747
622
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
748
 
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
623
        self.get_valid_bundle(None, 'b@cset-0-1')
749
624
 
750
625
        # Delete
751
626
        tt = TreeTransform(self.tree1)
775
650
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
776
651
 
777
652
        # Rollup
778
 
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
653
        self.get_valid_bundle(None, 'b@cset-0-4')
779
654
 
780
655
    def test_last_modified(self):
781
 
        self.tree1 = self.make_branch_and_tree('b1')
 
656
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
782
657
        self.b1 = self.tree1.branch
783
658
        tt = TreeTransform(self.tree1)
784
659
        tt.new_file('file', tt.root, 'file', 'file')
799
674
        tt.create_file('file2', trans_id)
800
675
        tt.apply()
801
676
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
802
 
        self.tree1.merge_from_branch(other.branch)
 
677
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
803
678
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
804
679
                          verbose=False)
805
680
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
806
681
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
807
682
 
808
683
    def test_hide_history(self):
809
 
        self.tree1 = self.make_branch_and_tree('b1')
 
684
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
810
685
        self.b1 = self.tree1.branch
811
686
 
812
687
        open('b1/one', 'wb').write('one\n')
818
693
        self.tree1.commit('modify', rev_id='a@cset-0-3')
819
694
        bundle_file = StringIO()
820
695
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
821
 
                               'a@cset-0-1', bundle_file, format=self.format)
822
 
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
823
 
        self.assertContainsRe(self.get_raw(bundle_file), 'one')
824
 
        self.assertContainsRe(self.get_raw(bundle_file), 'three')
825
 
 
826
 
    def test_bundle_same_basis(self):
827
 
        """Ensure using the basis as the target doesn't cause an error"""
828
 
        self.tree1 = self.make_branch_and_tree('b1')
829
 
        self.tree1.commit('add file', rev_id='a@cset-0-1')
830
 
        bundle_file = StringIO()
831
 
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-1',
832
696
                               'a@cset-0-1', bundle_file)
833
 
 
834
 
    @staticmethod
835
 
    def get_raw(bundle_file):
836
 
        return bundle_file.getvalue()
 
697
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
 
698
        self.assertContainsRe(bundle_file.getvalue(), 'one')
 
699
        self.assertContainsRe(bundle_file.getvalue(), 'three')
837
700
 
838
701
    def test_unicode_bundle(self):
839
 
        self.requireFeature(tests.UnicodeFilenameFeature)
840
702
        # Handle international characters
841
703
        os.mkdir('b1')
842
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
704
        try:
 
705
            f = open(u'b1/with Dod\xe9', 'wb')
 
706
        except UnicodeEncodeError:
 
707
            raise TestSkipped("Filesystem doesn't support unicode")
843
708
 
844
709
        self.tree1 = self.make_branch_and_tree('b1')
845
710
        self.b1 = self.tree1.branch
849
714
            u'William Dod\xe9\n').encode('utf-8'))
850
715
        f.close()
851
716
 
852
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
853
 
        self.tree1.commit(u'i18n commit from William Dod\xe9',
 
717
        self.tree1.add([u'with Dod\xe9'])
 
718
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
854
719
                          rev_id='i18n-1', committer=u'William Dod\xe9')
855
720
 
856
721
        # Add
857
 
        bundle = self.get_valid_bundle('null:', 'i18n-1')
 
722
        bundle = self.get_valid_bundle(None, 'i18n-1')
858
723
 
859
724
        # Modified
860
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
725
        f = open(u'b1/with Dod\xe9', 'wb')
861
726
        f.write(u'Modified \xb5\n'.encode('utf8'))
862
727
        f.close()
863
728
        self.tree1.commit(u'modified', rev_id='i18n-2')
864
729
 
865
730
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
866
 
 
 
731
        
867
732
        # Renamed
868
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
733
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
869
734
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
870
735
                          committer=u'Erik B\xe5gfors')
871
736
 
872
737
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
873
738
 
874
739
        # Removed
875
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
740
        self.tree1.remove([u'B\xe5gfors'])
876
741
        self.tree1.commit(u'removed', rev_id='i18n-4')
877
742
 
878
743
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
879
744
 
880
745
        # Rollup
881
 
        bundle = self.get_valid_bundle('null:', 'i18n-4')
 
746
        bundle = self.get_valid_bundle(None, 'i18n-4')
882
747
 
883
748
 
884
749
    def test_whitespace_bundle(self):
885
750
        if sys.platform in ('win32', 'cygwin'):
886
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
887
 
                                    ' with tabs or trailing spaces')
 
751
            raise TestSkipped('Windows doesn\'t support filenames'
 
752
                              ' with tabs or trailing spaces')
888
753
        self.tree1 = self.make_branch_and_tree('b1')
889
754
        self.b1 = self.tree1.branch
890
755
 
896
761
        # Added
897
762
        self.tree1.commit('funky whitespace', rev_id='white-1')
898
763
 
899
 
        bundle = self.get_valid_bundle('null:', 'white-1')
 
764
        bundle = self.get_valid_bundle(None, 'white-1')
900
765
 
901
766
        # Modified
902
767
        open('b1/trailing space ', 'ab').write('add some text\n')
915
780
        self.tree1.commit('removed', rev_id='white-4')
916
781
 
917
782
        bundle = self.get_valid_bundle('white-3', 'white-4')
918
 
 
 
783
        
919
784
        # Now test a complet roll-up
920
 
        bundle = self.get_valid_bundle('null:', 'white-4')
 
785
        bundle = self.get_valid_bundle(None, 'white-4')
921
786
 
922
787
    def test_alt_timezone_bundle(self):
923
788
        self.tree1 = self.make_branch_and_memory_tree('b1')
933
798
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
934
799
                          timezone=19800, timestamp=1152544886.0)
935
800
 
936
 
        bundle = self.get_valid_bundle('null:', 'tz-1')
937
 
 
 
801
        bundle = self.get_valid_bundle(None, 'tz-1')
 
802
        
938
803
        rev = bundle.revisions[0]
939
804
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
940
805
        self.assertEqual(19800, rev.timezone)
945
810
        self.tree1 = self.make_branch_and_tree('b1')
946
811
        self.b1 = self.tree1.branch
947
812
        self.tree1.commit('message', rev_id='revid1')
948
 
        bundle = self.get_valid_bundle('null:', 'revid1')
949
 
        tree = self.get_bundle_tree(bundle, 'revid1')
 
813
        bundle = self.get_valid_bundle(None, 'revid1')
 
814
        tree = bundle.revision_tree(self.b1.repository, 'revid1')
950
815
        self.assertEqual('revid1', tree.inventory.root.revision)
951
816
 
952
 
    def test_install_revisions(self):
953
 
        self.tree1 = self.make_branch_and_tree('b1')
954
 
        self.b1 = self.tree1.branch
955
 
        self.tree1.commit('message', rev_id='rev2a')
956
 
        bundle = self.get_valid_bundle('null:', 'rev2a')
957
 
        branch2 = self.make_branch('b2')
958
 
        self.assertFalse(branch2.repository.has_revision('rev2a'))
959
 
        target_revision = bundle.install_revisions(branch2.repository)
960
 
        self.assertTrue(branch2.repository.has_revision('rev2a'))
961
 
        self.assertEqual('rev2a', target_revision)
962
 
 
963
 
    def test_bundle_empty_property(self):
964
 
        """Test serializing revision properties with an empty value."""
965
 
        tree = self.make_branch_and_memory_tree('tree')
966
 
        tree.lock_write()
967
 
        self.addCleanup(tree.unlock)
968
 
        tree.add([''], ['TREE_ROOT'])
969
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
970
 
        self.b1 = tree.branch
971
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
972
 
        bundle = read_bundle(bundle_sio)
973
 
        revision_info = bundle.revisions[0]
974
 
        self.assertEqual('rev1', revision_info.revision_id)
975
 
        rev = revision_info.as_revision()
976
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
977
 
                         rev.properties)
978
 
 
979
 
    def test_bundle_sorted_properties(self):
980
 
        """For stability the writer should write properties in sorted order."""
981
 
        tree = self.make_branch_and_memory_tree('tree')
982
 
        tree.lock_write()
983
 
        self.addCleanup(tree.unlock)
984
 
 
985
 
        tree.add([''], ['TREE_ROOT'])
986
 
        tree.commit('One', rev_id='rev1',
987
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
988
 
        self.b1 = tree.branch
989
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
990
 
        bundle = read_bundle(bundle_sio)
991
 
        revision_info = bundle.revisions[0]
992
 
        self.assertEqual('rev1', revision_info.revision_id)
993
 
        rev = revision_info.as_revision()
994
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
995
 
                          'd':'1'}, rev.properties)
996
 
 
997
 
    def test_bundle_unicode_properties(self):
998
 
        """We should be able to round trip a non-ascii property."""
999
 
        tree = self.make_branch_and_memory_tree('tree')
1000
 
        tree.lock_write()
1001
 
        self.addCleanup(tree.unlock)
1002
 
 
1003
 
        tree.add([''], ['TREE_ROOT'])
1004
 
        # Revisions themselves do not require anything about revision property
1005
 
        # keys, other than that they are a basestring, and do not contain
1006
 
        # whitespace.
1007
 
        # However, Testaments assert than they are str(), and thus should not
1008
 
        # be Unicode.
1009
 
        tree.commit('One', rev_id='rev1',
1010
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
1011
 
        self.b1 = tree.branch
1012
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1013
 
        bundle = read_bundle(bundle_sio)
1014
 
        revision_info = bundle.revisions[0]
1015
 
        self.assertEqual('rev1', revision_info.revision_id)
1016
 
        rev = revision_info.as_revision()
1017
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1018
 
                          'alpha':u'\u03b1'}, rev.properties)
1019
 
 
1020
 
    def test_bundle_with_ghosts(self):
1021
 
        tree = self.make_branch_and_tree('tree')
1022
 
        self.b1 = tree.branch
1023
 
        self.build_tree_contents([('tree/file', 'content1')])
1024
 
        tree.add(['file'])
1025
 
        tree.commit('rev1')
1026
 
        self.build_tree_contents([('tree/file', 'content2')])
1027
 
        tree.add_parent_tree_id('ghost')
1028
 
        tree.commit('rev2', rev_id='rev2')
1029
 
        bundle = self.get_valid_bundle('null:', 'rev2')
1030
 
 
1031
 
    def make_simple_tree(self, format=None):
1032
 
        tree = self.make_branch_and_tree('b1', format=format)
1033
 
        self.b1 = tree.branch
1034
 
        self.build_tree(['b1/file'])
1035
 
        tree.add('file')
1036
 
        return tree
1037
 
 
1038
 
    def test_across_serializers(self):
1039
 
        tree = self.make_simple_tree('knit')
1040
 
        tree.commit('hello', rev_id='rev1')
1041
 
        tree.commit('hello', rev_id='rev2')
1042
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1043
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1044
 
        bundle.install_revisions(repo)
1045
 
        inv_text = repo._get_inventory_xml('rev2')
1046
 
        self.assertNotContainsRe(inv_text, 'format="5"')
1047
 
        self.assertContainsRe(inv_text, 'format="7"')
1048
 
 
1049
 
    def make_repo_with_installed_revisions(self):
1050
 
        tree = self.make_simple_tree('knit')
1051
 
        tree.commit('hello', rev_id='rev1')
1052
 
        tree.commit('hello', rev_id='rev2')
1053
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1054
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1055
 
        bundle.install_revisions(repo)
1056
 
        return repo
1057
 
 
1058
 
    def test_across_models(self):
1059
 
        repo = self.make_repo_with_installed_revisions()
1060
 
        inv = repo.get_inventory('rev2')
1061
 
        self.assertEqual('rev2', inv.root.revision)
1062
 
        root_id = inv.root.file_id
1063
 
        repo.lock_read()
1064
 
        self.addCleanup(repo.unlock)
1065
 
        self.assertEqual({(root_id, 'rev1'):(),
1066
 
            (root_id, 'rev2'):((root_id, 'rev1'),)},
1067
 
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
1068
 
 
1069
 
    def test_inv_hash_across_serializers(self):
1070
 
        repo = self.make_repo_with_installed_revisions()
1071
 
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
1072
 
        xml = repo._get_inventory_xml('rev2')
1073
 
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1074
 
 
1075
 
    def test_across_models_incompatible(self):
1076
 
        tree = self.make_simple_tree('dirstate-with-subtree')
1077
 
        tree.commit('hello', rev_id='rev1')
1078
 
        tree.commit('hello', rev_id='rev2')
1079
 
        try:
1080
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1081
 
        except errors.IncompatibleBundleFormat:
1082
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1083
 
        repo = self.make_repository('repo', format='knit')
1084
 
        bundle.install_revisions(repo)
1085
 
 
1086
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1087
 
        self.assertRaises(errors.IncompatibleRevision,
1088
 
                          bundle.install_revisions, repo)
1089
 
 
1090
 
    def test_get_merge_request(self):
1091
 
        tree = self.make_simple_tree()
1092
 
        tree.commit('hello', rev_id='rev1')
1093
 
        tree.commit('hello', rev_id='rev2')
1094
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1095
 
        result = bundle.get_merge_request(tree.branch.repository)
1096
 
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
1097
 
 
1098
 
    def test_with_subtree(self):
1099
 
        tree = self.make_branch_and_tree('tree',
1100
 
                                         format='dirstate-with-subtree')
1101
 
        self.b1 = tree.branch
1102
 
        subtree = self.make_branch_and_tree('tree/subtree',
1103
 
                                            format='dirstate-with-subtree')
1104
 
        tree.add('subtree')
1105
 
        tree.commit('hello', rev_id='rev1')
1106
 
        try:
1107
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1108
 
        except errors.IncompatibleBundleFormat:
1109
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1110
 
        if isinstance(bundle, v09.BundleInfo09):
1111
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
1112
 
        repo = self.make_repository('repo', format='knit')
1113
 
        self.assertRaises(errors.IncompatibleRevision,
1114
 
                          bundle.install_revisions, repo)
1115
 
        repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
1116
 
        bundle.install_revisions(repo2)
1117
 
 
1118
 
    def test_revision_id_with_slash(self):
1119
 
        self.tree1 = self.make_branch_and_tree('tree')
1120
 
        self.b1 = self.tree1.branch
1121
 
        try:
1122
 
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1123
 
        except ValueError:
1124
 
            raise tests.TestSkipped(
1125
 
                "Repository doesn't support revision ids with slashes")
1126
 
        bundle = self.get_valid_bundle('null:', 'rev/id')
1127
 
 
1128
 
    def test_skip_file(self):
1129
 
        """Make sure we don't accidentally write to the wrong versionedfile"""
1130
 
        self.tree1 = self.make_branch_and_tree('tree')
1131
 
        self.b1 = self.tree1.branch
1132
 
        # rev1 is not present in bundle, done by fetch
1133
 
        self.build_tree_contents([('tree/file2', 'contents1')])
1134
 
        self.tree1.add('file2', 'file2-id')
1135
 
        self.tree1.commit('rev1', rev_id='reva')
1136
 
        self.build_tree_contents([('tree/file3', 'contents2')])
1137
 
        # rev2 is present in bundle, and done by fetch
1138
 
        # having file1 in the bunle causes file1's versionedfile to be opened.
1139
 
        self.tree1.add('file3', 'file3-id')
1140
 
        self.tree1.commit('rev2')
1141
 
        # Updating file2 should not cause an attempt to add to file1's vf
1142
 
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
1143
 
        self.build_tree_contents([('tree/file2', 'contents3')])
1144
 
        self.tree1.commit('rev3', rev_id='rev3')
1145
 
        bundle = self.get_valid_bundle('reva', 'rev3')
1146
 
        if getattr(bundle, 'get_bundle_reader', None) is None:
1147
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
1148
 
        # be sure that file1 comes before file2
1149
 
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1150
 
            if f == 'file3-id':
1151
 
                break
1152
 
            self.assertNotEqual(f, 'file2-id')
1153
 
        bundle.install_revisions(target.branch.repository)
1154
 
 
1155
 
 
1156
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
1157
 
 
1158
 
    format = '0.8'
1159
 
 
1160
 
    def test_bundle_empty_property(self):
1161
 
        """Test serializing revision properties with an empty value."""
1162
 
        tree = self.make_branch_and_memory_tree('tree')
1163
 
        tree.lock_write()
1164
 
        self.addCleanup(tree.unlock)
1165
 
        tree.add([''], ['TREE_ROOT'])
1166
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1167
 
        self.b1 = tree.branch
1168
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1169
 
        self.assertContainsRe(bundle_sio.getvalue(),
1170
 
                              '# properties:\n'
1171
 
                              '#   branch-nick: tree\n'
1172
 
                              '#   empty: \n'
1173
 
                              '#   one: two\n'
1174
 
                             )
1175
 
        bundle = read_bundle(bundle_sio)
1176
 
        revision_info = bundle.revisions[0]
1177
 
        self.assertEqual('rev1', revision_info.revision_id)
1178
 
        rev = revision_info.as_revision()
1179
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
1180
 
                         rev.properties)
1181
 
 
1182
 
    def get_bundle_tree(self, bundle, revision_id):
1183
 
        repository = self.make_repository('repo')
1184
 
        return bundle.revision_tree(repository, 'revid1')
1185
 
 
1186
 
    def test_bundle_empty_property_alt(self):
1187
 
        """Test serializing revision properties with an empty value.
1188
 
 
1189
 
        Older readers had a bug when reading an empty property.
1190
 
        They assumed that all keys ended in ': \n'. However they would write an
1191
 
        empty value as ':\n'. This tests make sure that all newer bzr versions
1192
 
        can handle th second form.
1193
 
        """
1194
 
        tree = self.make_branch_and_memory_tree('tree')
1195
 
        tree.lock_write()
1196
 
        self.addCleanup(tree.unlock)
1197
 
        tree.add([''], ['TREE_ROOT'])
1198
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1199
 
        self.b1 = tree.branch
1200
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1201
 
        txt = bundle_sio.getvalue()
1202
 
        loc = txt.find('#   empty: ') + len('#   empty:')
1203
 
        # Create a new bundle, which strips the trailing space after empty
1204
 
        bundle_sio = StringIO(txt[:loc] + txt[loc+1:])
1205
 
 
1206
 
        self.assertContainsRe(bundle_sio.getvalue(),
1207
 
                              '# properties:\n'
1208
 
                              '#   branch-nick: tree\n'
1209
 
                              '#   empty:\n'
1210
 
                              '#   one: two\n'
1211
 
                             )
1212
 
        bundle = read_bundle(bundle_sio)
1213
 
        revision_info = bundle.revisions[0]
1214
 
        self.assertEqual('rev1', revision_info.revision_id)
1215
 
        rev = revision_info.as_revision()
1216
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
1217
 
                         rev.properties)
1218
 
 
1219
 
    def test_bundle_sorted_properties(self):
1220
 
        """For stability the writer should write properties in sorted order."""
1221
 
        tree = self.make_branch_and_memory_tree('tree')
1222
 
        tree.lock_write()
1223
 
        self.addCleanup(tree.unlock)
1224
 
 
1225
 
        tree.add([''], ['TREE_ROOT'])
1226
 
        tree.commit('One', rev_id='rev1',
1227
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
1228
 
        self.b1 = tree.branch
1229
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1230
 
        self.assertContainsRe(bundle_sio.getvalue(),
1231
 
                              '# properties:\n'
1232
 
                              '#   a: 4\n'
1233
 
                              '#   b: 3\n'
1234
 
                              '#   branch-nick: tree\n'
1235
 
                              '#   c: 2\n'
1236
 
                              '#   d: 1\n'
1237
 
                             )
1238
 
        bundle = read_bundle(bundle_sio)
1239
 
        revision_info = bundle.revisions[0]
1240
 
        self.assertEqual('rev1', revision_info.revision_id)
1241
 
        rev = revision_info.as_revision()
1242
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
1243
 
                          'd':'1'}, rev.properties)
1244
 
 
1245
 
    def test_bundle_unicode_properties(self):
1246
 
        """We should be able to round trip a non-ascii property."""
1247
 
        tree = self.make_branch_and_memory_tree('tree')
1248
 
        tree.lock_write()
1249
 
        self.addCleanup(tree.unlock)
1250
 
 
1251
 
        tree.add([''], ['TREE_ROOT'])
1252
 
        # Revisions themselves do not require anything about revision property
1253
 
        # keys, other than that they are a basestring, and do not contain
1254
 
        # whitespace.
1255
 
        # However, Testaments assert than they are str(), and thus should not
1256
 
        # be Unicode.
1257
 
        tree.commit('One', rev_id='rev1',
1258
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
1259
 
        self.b1 = tree.branch
1260
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1261
 
        self.assertContainsRe(bundle_sio.getvalue(),
1262
 
                              '# properties:\n'
1263
 
                              '#   alpha: \xce\xb1\n'
1264
 
                              '#   branch-nick: tree\n'
1265
 
                              '#   omega: \xce\xa9\n'
1266
 
                             )
1267
 
        bundle = read_bundle(bundle_sio)
1268
 
        revision_info = bundle.revisions[0]
1269
 
        self.assertEqual('rev1', revision_info.revision_id)
1270
 
        rev = revision_info.as_revision()
1271
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1272
 
                          'alpha':u'\u03b1'}, rev.properties)
1273
 
 
1274
 
 
1275
 
class V09BundleKnit2Tester(V08BundleTester):
1276
 
 
1277
 
    format = '0.9'
1278
 
 
1279
 
    def bzrdir_format(self):
1280
 
        format = bzrdir.BzrDirMetaFormat1()
1281
 
        format.repository_format = knitrepo.RepositoryFormatKnit3()
1282
 
        return format
1283
 
 
1284
 
 
1285
 
class V09BundleKnit1Tester(V08BundleTester):
1286
 
 
1287
 
    format = '0.9'
1288
 
 
1289
 
    def bzrdir_format(self):
1290
 
        format = bzrdir.BzrDirMetaFormat1()
1291
 
        format.repository_format = knitrepo.RepositoryFormatKnit1()
1292
 
        return format
1293
 
 
1294
 
 
1295
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
1296
 
 
1297
 
    format = '4'
1298
 
 
1299
 
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
1300
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1301
 
        Make sure that the text generated is valid, and that it
1302
 
        can be applied against the base, and generate the same information.
1303
 
 
1304
 
        :return: The in-memory bundle
1305
 
        """
1306
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1307
 
 
1308
 
        # This should also validate the generated bundle
1309
 
        bundle = read_bundle(bundle_txt)
1310
 
        repository = self.b1.repository
1311
 
        for bundle_rev in bundle.real_revisions:
1312
 
            # These really should have already been checked when we read the
1313
 
            # bundle, since it computes the sha1 hash for the revision, which
1314
 
            # only will match if everything is okay, but lets be explicit about
1315
 
            # it
1316
 
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1317
 
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1318
 
                      'timestamp', 'timezone', 'message', 'committer',
1319
 
                      'parent_ids', 'properties'):
1320
 
                self.assertEqual(getattr(branch_rev, a),
1321
 
                                 getattr(bundle_rev, a))
1322
 
            self.assertEqual(len(branch_rev.parent_ids),
1323
 
                             len(bundle_rev.parent_ids))
1324
 
        self.assertEqual(set(rev_ids),
1325
 
                         set([r.revision_id for r in bundle.real_revisions]))
1326
 
        self.valid_apply_bundle(base_rev_id, bundle,
1327
 
                                   checkout_dir=checkout_dir)
1328
 
 
1329
 
        return bundle
1330
 
 
1331
 
    def get_invalid_bundle(self, base_rev_id, rev_id):
1332
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1333
 
        Munge the text so that it's invalid.
1334
 
 
1335
 
        :return: The in-memory bundle
1336
 
        """
1337
 
        from bzrlib.bundle import serializer
1338
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1339
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1340
 
        new_text = new_text.replace('<file file_id="exe-1"',
1341
 
                                    '<file executable="y" file_id="exe-1"')
1342
 
        new_text = new_text.replace('B260', 'B275')
1343
 
        bundle_txt = StringIO()
1344
 
        bundle_txt.write(serializer._get_bundle_header('4'))
1345
 
        bundle_txt.write('\n')
1346
 
        bundle_txt.write(new_text.encode('bz2'))
1347
 
        bundle_txt.seek(0)
1348
 
        bundle = read_bundle(bundle_txt)
1349
 
        self.valid_apply_bundle(base_rev_id, bundle)
1350
 
        return bundle
1351
 
 
1352
 
    def create_bundle_text(self, base_rev_id, rev_id):
1353
 
        bundle_txt = StringIO()
1354
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1355
 
                               bundle_txt, format=self.format)
1356
 
        bundle_txt.seek(0)
1357
 
        self.assertEqual(bundle_txt.readline(),
1358
 
                         '# Bazaar revision bundle v%s\n' % self.format)
1359
 
        self.assertEqual(bundle_txt.readline(), '#\n')
1360
 
        rev = self.b1.repository.get_revision(rev_id)
1361
 
        bundle_txt.seek(0)
1362
 
        return bundle_txt, rev_ids
1363
 
 
1364
 
    def get_bundle_tree(self, bundle, revision_id):
1365
 
        repository = self.make_repository('repo')
1366
 
        bundle.install_revisions(repository)
1367
 
        return repository.revision_tree(revision_id)
1368
 
 
1369
 
    def test_creation(self):
1370
 
        tree = self.make_branch_and_tree('tree')
1371
 
        self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
1372
 
        tree.add('file', 'fileid-2')
1373
 
        tree.commit('added file', rev_id='rev1')
1374
 
        self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
1375
 
        tree.commit('changed file', rev_id='rev2')
1376
 
        s = StringIO()
1377
 
        serializer = BundleSerializerV4('1.0')
1378
 
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
1379
 
        s.seek(0)
1380
 
        tree2 = self.make_branch_and_tree('target')
1381
 
        target_repo = tree2.branch.repository
1382
 
        install_bundle(target_repo, serializer.read(s))
1383
 
        target_repo.lock_read()
1384
 
        self.addCleanup(target_repo.unlock)
1385
 
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1386
 
        repo_texts = dict((i, ''.join(content)) for i, content
1387
 
                          in target_repo.iter_files_bytes(
1388
 
                                [('fileid-2', 'rev1', '1'),
1389
 
                                 ('fileid-2', 'rev2', '2')]))
1390
 
        self.assertEqual({'1':'contents1\nstatic\n',
1391
 
                          '2':'contents2\nstatic\n'},
1392
 
                         repo_texts)
1393
 
        rtree = target_repo.revision_tree('rev2')
1394
 
        inventory_vf = target_repo.inventories
1395
 
        # If the inventory store has a graph, it must match the revision graph.
1396
 
        self.assertSubset(
1397
 
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
1398
 
            [None, (('rev1',),)])
1399
 
        self.assertEqual('changed file',
1400
 
                         target_repo.get_revision('rev2').message)
1401
 
 
1402
 
    @staticmethod
1403
 
    def get_raw(bundle_file):
1404
 
        bundle_file.seek(0)
1405
 
        line = bundle_file.readline()
1406
 
        line = bundle_file.readline()
1407
 
        lines = bundle_file.readlines()
1408
 
        return ''.join(lines).decode('bz2')
1409
 
 
1410
 
    def test_copy_signatures(self):
1411
 
        tree_a = self.make_branch_and_tree('tree_a')
1412
 
        import bzrlib.gpg
1413
 
        import bzrlib.commit as commit
1414
 
        oldstrategy = bzrlib.gpg.GPGStrategy
1415
 
        branch = tree_a.branch
1416
 
        repo_a = branch.repository
1417
 
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1418
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
1419
 
        try:
1420
 
            from bzrlib.testament import Testament
1421
 
            # monkey patch gpg signing mechanism
1422
 
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1423
 
            new_config = test_commit.MustSignConfig(branch)
1424
 
            commit.Commit(config=new_config).commit(message="base",
1425
 
                                                    allow_pointless=True,
1426
 
                                                    rev_id='B',
1427
 
                                                    working_tree=tree_a)
1428
 
            def sign(text):
1429
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
1430
 
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
1431
 
        finally:
1432
 
            bzrlib.gpg.GPGStrategy = oldstrategy
1433
 
        tree_b = self.make_branch_and_tree('tree_b')
1434
 
        repo_b = tree_b.branch.repository
1435
 
        s = StringIO()
1436
 
        serializer = BundleSerializerV4('4')
1437
 
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
1438
 
        s.seek(0)
1439
 
        install_bundle(repo_b, serializer.read(s))
1440
 
        self.assertTrue(repo_b.has_signature_for_revision_id('B'))
1441
 
        self.assertEqual(repo_b.get_signature_text('B'),
1442
 
                         repo_a.get_signature_text('B'))
1443
 
        s.seek(0)
1444
 
        # ensure repeat installs are harmless
1445
 
        install_bundle(repo_b, serializer.read(s))
1446
 
 
1447
 
 
1448
 
class V4_2aBundleTester(V4BundleTester):
1449
 
 
1450
 
    def bzrdir_format(self):
1451
 
        return '2a'
1452
 
 
1453
 
    def get_invalid_bundle(self, base_rev_id, rev_id):
1454
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1455
 
        Munge the text so that it's invalid.
1456
 
 
1457
 
        :return: The in-memory bundle
1458
 
        """
1459
 
        from bzrlib.bundle import serializer
1460
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1461
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1462
 
        # We are going to be replacing some text to set the executable bit on a
1463
 
        # file. Make sure the text replacement actually works correctly.
1464
 
        self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
1465
 
        new_text = new_text.replace('<file file_id="exe-1"',
1466
 
                                    '<file executable="y" file_id="exe-1"')
1467
 
        new_text = new_text.replace('B244', 'B259')
1468
 
        bundle_txt = StringIO()
1469
 
        bundle_txt.write(serializer._get_bundle_header('4'))
1470
 
        bundle_txt.write('\n')
1471
 
        bundle_txt.write(new_text.encode('bz2'))
1472
 
        bundle_txt.seek(0)
1473
 
        bundle = read_bundle(bundle_txt)
1474
 
        self.valid_apply_bundle(base_rev_id, bundle)
1475
 
        return bundle
1476
 
 
1477
 
    def make_merged_branch(self):
1478
 
        builder = self.make_branch_builder('source')
1479
 
        builder.start_series()
1480
 
        builder.build_snapshot('a@cset-0-1', None, [
1481
 
            ('add', ('', 'root-id', 'directory', None)),
1482
 
            ('add', ('file', 'file-id', 'file', 'original content\n')),
1483
 
            ])
1484
 
        builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
1485
 
            ('modify', ('file-id', 'new-content\n')),
1486
 
            ])
1487
 
        builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
1488
 
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1489
 
            ])
1490
 
        builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
1491
 
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1492
 
            ])
1493
 
        builder.finish_series()
1494
 
        self.b1 = builder.get_branch()
1495
 
        self.b1.lock_read()
1496
 
        self.addCleanup(self.b1.unlock)
1497
 
 
1498
 
    def make_bundle_just_inventories(self, base_revision_id,
1499
 
                                     target_revision_id,
1500
 
                                     revision_ids):
1501
 
        sio = StringIO()
1502
 
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
1503
 
                                         self.b1.repository, sio)
1504
 
        writer.bundle.begin()
1505
 
        writer._add_inventory_mpdiffs_from_serializer(revision_ids)
1506
 
        writer.bundle.end()
1507
 
        sio.seek(0)
1508
 
        return sio
1509
 
 
1510
 
    def test_single_inventory_multiple_parents_as_xml(self):
1511
 
        self.make_merged_branch()
1512
 
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1513
 
                                                ['a@cset-0-3'])
1514
 
        reader = v4.BundleReader(sio, stream_input=False)
1515
 
        records = list(reader.iter_records())
1516
 
        self.assertEqual(1, len(records))
1517
 
        (bytes, metadata, repo_kind, revision_id,
1518
 
         file_id) = records[0]
1519
 
        self.assertIs(None, file_id)
1520
 
        self.assertEqual('a@cset-0-3', revision_id)
1521
 
        self.assertEqual('inventory', repo_kind)
1522
 
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1523
 
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1524
 
                          'storage_kind': 'mpdiff',
1525
 
                         }, metadata)
1526
 
        # We should have an mpdiff that takes some lines from both parents.
1527
 
        self.assertEqualDiff(
1528
 
            'i 1\n'
1529
 
            '<inventory format="10" revision_id="a@cset-0-3">\n'
1530
 
            '\n'
1531
 
            'c 0 1 1 2\n'
1532
 
            'c 1 3 3 2\n', bytes)
1533
 
 
1534
 
    def test_single_inv_no_parents_as_xml(self):
1535
 
        self.make_merged_branch()
1536
 
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
1537
 
                                                ['a@cset-0-1'])
1538
 
        reader = v4.BundleReader(sio, stream_input=False)
1539
 
        records = list(reader.iter_records())
1540
 
        self.assertEqual(1, len(records))
1541
 
        (bytes, metadata, repo_kind, revision_id,
1542
 
         file_id) = records[0]
1543
 
        self.assertIs(None, file_id)
1544
 
        self.assertEqual('a@cset-0-1', revision_id)
1545
 
        self.assertEqual('inventory', repo_kind)
1546
 
        self.assertEqual({'parents': [],
1547
 
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
1548
 
                          'storage_kind': 'mpdiff',
1549
 
                         }, metadata)
1550
 
        # We should have an mpdiff that takes some lines from both parents.
1551
 
        self.assertEqualDiff(
1552
 
            'i 4\n'
1553
 
            '<inventory format="10" revision_id="a@cset-0-1">\n'
1554
 
            '<directory file_id="root-id" name=""'
1555
 
                ' revision="a@cset-0-1" />\n'
1556
 
            '<file file_id="file-id" name="file" parent_id="root-id"'
1557
 
                ' revision="a@cset-0-1"'
1558
 
                ' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
1559
 
                ' text_size="17" />\n'
1560
 
            '</inventory>\n'
1561
 
            '\n', bytes)
1562
 
 
1563
 
    def test_multiple_inventories_as_xml(self):
1564
 
        self.make_merged_branch()
1565
 
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1566
 
            ['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
1567
 
        reader = v4.BundleReader(sio, stream_input=False)
1568
 
        records = list(reader.iter_records())
1569
 
        self.assertEqual(3, len(records))
1570
 
        revision_ids = [rev_id for b, m, k, rev_id, f in records]
1571
 
        self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
1572
 
                         revision_ids)
1573
 
        metadata_2a = records[0][1]
1574
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1575
 
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
1576
 
                          'storage_kind': 'mpdiff',
1577
 
                         }, metadata_2a)
1578
 
        metadata_2b = records[1][1]
1579
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1580
 
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
1581
 
                          'storage_kind': 'mpdiff',
1582
 
                         }, metadata_2b)
1583
 
        metadata_3 = records[2][1]
1584
 
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1585
 
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1586
 
                          'storage_kind': 'mpdiff',
1587
 
                         }, metadata_3)
1588
 
        bytes_2a = records[0][0]
1589
 
        self.assertEqualDiff(
1590
 
            'i 1\n'
1591
 
            '<inventory format="10" revision_id="a@cset-0-2a">\n'
1592
 
            '\n'
1593
 
            'c 0 1 1 1\n'
1594
 
            'i 1\n'
1595
 
            '<file file_id="file-id" name="file" parent_id="root-id"'
1596
 
                ' revision="a@cset-0-2a"'
1597
 
                ' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
1598
 
                ' text_size="12" />\n'
1599
 
            '\n'
1600
 
            'c 0 3 3 1\n', bytes_2a)
1601
 
        bytes_2b = records[1][0]
1602
 
        self.assertEqualDiff(
1603
 
            'i 1\n'
1604
 
            '<inventory format="10" revision_id="a@cset-0-2b">\n'
1605
 
            '\n'
1606
 
            'c 0 1 1 2\n'
1607
 
            'i 1\n'
1608
 
            '<file file_id="file2-id" name="other-file" parent_id="root-id"'
1609
 
                ' revision="a@cset-0-2b"'
1610
 
                ' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
1611
 
                ' text_size="14" />\n'
1612
 
            '\n'
1613
 
            'c 0 3 4 1\n', bytes_2b)
1614
 
        bytes_3 = records[2][0]
1615
 
        self.assertEqualDiff(
1616
 
            'i 1\n'
1617
 
            '<inventory format="10" revision_id="a@cset-0-3">\n'
1618
 
            '\n'
1619
 
            'c 0 1 1 2\n'
1620
 
            'c 1 3 3 2\n', bytes_3)
1621
 
 
1622
 
    def test_creating_bundle_preserves_chk_pages(self):
1623
 
        self.make_merged_branch()
1624
 
        target = self.b1.bzrdir.sprout('target',
1625
 
                                       revision_id='a@cset-0-2a').open_branch()
1626
 
        bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
1627
 
                                                      'a@cset-0-3')
1628
 
        self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
1629
 
        bundle = read_bundle(bundle_txt)
1630
 
        target.lock_write()
1631
 
        self.addCleanup(target.unlock)
1632
 
        install_bundle(target.repository, bundle)
1633
 
        inv1 = self.b1.repository.inventories.get_record_stream([
1634
 
            ('a@cset-0-3',)], 'unordered',
1635
 
            True).next().get_bytes_as('fulltext')
1636
 
        inv2 = target.repository.inventories.get_record_stream([
1637
 
            ('a@cset-0-3',)], 'unordered',
1638
 
            True).next().get_bytes_as('fulltext')
1639
 
        self.assertEqualDiff(inv1, inv2)
1640
 
 
1641
 
 
1642
 
class MungedBundleTester(object):
 
817
 
 
818
class MungedBundleTester(TestCaseWithTransport):
1643
819
 
1644
820
    def build_test_bundle(self):
1645
821
        wt = self.make_branch_and_tree('b1')
1654
830
 
1655
831
        bundle_txt = StringIO()
1656
832
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
1657
 
                               'a@cset-0-1', bundle_txt, self.format)
1658
 
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
 
833
                               'a@cset-0-1', bundle_txt)
 
834
        self.assertEqual(['a@cset-0-2'], rev_ids)
1659
835
        bundle_txt.seek(0, 0)
1660
836
        return bundle_txt
1661
837
 
1690
866
        bundle = read_bundle(bundle_txt)
1691
867
        self.check_valid(bundle)
1692
868
 
1693
 
 
1694
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
1695
 
 
1696
 
    format = '0.9'
1697
 
 
1698
869
    def test_missing_trailing_whitespace(self):
1699
870
        bundle_txt = self.build_test_bundle()
1700
871
 
1728
899
        bundle = read_bundle(bundle_txt)
1729
900
        self.check_valid(bundle)
1730
901
 
1731
 
 
1732
 
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
1733
 
 
1734
 
    format = '4'
1735
 
 
1736
 
 
1737
 
class TestBundleWriterReader(tests.TestCase):
1738
 
 
1739
 
    def test_roundtrip_record(self):
1740
 
        fileobj = StringIO()
1741
 
        writer = v4.BundleWriter(fileobj)
1742
 
        writer.begin()
1743
 
        writer.add_info_record(foo='bar')
1744
 
        writer._add_record("Record body", {'parents': ['1', '3'],
1745
 
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1746
 
        writer.end()
1747
 
        fileobj.seek(0)
1748
 
        reader = v4.BundleReader(fileobj, stream_input=True)
1749
 
        record_iter = reader.iter_records()
1750
 
        record = record_iter.next()
1751
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1752
 
            'info', None, None), record)
1753
 
        record = record_iter.next()
1754
 
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1755
 
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1756
 
                          record)
1757
 
 
1758
 
    def test_roundtrip_record_memory_hungry(self):
1759
 
        fileobj = StringIO()
1760
 
        writer = v4.BundleWriter(fileobj)
1761
 
        writer.begin()
1762
 
        writer.add_info_record(foo='bar')
1763
 
        writer._add_record("Record body", {'parents': ['1', '3'],
1764
 
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1765
 
        writer.end()
1766
 
        fileobj.seek(0)
1767
 
        reader = v4.BundleReader(fileobj, stream_input=False)
1768
 
        record_iter = reader.iter_records()
1769
 
        record = record_iter.next()
1770
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1771
 
            'info', None, None), record)
1772
 
        record = record_iter.next()
1773
 
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1774
 
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1775
 
                          record)
1776
 
 
1777
 
    def test_encode_name(self):
1778
 
        self.assertEqual('revision/rev1',
1779
 
            v4.BundleWriter.encode_name('revision', 'rev1'))
1780
 
        self.assertEqual('file/rev//1/file-id-1',
1781
 
            v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
1782
 
        self.assertEqual('info',
1783
 
            v4.BundleWriter.encode_name('info', None, None))
1784
 
 
1785
 
    def test_decode_name(self):
1786
 
        self.assertEqual(('revision', 'rev1', None),
1787
 
            v4.BundleReader.decode_name('revision/rev1'))
1788
 
        self.assertEqual(('file', 'rev/1', 'file-id-1'),
1789
 
            v4.BundleReader.decode_name('file/rev//1/file-id-1'))
1790
 
        self.assertEqual(('info', None, None),
1791
 
                         v4.BundleReader.decode_name('info'))
1792
 
 
1793
 
    def test_too_many_names(self):
1794
 
        fileobj = StringIO()
1795
 
        writer = v4.BundleWriter(fileobj)
1796
 
        writer.begin()
1797
 
        writer.add_info_record(foo='bar')
1798
 
        writer._container.add_bytes_record('blah', ['two', 'names'])
1799
 
        writer.end()
1800
 
        fileobj.seek(0)
1801
 
        record_iter = v4.BundleReader(fileobj).iter_records()
1802
 
        record = record_iter.next()
1803
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1804
 
            'info', None, None), record)
1805
 
        self.assertRaises(errors.BadBundle, record_iter.next)
1806
 
 
1807
 
 
1808
 
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
1809
 
 
1810
 
    def test_read_mergeable_skips_local(self):
1811
 
        """A local bundle named like the URL should not be read.
1812
 
        """
1813
 
        out, wt = test_read_bundle.create_bundle_file(self)
1814
 
        class FooService(object):
1815
 
            """A directory service that always returns source"""
1816
 
 
1817
 
            def look_up(self, name, url):
1818
 
                return 'source'
1819
 
        directories.register('foo:', FooService, 'Testing directory service')
1820
 
        self.addCleanup(directories.remove, 'foo:')
1821
 
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1822
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1823
 
                          'foo:bar')
1824
 
 
1825
 
    def test_infinite_redirects_are_not_a_bundle(self):
1826
 
        """If a URL causes TooManyRedirections then NotABundle is raised.
1827
 
        """
1828
 
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
1829
 
        server = RedirectingMemoryServer()
1830
 
        self.start_server(server)
1831
 
        url = server.get_url() + 'infinite-loop'
1832
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
1833
 
 
1834
 
    def test_smart_server_connection_reset(self):
1835
 
        """If a smart server connection fails during the attempt to read a
1836
 
        bundle, then the ConnectionReset error should be propagated.
1837
 
        """
1838
 
        # Instantiate a server that will provoke a ConnectionReset
1839
 
        sock_server = _DisconnectingTCPServer()
1840
 
        self.start_server(sock_server)
1841
 
        # We don't really care what the url is since the server will close the
1842
 
        # connection without interpreting it
1843
 
        url = sock_server.get_url()
1844
 
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1845
 
 
1846
 
 
1847
 
class _DisconnectingTCPServer(object):
1848
 
    """A TCP server that immediately closes any connection made to it."""
1849
 
 
1850
 
    def start_server(self):
1851
 
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1852
 
        self.sock.bind(('127.0.0.1', 0))
1853
 
        self.sock.listen(1)
1854
 
        self.port = self.sock.getsockname()[1]
1855
 
        self.thread = threading.Thread(
1856
 
            name='%s (port %d)' % (self.__class__.__name__, self.port),
1857
 
            target=self.accept_and_close)
1858
 
        self.thread.start()
1859
 
 
1860
 
    def accept_and_close(self):
1861
 
        conn, addr = self.sock.accept()
1862
 
        conn.shutdown(socket.SHUT_RDWR)
1863
 
        conn.close()
1864
 
 
1865
 
    def get_url(self):
1866
 
        return 'bzr://127.0.0.1:%d/' % (self.port,)
1867
 
 
1868
 
    def stop_server(self):
1869
 
        try:
1870
 
            # make sure the thread dies by connecting to the listening socket,
1871
 
            # just in case the test failed to do so.
1872
 
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1873
 
            conn.connect(self.sock.getsockname())
1874
 
            conn.close()
1875
 
        except socket.error:
1876
 
            pass
1877
 
        self.sock.close()
1878
 
        self.thread.join()