~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Alexander Belchenko
  • Date: 2006-08-01 18:27:42 UTC
  • mto: (1711.2.111 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 1906.
  • Revision ID: bialix@ukr.net-20060801182742-b28d2edf7eea75d1
English

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
 
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
from bzrlib.merge import Merge3Merger
 
32
from bzrlib.osutils import has_symlinks, sha_file
 
33
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
 
34
                          TestCase, TestSkipped)
48
35
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()
 
36
from bzrlib.workingtree import WorkingTree
65
37
 
66
38
 
67
39
class MockTree(object):
68
 
 
69
40
    def __init__(self):
70
 
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
 
41
        from bzrlib.inventory import RootEntry, ROOT_ID
71
42
        object.__init__(self)
72
43
        self.paths = {ROOT_ID: ""}
73
44
        self.ids = {"": ROOT_ID}
74
45
        self.contents = {}
75
 
        self.root = InventoryDirectory(ROOT_ID, '', None)
 
46
        self.root = RootEntry(ROOT_ID)
76
47
 
77
48
    inventory = property(lambda x:x)
78
49
 
79
 
    def all_file_ids(self):
80
 
        return set(self.paths.keys())
 
50
    def __iter__(self):
 
51
        return self.paths.iterkeys()
81
52
 
82
53
    def __getitem__(self, file_id):
83
54
        if file_id == self.root.file_id:
113
84
            ie = InventoryDirectory(file_id, name, parent_id)
114
85
        elif kind == 'file':
115
86
            ie = InventoryFile(file_id, name, parent_id)
116
 
            ie.text_sha1 = text_sha_1
117
 
            ie.text_size = text_size
118
87
        elif kind == 'symlink':
119
88
            ie = InventoryLink(file_id, name, parent_id)
120
89
        else:
121
 
            raise errors.BzrError('unknown kind %r' % kind)
 
90
            raise BzrError('unknown kind %r' % kind)
 
91
        ie.text_sha1 = text_sha_1
 
92
        ie.text_size = text_size
122
93
        return ie
123
94
 
124
95
    def add_dir(self, file_id, path):
125
96
        self.paths[file_id] = path
126
97
        self.ids[path] = file_id
127
 
 
 
98
    
128
99
    def add_file(self, file_id, path, contents):
129
100
        self.add_dir(file_id, path)
130
101
        self.contents[file_id] = contents
144
115
        result.seek(0,0)
145
116
        return result
146
117
 
147
 
    def get_file_revision(self, file_id):
148
 
        return self.inventory[file_id].revision
149
 
 
150
118
    def contents_stats(self, file_id):
151
119
        if file_id not in self.contents:
152
120
            return None, None
153
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
121
        text_sha1 = sha_file(self.get_file(file_id))
154
122
        return text_sha1, len(self.contents[file_id])
155
123
 
156
124
 
157
 
class BTreeTester(tests.TestCase):
 
125
class BTreeTester(TestCase):
158
126
    """A simple unittest tester for the BundleTree class."""
159
127
 
160
128
    def make_tree_1(self):
164
132
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
165
133
        mtree.add_dir("d", "grandparent/alt_parent")
166
134
        return BundleTree(mtree, ''), mtree
167
 
 
 
135
        
168
136
    def test_renames(self):
169
137
        """Ensure that file renames have the proper effect on children"""
170
138
        btree = self.make_tree_1()[0]
171
139
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
172
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
140
        self.assertEqual(btree.old_path("grandparent/parent"), 
173
141
                         "grandparent/parent")
174
142
        self.assertEqual(btree.old_path("grandparent/parent/file"),
175
143
                         "grandparent/parent/file")
182
150
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
183
151
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
184
152
 
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)
 
153
        assert btree.path2id("grandparent2") is None
 
154
        assert btree.path2id("grandparent2/parent") is None
 
155
        assert btree.path2id("grandparent2/parent/file") is None
188
156
 
189
157
        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)
 
