~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Vincent Ladeuil
  • Date: 2010-10-26 08:08:23 UTC
  • mfrom: (5514.1.1 665100-content-type)
  • mto: This revision was merged to the branch mainline in revision 5516.
  • Revision ID: v.ladeuil+lp@free.fr-20101026080823-3wggo03b7cpn9908
Correctly set the Content-Type header when POSTing http requests

Show diffs side-by-side

added added

removed removed

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