158
        assert btree.old_path("grandparent") is None
 
159
        assert btree.old_path("grandparent/parent") is None
 
160
        assert btree.old_path("grandparent/parent/file") is None
193
161
 
194
162
        self.assertEqual(btree.id2path("a"), "grandparent2")
195
163
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
199
167
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
200
168
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
201
169
 
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)
 
170
        assert btree.path2id("grandparent") is None
 
171
        assert btree.path2id("grandparent/parent") is None
 
172
        assert btree.path2id("grandparent/parent/file") is None
205
173
 
206
174
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
207
175
        self.assertEqual(btree.id2path("a"), "grandparent2")
212
180
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
213
181
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
214
182
 
215
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
216
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
183
        assert btree.path2id("grandparent2/parent") is None
 
184
        assert btree.path2id("grandparent2/parent/file") is None
217
185
 
218
 
        btree.note_rename("grandparent/parent/file",
 
186
        btree.note_rename("grandparent/parent/file", 
219
187
                          "grandparent2/parent2/file2")
220
188
        self.assertEqual(btree.id2path("a"), "grandparent2")
221
189
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
225
193
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
226
194
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
227
195
 
228
 
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
 
196
        assert btree.path2id("grandparent2/parent2/file") is None
229
197
 
230
198
    def test_moves(self):
231
199
        """Ensure that file moves have the proper effect on children"""
232
200
        btree = self.make_tree_1()[0]
233
 
        btree.note_rename("grandparent/parent/file",
 
201
        btree.note_rename("grandparent/parent/file", 
234
202
                          "grandparent/alt_parent/file")
235
203
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
236
204
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
237
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
205
        assert btree.path2id("grandparent/parent/file") is None
238
206
 
239
207
    def unified_diff(self, old, new):
240
208
        out = StringIO()
241
 
        diff.internal_diff("old", old, "new", new, out)
 
209
        internal_diff("old", old, "new", new, out)
242
210
        out.seek(0,0)
243
211
        return out.read()
244
212
 
245
213
    def make_tree_2(self):
246
214
        btree = self.make_tree_1()[0]
247
 
        btree.note_rename("grandparent/parent/file",
 
215
        btree.note_rename("grandparent/parent/file", 
248
216
                          "grandparent/alt_parent/file")
249
 
        self.assertTrue(btree.id2path("e") is None)
250
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
217
        assert btree.id2path("e") is None
 
218
        assert btree.path2id("grandparent/parent/file") is None
251
219
        btree.note_id("e", "grandparent/parent/file")
252
220
        return btree
253
221
 
279
247
    def make_tree_3(self):
280
248
        btree, mtree = self.make_tree_1()
281
249
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
282
 
        btree.note_rename("grandparent/parent/file",
 
250
        btree.note_rename("grandparent/parent/file", 
283
251
                          "grandparent/alt_parent/file")
284
 
        btree.note_rename("grandparent/parent/topping",
 
252
        btree.note_rename("grandparent/parent/topping", 
285
253
                          "grandparent/alt_parent/stopping")
286
254
        return btree
287
255
 
311
279
        btree = self.make_tree_1()[0]
312
280
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
313
281
        btree.note_deletion("grandparent/parent/file")
314
 
        self.assertTrue(btree.id2path("c") is None)
315
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
282
        assert btree.id2path("c") is None
 
283
        assert btree.path2id("grandparent/parent/file") is None
316
284
 
317
285
    def sorted_ids(self, tree):
318
286
        ids = list(tree)
326
294
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
327
295
        btree.note_deletion("grandparent/parent/file")
328
296
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
329
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
297
        btree.note_last_changed("grandparent/alt_parent/fool", 
330
298
                                "revisionidiguess")
331
299
        self.assertEqual(self.sorted_ids(btree),
332
300
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
333
301
 
334
302
 
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)
 
303
class BundleTester(TestCaseWithTransport):
389
304
 
390
305
    def create_bundle_text(self, base_rev_id, rev_id):
391
306
        bundle_txt = StringIO()
392
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
393
 
                               bundle_txt, format=self.format)
 
307
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
308
                               bundle_txt)
394
309
        bundle_txt.seek(0)
395
 
        self.assertEqual(bundle_txt.readline(),
396
 
                         '# Bazaar revision bundle v%s\n' % self.format)
 
310
        self.assertEqual(bundle_txt.readline(), 
 
311
                         '# Bazaar revision bundle v0.8\n')
397
312
        self.assertEqual(bundle_txt.readline(), '#\n')
398
313
 
399
314
        rev = self.b1.repository.get_revision(rev_id)
400
315
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
401
316
                         u'# message:\n')
 
317
 
 
318
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
402
319
        bundle_txt.seek(0)
403
320
        return bundle_txt, rev_ids
404
321
 
406
323
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
407
324
        Make sure that the text generated is valid, and that it
408
325
        can be applied against the base, and generate the same information.
409
 
 
410
 
        :return: The in-memory bundle
 
326
        
 
327
        :return: The in-memory bundle 
411
328
        """
412
329
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
413
330
 
414
 
        # This should also validate the generated bundle
 
331
        # This should also validate the generated bundle 
415
332
        bundle = read_bundle(bundle_txt)
416
333
        repository = self.b1.repository
417
334
        for bundle_rev in bundle.real_revisions:
421
338
            # it
422
339
            branch_rev = repository.get_revision(bundle_rev.revision_id)
423
340
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
424
 
                      'timestamp', 'timezone', 'message', 'committer',
 
341
                      'timestamp', 'timezone', 'message', 'committer', 
425
342
                      'parent_ids', 'properties'):
426
 
                self.assertEqual(getattr(branch_rev, a),
 
343
                self.assertEqual(getattr(branch_rev, a), 
427
344
                                 getattr(bundle_rev, a))
428
 
            self.assertEqual(len(branch_rev.parent_ids),
 
345
            self.assertEqual(len(branch_rev.parent_ids), 
429
346
                             len(bundle_rev.parent_ids))
430
 
        self.assertEqual(rev_ids,
 
347
        self.assertEqual(rev_ids, 
431
348
                         [r.revision_id for r in bundle.real_revisions])
432
349
        self.valid_apply_bundle(base_rev_id, bundle,
433
350
                                   checkout_dir=checkout_dir)
437
354
    def get_invalid_bundle(self, base_rev_id, rev_id):
438
355
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
439
356
        Munge the text so that it's invalid.
440
 
 
 
357
        
441
358
        :return: The in-memory bundle
442
359
        """
443
360
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
444
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
361
        new_text = bundle_txt.getvalue().replace('executable:no', 
445
362
                                               'executable:yes')
446
363
        bundle_txt = StringIO(new_text)
447
364
        bundle = read_bundle(bundle_txt)
448
365
        self.valid_apply_bundle(base_rev_id, bundle)
449
 
        return bundle
 
366
        return bundle 
450
367
 
451
368
    def test_non_bundle(self):
452
 
        self.assertRaises(errors.NotABundle,
453
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
369
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
454
370
 
455
371
    def test_malformed(self):
456
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
372
        self.assertRaises(BadBundle, read_bundle, 
457
373
                          StringIO('# Bazaar revision bundle v'))
458
374
 
459
375
    def test_crlf_bundle(self):
460
376
        try:
461
377
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
462
 
        except errors.BadBundle:
 
378
        except BadBundle:
463
379
            # It is currently permitted for bundles with crlf line endings to
464
380
            # make read_bundle raise a BadBundle, but this should be fixed.
465
381
            # Anything else, especially NotABundle, is an error.
470
386
        """
471
387
 
472
388
        if checkout_dir is None:
473
 
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
 
389
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
474
390
        else:
475
391
            if not os.path.exists(checkout_dir):
476
392
                os.mkdir(checkout_dir)
477
 
        tree = self.make_branch_and_tree(checkout_dir)
 
393
        tree = BzrDir.create_standalone_workingtree(checkout_dir)
478
394
        s = StringIO()
479
 
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
480
 
                                 format=self.format)
 
395
        ancestors = write_bundle(self.b1.repository, rev_id, None, s)
481
396
        s.seek(0)
482
 
        self.assertIsInstance(s.getvalue(), str)
 
397
        assert isinstance(s.getvalue(), str), (
 
398
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
483
399
        install_bundle(tree.branch.repository, read_bundle(s))
484
400
        for ancestor in ancestors:
485
401
            old = self.b1.repository.revision_tree(ancestor)
486
402
            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):
 
403
 
 
404
            # Check that there aren't any inventory level changes
 
405
            delta = new.changes_from(old)
 
406
            self.assertFalse(delta.has_changed(),
 
407
                             'Revision %s not copied correctly.'
 
408
                             % (ancestor,))
 
409
 
 
410
            # Now check that the file contents are all correct
 
411
            for inventory_id in old:
 
412
                try:
 
413
                    old_file = old.get_file(inventory_id)
 
414
                except:
 
415
                    continue
 
416
                if old_file is None:
 
417
                    continue
 
418
                self.assertEqual(old_file.read(),
 
419
                                 new.get_file(inventory_id).read())
 
420
        if rev_id is not None:
510
421
            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])
 
422
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
513
423
            tree.update()
514
424
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
515
425
            self.assertFalse(delta.has_changed(),
516
 
                             'Working tree has modifications: %s' % delta)
 
426
                             'Working tree has modifications')
517
427
        return tree
518
428
 
519
429
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
521
431
        sure everything matches the builtin branch.
522
432
        """
523
433
        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
 
        original_parents = to_tree.get_parent_ids()
532
434
        repository = to_tree.branch.repository
533
 
        original_parents = to_tree.get_parent_ids()
534
435
        self.assertIs(repository.has_revision(base_rev_id), True)
535
436
        for rev in info.real_revisions:
536
437
            self.assert_(not repository.has_revision(rev.revision_id),
537
 
                'Revision {%s} present before applying bundle'
 
438
                'Revision {%s} present before applying bundle' 
538
439
                % rev.revision_id)
539
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
440
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
540
441
 
541
442
        for rev in info.real_revisions:
542
443
            self.assert_(repository.has_revision(rev.revision_id),
543
 
                'Missing revision {%s} after applying bundle'
 
444
                'Missing revision {%s} after applying bundle' 
544
445
                % rev.revision_id)
545
446
 
546
447
        self.assert_(to_tree.branch.repository.has_revision(info.target))
547
448
        # Do we also want to verify that all the texts have been added?
548
449
 
549
 
        self.assertEqual(original_parents + [info.target],
550
 
            to_tree.get_parent_ids())
 
450
        self.assert_(info.target in to_tree.pending_merges())
 
451
 
551
452
 
552
453
        rev = info.real_revisions[-1]
553
454
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
554
455
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
555
 
 
 
456
        
556
457
        # TODO: make sure the target tree is identical to base tree
557
458
        #       we might also check the working tree.
558
459
 
577
478
        self.tree1 = self.make_branch_and_tree('b1')
578
479
        self.b1 = self.tree1.branch
579
480
 
580
 
        self.build_tree_contents([('b1/one', 'one\n')])
581
 
        self.tree1.add('one', 'one-id')
582
 
        self.tree1.set_root_id('root-id')
 
481
        open('b1/one', 'wb').write('one\n')
 
482
        self.tree1.add('one')
583
483
        self.tree1.commit('add one', rev_id='a@cset-0-1')
584
484
 
585
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
 
485
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
 
486
        # FIXME: The current write_bundle api no longer supports
 
487
        #        setting a custom summary message
 
488
        #        We should re-introduce the ability, and update
 
489
        #        the tests to make sure it works.
 
490
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
 
491
        #         message='With a specialized message')
586
492
 
587
493
        # Make sure we can handle files with spaces, tabs, other
588
494
        # bogus characters
596
502
                , 'b1/sub/sub/'
597
503
                , 'b1/sub/sub/nonempty.txt'
598
504
                ])
599
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
600
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
505
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
506
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
601
507
        tt = TreeTransform(self.tree1)
602
508
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
603
509
        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
510
        self.tree1.add([
608
 
                  'dir'
 
511
                'with space.txt'
 
512
                , 'dir'
609
513
                , 'dir/filein subdir.c'
610
514
                , 'dir/WithCaps.txt'
611
515
                , 'dir/ pre space'
619
523
 
620
524
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
621
525
 
622
 
        # Check a rollup bundle
623
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
 
526
        # Check a rollup bundle 
 
527
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
624
528
 
625
529
        # Now delete entries
626
530
        self.tree1.remove(
633
537
        tt.set_executability(False, trans_id)
634
538
        tt.apply()
635
539
        self.tree1.commit('removed', rev_id='a@cset-0-3')
636
 
 
 
540
        
637
541
        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')
 
542
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
 
543
                          'a@cset-0-2', 'a@cset-0-3')
 
544
        # Check a rollup bundle 
 
545
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
644
546
 
645
547
        # Now move the directory
646
548
        self.tree1.rename_one('dir', 'sub/dir')
647
549
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
648
550
 
649
551
        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')
 
552
        # Check a rollup bundle 
 
553
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
652
554
 
653
555
        # Modified files
654
556
        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')
 
557
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
657
558
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
658
 
        self.tree1.rename_one('sub/dir/ pre space',
 
559
        self.tree1.rename_one('sub/dir/ pre space', 
659
560
                              'sub/ start space')
660
561
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
661
562
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
667
568
                          verbose=False)
668
569
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
669
570
        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
571
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
676
572
        other.commit('rename file', rev_id='a@cset-0-6b')
677
 
        self.tree1.merge_from_branch(other.branch)
 
573
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
678
574
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
679
575
                          verbose=False)
680
576
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
681
577
 
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')
 
578
    def test_symlink_bundle(self):
 
579
        if not has_symlinks():
 
580
            raise TestSkipped("No symlink support")
 
581
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
687
582
        self.b1 = self.tree1.branch
688
 
 
689
583
        tt = TreeTransform(self.tree1)
690
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
584
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
691
585
        tt.apply()
692
586
        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
 
 
 
587
        self.get_valid_bundle(None, 'l@cset-0-1')
699
588
        tt = TreeTransform(self.tree1)
700
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
589
        trans_id = tt.trans_id_tree_file_id('link-1')
701
590
        tt.adjust_path('link2', tt.root, trans_id)
702
591
        tt.delete_contents(trans_id)
703
 
        tt.create_symlink(new_link_target, trans_id)
 
592
        tt.create_symlink('mars', trans_id)
704
593
        tt.apply()
705
594
        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
 
 
 
595
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
713
596
        tt = TreeTransform(self.tree1)
714
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
597
        trans_id = tt.trans_id_tree_file_id('link-1')
715
598
        tt.delete_contents(trans_id)
716
599
        tt.create_symlink('jupiter', trans_id)
717
600
        tt.apply()
718
601
        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
 
 
 
602
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
721
603
        tt = TreeTransform(self.tree1)
722
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
604
        trans_id = tt.trans_id_tree_file_id('link-1')
723
605
        tt.delete_contents(trans_id)
724
606
        tt.apply()
725
607
        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}')
 
608
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
736
609
 
737
610
    def test_binary_bundle(self):
738
 
        self.tree1 = self.make_branch_and_tree('b1')
 
611
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
739
612
        self.b1 = self.tree1.branch
740
613
        tt = TreeTransform(self.tree1)
741
 
 
 
614
        
742
615
        # Add
743
616
        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')
 
617
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
746
618
        tt.apply()
747
619
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
748
 
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
620
        self.get_valid_bundle(None, 'b@cset-0-1')
749
621
 
750
622
        # Delete
751
623
        tt = TreeTransform(self.tree1)
775
647
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
776
648
 
777
649
        # Rollup
778
 
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
650
        self.get_valid_bundle(None, 'b@cset-0-4')
779
651
 
780
652
    def test_last_modified(self):
781
 
        self.tree1 = self.make_branch_and_tree('b1')
 
653
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
782
654
        self.b1 = self.tree1.branch
783
655
        tt = TreeTransform(self.tree1)
784
656
        tt.new_file('file', tt.root, 'file', 'file')
799
671
        tt.create_file('file2', trans_id)
800
672
        tt.apply()
801
673
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
802
 
        self.tree1.merge_from_branch(other.branch)
 
674
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
803
675
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
804
676
                          verbose=False)
805
677
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
806
678
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
807
679
 
808
680
    def test_hide_history(self):
809
 
        self.tree1 = self.make_branch_and_tree('b1')
 
681
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
810
682
        self.b1 = self.tree1.branch
811
683
 
812
684
        open('b1/one', 'wb').write('one\n')
818
690
        self.tree1.commit('modify', rev_id='a@cset-0-3')
819
691
        bundle_file = StringIO()
820
692
        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
693
                               'a@cset-0-1', bundle_file)
833
 
 
834
 
    @staticmethod
835
 
    def get_raw(bundle_file):
836
 
        return bundle_file.getvalue()
 
694
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
 
695
        self.assertContainsRe(bundle_file.getvalue(), 'one')
 
696
        self.assertContainsRe(bundle_file.getvalue(), 'three')
837
697
 
838
698
    def test_unicode_bundle(self):
839
 
        self.requireFeature(tests.UnicodeFilenameFeature)
840
699
        # Handle international characters
841
700
        os.mkdir('b1')
842
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
701
        try:
 
702
            f = open(u'b1/with Dod\xe9', 'wb')
 
703
        except UnicodeEncodeError:
 
704
            raise TestSkipped("Filesystem doesn't support unicode")
843
705
 
844
706
        self.tree1 = self.make_branch_and_tree('b1')
845
707
        self.b1 = self.tree1.branch
849
711
            u'William Dod\xe9\n').encode('utf-8'))
850
712
        f.close()
851
713
 
852
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
853
 
        self.tree1.commit(u'i18n commit from William Dod\xe9',
 
714
        self.tree1.add([u'with Dod\xe9'])
 
715
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
854
716
                          rev_id='i18n-1', committer=u'William Dod\xe9')
855
717
 
856
718
        # Add
857
 
        bundle = self.get_valid_bundle('null:', 'i18n-1')
 
719
        bundle = self.get_valid_bundle(None, 'i18n-1')
858
720
 
859
721
        # Modified
860
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
722
        f = open(u'b1/with Dod\xe9', 'wb')
861
723
        f.write(u'Modified \xb5\n'.encode('utf8'))
862
724
        f.close()
863
725
        self.tree1.commit(u'modified', rev_id='i18n-2')
864
726
 
865
727
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
866
 
 
 
728
        
867
729
        # Renamed
868
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
730
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
869
731
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
870
732
                          committer=u'Erik B\xe5gfors')
871
733
 
872
734
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
873
735
 
874
736
        # Removed
875
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
737
        self.tree1.remove([u'B\xe5gfors'])
876
738
        self.tree1.commit(u'removed', rev_id='i18n-4')
877
739
 
878
740
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
879
741
 
880
742
        # Rollup
881
 
        bundle = self.get_valid_bundle('null:', 'i18n-4')
 
743
        bundle = self.get_valid_bundle(None, 'i18n-4')
882
744
 
883
745
 
884
746
    def test_whitespace_bundle(self):
885
747
        if sys.platform in ('win32', 'cygwin'):
886
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
887
 
                                    ' with tabs or trailing spaces')
 
748
            raise TestSkipped('Windows doesn\'t support filenames'
 
749
                              ' with tabs or trailing spaces')
888
750
        self.tree1 = self.make_branch_and_tree('b1')
889
751
        self.b1 = self.tree1.branch
890
752
 
896
758
        # Added
897
759
        self.tree1.commit('funky whitespace', rev_id='white-1')
898
760
 
899
 
        bundle = self.get_valid_bundle('null:', 'white-1')
 
761
        bundle = self.get_valid_bundle(None, 'white-1')
900
762
 
901
763
        # Modified
902
764
        open('b1/trailing space ', 'ab').write('add some text\n')
915
777
        self.tree1.commit('removed', rev_id='white-4')
916
778
 
917
779
        bundle = self.get_valid_bundle('white-3', 'white-4')
918
 
 
 
780
        
919
781
        # Now test a complet roll-up
920
 
        bundle = self.get_valid_bundle('null:', 'white-4')
 
782
        bundle = self.get_valid_bundle(None, 'white-4')
921
783
 
922
784
    def test_alt_timezone_bundle(self):
923
 
        self.tree1 = self.make_branch_and_memory_tree('b1')
 
785
        self.tree1 = self.make_branch_and_tree('b1')
924
786
        self.b1 = self.tree1.branch
925
 
        builder = treebuilder.TreeBuilder()
926
787
 
927
 
        self.tree1.lock_write()
928
 
        builder.start_tree(self.tree1)
929
 
        builder.build(['newfile'])
930
 
        builder.finish_tree()
 
788
        self.build_tree(['b1/newfile'])
 
789
        self.tree1.add(['newfile'])
931
790
 
932
791
        # Asia/Colombo offset = 5 hours 30 minutes
933
792
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
934
793
                          timezone=19800, timestamp=1152544886.0)
935
794
 
936
 
        bundle = self.get_valid_bundle('null:', 'tz-1')
937
 
 
 
795
        bundle = self.get_valid_bundle(None, 'tz-1')
 
796
        
938
797
        rev = bundle.revisions[0]
939
798
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
940
799
        self.assertEqual(19800, rev.timezone)
941
800
        self.assertEqual(1152544886.0, rev.timestamp)
942
 
        self.tree1.unlock()
943
 
 
944
 
    def test_bundle_root_id(self):
945
 
        self.tree1 = self.make_branch_and_tree('b1')
946
 
        self.b1 = self.tree1.branch
947
 
        self.tree1.commit('message', rev_id='revid1')
948
 
        bundle = self.get_valid_bundle('null:', 'revid1')
949
 
        tree = self.get_bundle_tree(bundle, 'revid1')
950
 
        self.assertEqual('revid1', tree.inventory.root.revision)
951
 
 
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):
 
801
 
 
802
 
 
803
class MungedBundleTester(TestCaseWithTransport):
1643
804
 
1644
805
    def build_test_bundle(self):
1645
806
        wt = self.make_branch_and_tree('b1')
1654
815
 
1655
816
        bundle_txt = StringIO()
1656
817
        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))
 
818
                               'a@cset-0-1', bundle_txt)
 
819
        self.assertEqual(['a@cset-0-2'], rev_ids)
1659
820
        bundle_txt.seek(0, 0)
1660
821
        return bundle_txt
1661
822
 
1690
851
        bundle = read_bundle(bundle_txt)
1691
852
        self.check_valid(bundle)
1692
853
 
1693
 
 
1694
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
1695
 
 
1696
 
    format = '0.9'
1697
 
 
1698
854
    def test_missing_trailing_whitespace(self):
1699
855
        bundle_txt = self.build_test_bundle()
1700
856
 
1728
884
        bundle = read_bundle(bundle_txt)
1729
885
        self.check_valid(bundle)
1730
886
 
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()