~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Martin Packman
  • Date: 2011-12-23 19:38:22 UTC
  • mto: This revision was merged to the branch mainline in revision 6405.
  • Revision ID: martin.packman@canonical.com-20111223193822-hesheea4o8aqwexv
Accept and document passing the medium rather than transport for smart connections

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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., 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
 
20
import SocketServer
19
21
import sys
20
 
import tempfile
 
22
import threading
21
23
 
22
24
from bzrlib import (
23
25
    bzrdir,
 
26
    diff,
24
27
    errors,
25
28
    inventory,
26
 
    repository,
 
29
    merge,
 
30
    osutils,
 
31
    revision as _mod_revision,
 
32
    tests,
27
33
    treebuilder,
28
34
    )
29
 
from bzrlib.builtins import _merge_helper
30
 
from bzrlib.bzrdir import BzrDir
 
35
from bzrlib.bundle import read_mergeable_from_url
31
36
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
32
37
from bzrlib.bundle.bundle_data import BundleTree
33
 
from bzrlib.bundle.serializer import write_bundle, read_bundle
 
38
from bzrlib.directory_service import directories
 
39
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
34
40
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
35
41
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
36
 
from bzrlib.branch import Branch
37
 
from bzrlib.diff import internal_diff
38
 
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
39
 
                           NoSuchFile,)
40
 
from bzrlib.merge import Merge3Merger
 
42
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
41
43
from bzrlib.repofmt import knitrepo
42
 
from bzrlib.osutils import has_symlinks, sha_file
43
 
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
44
 
                          TestCase, TestSkipped)
 
44
from bzrlib.tests import (
 
45
    features,
 
46
    test_commit,
 
47
    test_read_bundle,
 
48
    test_server,
 
49
    )
45
50
from bzrlib.transform import TreeTransform
46
 
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()
47
67
 
48
68
 
49
69
class MockTree(object):
 
70
 
50
71
    def __init__(self):
51
72
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
52
73
        object.__init__(self)
57
78
 
58
79
    inventory = property(lambda x:x)
59
80
 
60
 
    def __iter__(self):
61
 
        return self.paths.iterkeys()
 
81
    def all_file_ids(self):
 
82
        return set(self.paths.keys())
62
83
 
63
84
    def __getitem__(self, file_id):
64
85
        if file_id == self.root.file_id:
94
115
            ie = InventoryDirectory(file_id, name, parent_id)
95
116
        elif kind == 'file':
96
117
            ie = InventoryFile(file_id, name, parent_id)
 
118
            ie.text_sha1 = text_sha_1
 
119
            ie.text_size = text_size
97
120
        elif kind == 'symlink':
98
121
            ie = InventoryLink(file_id, name, parent_id)
99
122
        else:
100
 
            raise BzrError('unknown kind %r' % kind)
101
 
        ie.text_sha1 = text_sha_1
102
 
        ie.text_size = text_size
 
123
            raise errors.BzrError('unknown kind %r' % kind)
103
124
        return ie
104
125
 
105
126
    def add_dir(self, file_id, path):
106
127
        self.paths[file_id] = path
107
128
        self.ids[path] = file_id
108
 
    
 
129
 
109
130
    def add_file(self, file_id, path, contents):
110
131
        self.add_dir(file_id, path)
111
132
        self.contents[file_id] = contents
125
146
        result.seek(0,0)
126
147
        return result
127
148
 
 
149
    def get_file_revision(self, file_id):
 
150
        return self.inventory[file_id].revision
 
151
 
128
152
    def contents_stats(self, file_id):
129
153
        if file_id not in self.contents:
130
154
            return None, None
131
 
        text_sha1 = sha_file(self.get_file(file_id))
 
155
        text_sha1 = osutils.sha_file(self.get_file(file_id))
132
156
        return text_sha1, len(self.contents[file_id])
133
157
 
134
158
 
135
 
class BTreeTester(TestCase):
 
159
class BTreeTester(tests.TestCase):
136
160
    """A simple unittest tester for the BundleTree class."""
137
161
 
138
162
    def make_tree_1(self):
142
166
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
143
167
        mtree.add_dir("d", "grandparent/alt_parent")
144
168
        return BundleTree(mtree, ''), mtree
145
 
        
 
169
 
146
170
    def test_renames(self):
147
171
        """Ensure that file renames have the proper effect on children"""
148
172
        btree = self.make_tree_1()[0]
149
173
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
150
 
        self.assertEqual(btree.old_path("grandparent/parent"), 
 
174
        self.assertEqual(btree.old_path("grandparent/parent"),
151
175
                         "grandparent/parent")
152
176
        self.assertEqual(btree.old_path("grandparent/parent/file"),
153
177
                         "grandparent/parent/file")
160
184
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
161
185
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
162
186
 
163
 
        assert btree.path2id("grandparent2") is None
164
 
        assert btree.path2id("grandparent2/parent") is None
165
 
        assert btree.path2id("grandparent2/parent/file") is None
 
187
        self.assertTrue(btree.path2id("grandparent2") is None)
 
188
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
189
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
166
190
 
167
191
        btree.note_rename("grandparent", "grandparent2")
168
 
        assert btree.old_path("grandparent") is None
169
 
        assert btree.old_path("grandparent/parent") is None
170
 
        assert btree.old_path("grandparent/parent/file") is None
 
192
        self.assertTrue(btree.old_path("grandparent") is None)
 
193
        self.assertTrue(btree.old_path("grandparent/parent") is None)
 
194
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
171
195
 
172
196
        self.assertEqual(btree.id2path("a"), "grandparent2")
173
197
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
177
201
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
178
202
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
179
203
 
180
 
        assert btree.path2id("grandparent") is None
181
 
        assert btree.path2id("grandparent/parent") is None
182
 
        assert btree.path2id("grandparent/parent/file") is None
 
204
        self.assertTrue(btree.path2id("grandparent") is None)
 
205
        self.assertTrue(btree.path2id("grandparent/parent") is None)
 
206
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
183
207
 
184
208
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
185
209
        self.assertEqual(btree.id2path("a"), "grandparent2")
190
214
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
191
215
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
192
216
 
193
 
        assert btree.path2id("grandparent2/parent") is None
194
 
        assert btree.path2id("grandparent2/parent/file") is None
 
217
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
218
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
195
219
 
196
 
        btree.note_rename("grandparent/parent/file", 
 
220
        btree.note_rename("grandparent/parent/file",
197
221
                          "grandparent2/parent2/file2")
198
222
        self.assertEqual(btree.id2path("a"), "grandparent2")
199
223
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
203
227
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
204
228
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
205
229
 
206
 
        assert btree.path2id("grandparent2/parent2/file") is None
 
230
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
207
231
 
208
232
    def test_moves(self):
209
233
        """Ensure that file moves have the proper effect on children"""
210
234
        btree = self.make_tree_1()[0]
211
 
        btree.note_rename("grandparent/parent/file", 
 
235
        btree.note_rename("grandparent/parent/file",
212
236
                          "grandparent/alt_parent/file")
213
237
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
214
238
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
215
 
        assert btree.path2id("grandparent/parent/file") is None
 
239
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
216
240
 
217
241
    def unified_diff(self, old, new):
218
242
        out = StringIO()
219
 
        internal_diff("old", old, "new", new, out)
 
243
        diff.internal_diff("old", old, "new", new, out)
220
244
        out.seek(0,0)
221
245
        return out.read()
222
246
 
223
247
    def make_tree_2(self):
224
248
        btree = self.make_tree_1()[0]
225
 
        btree.note_rename("grandparent/parent/file", 
 
249
        btree.note_rename("grandparent/parent/file",
226
250
                          "grandparent/alt_parent/file")
227
 
        assert btree.id2path("e") is None
228
 
        assert btree.path2id("grandparent/parent/file") is None
 
251
        self.assertTrue(btree.id2path("e") is None)
 
252
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
229
253
        btree.note_id("e", "grandparent/parent/file")
230
254
        return btree
231
255
 
257
281
    def make_tree_3(self):
258
282
        btree, mtree = self.make_tree_1()
259
283
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
260
 
        btree.note_rename("grandparent/parent/file", 
 
284
        btree.note_rename("grandparent/parent/file",
261
285
                          "grandparent/alt_parent/file")
262
 
        btree.note_rename("grandparent/parent/topping", 
 
286
        btree.note_rename("grandparent/parent/topping",
263
287
                          "grandparent/alt_parent/stopping")
264
288
        return btree
265
289
 
289
313
        btree = self.make_tree_1()[0]
290
314
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
291
315
        btree.note_deletion("grandparent/parent/file")
292
 
        assert btree.id2path("c") is None
293
 
        assert btree.path2id("grandparent/parent/file") is None
 
316
        self.assertTrue(btree.id2path("c") is None)
 
317
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
294
318
 
295
319
    def sorted_ids(self, tree):
296
320
        ids = list(tree)
304
328
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
305
329
        btree.note_deletion("grandparent/parent/file")
306
330
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
307
 
        btree.note_last_changed("grandparent/alt_parent/fool", 
 
331
        btree.note_last_changed("grandparent/alt_parent/fool",
308
332
                                "revisionidiguess")
309
333
        self.assertEqual(self.sorted_ids(btree),
310
334
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
311
335
 
312
336
 
313
 
class BundleTester1(TestCaseWithTransport):
 
337
class BundleTester1(tests.TestCaseWithTransport):
314
338
 
315
339
    def test_mismatched_bundle(self):
316
340
        format = bzrdir.BzrDirMetaFormat1()
317
341
        format.repository_format = knitrepo.RepositoryFormatKnit3()
318
342
        serializer = BundleSerializerV08('0.8')
319
343
        b = self.make_branch('.', format=format)
320
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
 
344
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
321
345
                          b.repository, [], {}, StringIO())
322
346
 
323
347
    def test_matched_bundle(self):
336
360
        source.commit('one', rev_id='one-id')
337
361
        source.commit('two', rev_id='two-id')
338
362
        text = StringIO()
339
 
        write_bundle(source.branch.repository, 'two-id', None, text, 
 
363
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
340
364
                     format='0.9')
341
365
        text.seek(0)
342
366
 
343
367
        format = bzrdir.BzrDirMetaFormat1()
344
368
        format.repository_format = knitrepo.RepositoryFormatKnit1()
345
369
        target = self.make_branch('target', format=format)
346
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
370
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
347
371
                          target.repository, read_bundle(text))
348
372
 
349
373
 
350
 
class V08BundleTester(TestCaseWithTransport):
351
 
 
352
 
    format = '0.8'
 
374
class BundleTester(object):
353
375
 
354
376
    def bzrdir_format(self):
355
377
        format = bzrdir.BzrDirMetaFormat1()
359
381
    def make_branch_and_tree(self, path, format=None):
360
382
        if format is None:
361
383
            format = self.bzrdir_format()
362
 
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
384
        return tests.TestCaseWithTransport.make_branch_and_tree(
 
385
            self, path, format)
363
386
 
364
387
    def make_branch(self, path, format=None):
365
388
        if format is None:
366
389
            format = self.bzrdir_format()
367
 
        return TestCaseWithTransport.make_branch(self, path, format)
 
390
        return tests.TestCaseWithTransport.make_branch(self, path, format)
368
391
 
369
392
    def create_bundle_text(self, base_rev_id, rev_id):
370
393
        bundle_txt = StringIO()
371
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
394
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
372
395
                               bundle_txt, format=self.format)
373
396
        bundle_txt.seek(0)
374
 
        self.assertEqual(bundle_txt.readline(), 
 
397
        self.assertEqual(bundle_txt.readline(),
375
398
                         '# Bazaar revision bundle v%s\n' % self.format)
376
399
        self.assertEqual(bundle_txt.readline(), '#\n')
377
400
 
378
401
        rev = self.b1.repository.get_revision(rev_id)
379
402
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
380
403
                         u'# message:\n')
381
 
 
382
 
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
383
404
        bundle_txt.seek(0)
384
405
        return bundle_txt, rev_ids
385
406
 
387
408
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
388
409
        Make sure that the text generated is valid, and that it
389
410
        can be applied against the base, and generate the same information.
390
 
        
391
 
        :return: The in-memory bundle 
 
411
 
 
412
        :return: The in-memory bundle
392
413
        """
393
414
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
394
415
 
395
 
        # This should also validate the generated bundle 
 
416
        # This should also validate the generated bundle
396
417
        bundle = read_bundle(bundle_txt)
397
418
        repository = self.b1.repository
398
419
        for bundle_rev in bundle.real_revisions:
402
423
            # it
403
424
            branch_rev = repository.get_revision(bundle_rev.revision_id)
404
425
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
405
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
426
                      'timestamp', 'timezone', 'message', 'committer',
406
427
                      'parent_ids', 'properties'):
407
 
                self.assertEqual(getattr(branch_rev, a), 
 
428
                self.assertEqual(getattr(branch_rev, a),
408
429
                                 getattr(bundle_rev, a))
409
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
430
            self.assertEqual(len(branch_rev.parent_ids),
410
431
                             len(bundle_rev.parent_ids))
411
 
        self.assertEqual(rev_ids, 
 
432
        self.assertEqual(rev_ids,
412
433
                         [r.revision_id for r in bundle.real_revisions])
413
434
        self.valid_apply_bundle(base_rev_id, bundle,
414
435
                                   checkout_dir=checkout_dir)
418
439
    def get_invalid_bundle(self, base_rev_id, rev_id):
419
440
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
420
441
        Munge the text so that it's invalid.
421
 
        
 
442
 
422
443
        :return: The in-memory bundle
423
444
        """
424
445
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
425
 
        new_text = bundle_txt.getvalue().replace('executable:no', 
 
446
        new_text = bundle_txt.getvalue().replace('executable:no',
426
447
                                               'executable:yes')
427
448
        bundle_txt = StringIO(new_text)
428
449
        bundle = read_bundle(bundle_txt)
429
450
        self.valid_apply_bundle(base_rev_id, bundle)
430
 
        return bundle 
 
451
        return bundle
431
452
 
432
453
    def test_non_bundle(self):
433
 
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
 
454
        self.assertRaises(errors.NotABundle,
 
455
                          read_bundle, StringIO('#!/bin/sh\n'))
434
456
 
435
457
    def test_malformed(self):
436
 
        self.assertRaises(BadBundle, read_bundle, 
 
458
        self.assertRaises(errors.BadBundle, read_bundle,
437
459
                          StringIO('# Bazaar revision bundle v'))
438
460
 
439
461
    def test_crlf_bundle(self):
440
462
        try:
441
463
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
442
 
        except BadBundle:
 
464
        except errors.BadBundle:
443
465
            # It is currently permitted for bundles with crlf line endings to
444
466
            # make read_bundle raise a BadBundle, but this should be fixed.
445
467
            # Anything else, especially NotABundle, is an error.
450
472
        """
451
473
 
452
474
        if checkout_dir is None:
453
 
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
 
475
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
454
476
        else:
455
477
            if not os.path.exists(checkout_dir):
456
478
                os.mkdir(checkout_dir)
457
479
        tree = self.make_branch_and_tree(checkout_dir)
458
480
        s = StringIO()
459
 
        ancestors = write_bundle(self.b1.repository, rev_id, None, s,
 
481
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
460
482
                                 format=self.format)
461
483
        s.seek(0)
462
 
        assert isinstance(s.getvalue(), str), (
463
 
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
 
484
        self.assertIsInstance(s.getvalue(), str)
464
485
        install_bundle(tree.branch.repository, read_bundle(s))
465
486
        for ancestor in ancestors:
466
487
            old = self.b1.repository.revision_tree(ancestor)
467
488
            new = tree.branch.repository.revision_tree(ancestor)
468
 
 
469
 
            # Check that there aren't any inventory level changes
470
 
            delta = new.changes_from(old)
471
 
            self.assertFalse(delta.has_changed(),
472
 
                             'Revision %s not copied correctly.'
473
 
                             % (ancestor,))
474
 
 
475
 
            # Now check that the file contents are all correct
476
 
            for inventory_id in old:
477
 
                try:
478
 
                    old_file = old.get_file(inventory_id)
479
 
                except NoSuchFile:
480
 
                    continue
481
 
                if old_file is None:
482
 
                    continue
483
 
                self.assertEqual(old_file.read(),
484
 
                                 new.get_file(inventory_id).read())
485
 
        if rev_id is not None:
486
 
            rh = self.b1.revision_history()
487
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
 
489
            old.lock_read()
 
490
            new.lock_read()
 
491
            try:
 
492
                # Check that there aren't any inventory level changes
 
493
                delta = new.changes_from(old)
 
494
                self.assertFalse(delta.has_changed(),
 
495
                                 'Revision %s not copied correctly.'
 
496
                                 % (ancestor,))
 
497
 
 
498
                # Now check that the file contents are all correct
 
499
                for inventory_id in old.all_file_ids():
 
500
                    try:
 
501
                        old_file = old.get_file(inventory_id)
 
502
                    except errors.NoSuchFile:
 
503
                        continue
 
504
                    if old_file is None:
 
505
                        continue
 
506
                    self.assertEqual(old_file.read(),
 
507
                                     new.get_file(inventory_id).read())
 
508
            finally:
 
509
                new.unlock()
 
510
                old.unlock()
 
511
        if not _mod_revision.is_null(rev_id):
 
512
            tree.branch.generate_revision_history(rev_id)
488
513
            tree.update()
489
514
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
490
515
            self.assertFalse(delta.has_changed(),
496
521
        sure everything matches the builtin branch.
497
522
        """
498
523
        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):
499
531
        original_parents = to_tree.get_parent_ids()
500
532
        repository = to_tree.branch.repository
501
533
        original_parents = to_tree.get_parent_ids()
502
534
        self.assertIs(repository.has_revision(base_rev_id), True)
503
535
        for rev in info.real_revisions:
504
536
            self.assert_(not repository.has_revision(rev.revision_id),
505
 
                'Revision {%s} present before applying bundle' 
 
537
                'Revision {%s} present before applying bundle'
506
538
                % rev.revision_id)
507
 
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
 
539
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
508
540
 
509
541
        for rev in info.real_revisions:
510
542
            self.assert_(repository.has_revision(rev.revision_id),
511
 
                'Missing revision {%s} after applying bundle' 
 
543
                'Missing revision {%s} after applying bundle'
512
544
                % rev.revision_id)
513
545
 
514
546
        self.assert_(to_tree.branch.repository.has_revision(info.target))
520
552
        rev = info.real_revisions[-1]
521
553
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
522
554
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
523
 
        
 
555
 
524
556
        # TODO: make sure the target tree is identical to base tree
525
557
        #       we might also check the working tree.
526
558
 
545
577
        self.tree1 = self.make_branch_and_tree('b1')
546
578
        self.b1 = self.tree1.branch
547
579
 
548
 
        open('b1/one', 'wb').write('one\n')
549
 
        self.tree1.add('one')
 
580
        self.build_tree_contents([('b1/one', 'one\n')])
 
581
        self.tree1.add('one', 'one-id')
 
582
        self.tree1.set_root_id('root-id')
550
583
        self.tree1.commit('add one', rev_id='a@cset-0-1')
551
584
 
552
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
553
 
        # FIXME: The current write_bundle api no longer supports
554
 
        #        setting a custom summary message
555
 
        #        We should re-introduce the ability, and update
556
 
        #        the tests to make sure it works.
557
 
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
558
 
        #         message='With a specialized message')
 
585
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
559
586
 
560
587
        # Make sure we can handle files with spaces, tabs, other
561
588
        # bogus characters
569
596
                , 'b1/sub/sub/'
570
597
                , 'b1/sub/sub/nonempty.txt'
571
598
                ])
572
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
573
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
 
599
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
 
600
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
574
601
        tt = TreeTransform(self.tree1)
575
602
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
576
603
        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')
577
607
        self.tree1.add([
578
 
                'with space.txt'
579
 
                , 'dir'
 
608
                  'dir'
580
609
                , 'dir/filein subdir.c'
581
610
                , 'dir/WithCaps.txt'
582
611
                , 'dir/ pre space'
590
619
 
591
620
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
592
621
 
593
 
        # Check a rollup bundle 
594
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
 
622
        # Check a rollup bundle
 
623
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
595
624
 
596
625
        # Now delete entries
597
626
        self.tree1.remove(
604
633
        tt.set_executability(False, trans_id)
605
634
        tt.apply()
606
635
        self.tree1.commit('removed', rev_id='a@cset-0-3')
607
 
        
 
636
 
608
637
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
609
 
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
610
 
                          'a@cset-0-2', 'a@cset-0-3')
611
 
        # Check a rollup bundle 
612
 
        bundle = self.get_valid_bundle(None, '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')
613
644
 
614
645
        # Now move the directory
615
646
        self.tree1.rename_one('dir', 'sub/dir')
616
647
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
617
648
 
618
649
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
619
 
        # Check a rollup bundle 
620
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
 
650
        # Check a rollup bundle
 
651
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
621
652
 
622
653
        # Modified files
623
654
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
624
 
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
 
655
        open('b1/sub/dir/ pre space', 'ab').write(
 
656
             '\r\nAdding some\r\nDOS format lines\r\n')
625
657
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
626
 
        self.tree1.rename_one('sub/dir/ pre space', 
 
658
        self.tree1.rename_one('sub/dir/ pre space',
627
659
                              'sub/ start space')
628
660
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
629
661
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
635
667
                          verbose=False)
636
668
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
637
669
        other = self.get_checkout('a@cset-0-5')
638
 
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
639
 
            'a@cset-0-5')
640
 
        tree2_inv = other.branch.repository.get_inventory_xml('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')
641
674
        self.assertEqualDiff(tree1_inv, tree2_inv)
642
675
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
643
676
        other.commit('rename file', rev_id='a@cset-0-6b')
644
 
        _merge_helper([other.basedir, -1], [None, None],
645
 
                      this_dir=self.tree1.basedir)
 
677
        self.tree1.merge_from_branch(other.branch)
646
678
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
647
679
                          verbose=False)
648
680
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
649
681
 
650
 
    def test_symlink_bundle(self):
651
 
        if not has_symlinks():
652
 
            raise TestSkipped("No symlink support")
 
682
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
 
683
        link_id = 'link-1'
 
684
 
 
685
        self.requireFeature(features.SymlinkFeature)
653
686
        self.tree1 = self.make_branch_and_tree('b1')
654
687
        self.b1 = self.tree1.branch
 
688
 
655
689
        tt = TreeTransform(self.tree1)
656
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
 
690
        tt.new_symlink(link_name, tt.root, link_target, link_id)
657
691
        tt.apply()
658
692
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
659
 
        self.get_valid_bundle(None, '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
 
660
699
        tt = TreeTransform(self.tree1)
661
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
700
        trans_id = tt.trans_id_tree_file_id(link_id)
662
701
        tt.adjust_path('link2', tt.root, trans_id)
663
702
        tt.delete_contents(trans_id)
664
 
        tt.create_symlink('mars', trans_id)
 
703
        tt.create_symlink(new_link_target, trans_id)
665
704
        tt.apply()
666
705
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
667
 
        self.get_valid_bundle('l@cset-0-1', '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
 
668
713
        tt = TreeTransform(self.tree1)
669
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
714
        trans_id = tt.trans_id_tree_file_id(link_id)
670
715
        tt.delete_contents(trans_id)
671
716
        tt.create_symlink('jupiter', trans_id)
672
717
        tt.apply()
673
718
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
674
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
719
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
720
 
675
721
        tt = TreeTransform(self.tree1)
676
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
722
        trans_id = tt.trans_id_tree_file_id(link_id)
677
723
        tt.delete_contents(trans_id)
678
724
        tt.apply()
679
725
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
680
 
        self.get_valid_bundle('l@cset-0-3', '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(features.UnicodeFilenameFeature)
 
733
        self._test_symlink_bundle(u'\N{Euro Sign}link',
 
734
                                  u'bar/\N{Euro Sign}foo',
 
735
                                  u'mars\N{Euro Sign}')
681
736
 
682
737
    def test_binary_bundle(self):
683
738
        self.tree1 = self.make_branch_and_tree('b1')
684
739
        self.b1 = self.tree1.branch
685
740
        tt = TreeTransform(self.tree1)
686
 
        
 
741
 
687
742
        # Add
688
743
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
689
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
 
744
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
 
745
            'binary-2')
690
746
        tt.apply()
691
747
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
692
 
        self.get_valid_bundle(None, 'b@cset-0-1')
 
748
        self.get_valid_bundle('null:', 'b@cset-0-1')
693
749
 
694
750
        # Delete
695
751
        tt = TreeTransform(self.tree1)
719
775
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
720
776
 
721
777
        # Rollup
722
 
        self.get_valid_bundle(None, 'b@cset-0-4')
 
778
        self.get_valid_bundle('null:', 'b@cset-0-4')
723
779
 
724
780
    def test_last_modified(self):
725
781
        self.tree1 = self.make_branch_and_tree('b1')
743
799
        tt.create_file('file2', trans_id)
744
800
        tt.apply()
745
801
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
746
 
        _merge_helper([other.basedir, -1], [None, None],
747
 
                      this_dir=self.tree1.basedir)
 
802
        self.tree1.merge_from_branch(other.branch)
748
803
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
749
804
                          verbose=False)
750
805
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
765
820
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
766
821
                               'a@cset-0-1', bundle_file, format=self.format)
767
822
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
768
 
        self.assertContainsRe(bundle_file.getvalue(), 'one')
769
 
        self.assertContainsRe(bundle_file.getvalue(), 'three')
 
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
                               'a@cset-0-1', bundle_file)
 
833
 
 
834
    @staticmethod
 
835
    def get_raw(bundle_file):
 
836
        return bundle_file.getvalue()
770
837
 
771
838
    def test_unicode_bundle(self):
 
839
        self.requireFeature(features.UnicodeFilenameFeature)
772
840
        # Handle international characters
773
841
        os.mkdir('b1')
774
 
        try:
775
 
            f = open(u'b1/with Dod\xe9', 'wb')
776
 
        except UnicodeEncodeError:
777
 
            raise TestSkipped("Filesystem doesn't support unicode")
 
842
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
778
843
 
779
844
        self.tree1 = self.make_branch_and_tree('b1')
780
845
        self.b1 = self.tree1.branch
784
849
            u'William Dod\xe9\n').encode('utf-8'))
785
850
        f.close()
786
851
 
787
 
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
 
852
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
788
853
        self.tree1.commit(u'i18n commit from William Dod\xe9',
789
854
                          rev_id='i18n-1', committer=u'William Dod\xe9')
790
855
 
791
 
        if sys.platform == 'darwin':
792
 
            # On Mac the '\xe9' gets changed to 'e\u0301'
793
 
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
794
 
                             sorted(os.listdir(u'b1')))
795
 
            delta = self.tree1.changes_from(self.tree1.basis_tree())
796
 
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
797
 
                             delta.removed)
798
 
            self.knownFailure("Mac OSX doesn't preserve unicode"
799
 
                              " combining characters.")
800
 
 
801
856
        # Add
802
 
        bundle = self.get_valid_bundle(None, 'i18n-1')
 
857
        bundle = self.get_valid_bundle('null:', 'i18n-1')
803
858
 
804
859
        # Modified
805
 
        f = open(u'b1/with Dod\xe9', 'wb')
 
860
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
806
861
        f.write(u'Modified \xb5\n'.encode('utf8'))
807
862
        f.close()
808
863
        self.tree1.commit(u'modified', rev_id='i18n-2')
809
864
 
810
865
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
811
 
        
 
866
 
812
867
        # Renamed
813
 
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
 
868
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
814
869
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
815
870
                          committer=u'Erik B\xe5gfors')
816
871
 
817
872
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
818
873
 
819
874
        # Removed
820
 
        self.tree1.remove([u'B\xe5gfors'])
 
875
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
821
876
        self.tree1.commit(u'removed', rev_id='i18n-4')
822
877
 
823
878
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
824
879
 
825
880
        # Rollup
826
 
        bundle = self.get_valid_bundle(None, 'i18n-4')
 
881
        bundle = self.get_valid_bundle('null:', 'i18n-4')
827
882
 
828
883
 
829
884
    def test_whitespace_bundle(self):
830
885
        if sys.platform in ('win32', 'cygwin'):
831
 
            raise TestSkipped('Windows doesn\'t support filenames'
832
 
                              ' with tabs or trailing spaces')
 
886
            raise tests.TestSkipped('Windows doesn\'t support filenames'
 
887
                                    ' with tabs or trailing spaces')
833
888
        self.tree1 = self.make_branch_and_tree('b1')
834
889
        self.b1 = self.tree1.branch
835
890
 
841
896
        # Added
842
897
        self.tree1.commit('funky whitespace', rev_id='white-1')
843
898
 
844
 
        bundle = self.get_valid_bundle(None, 'white-1')
 
899
        bundle = self.get_valid_bundle('null:', 'white-1')
845
900
 
846
901
        # Modified
847
902
        open('b1/trailing space ', 'ab').write('add some text\n')
860
915
        self.tree1.commit('removed', rev_id='white-4')
861
916
 
862
917
        bundle = self.get_valid_bundle('white-3', 'white-4')
863
 
        
 
918
 
864
919
        # Now test a complet roll-up
865
 
        bundle = self.get_valid_bundle(None, 'white-4')
 
920
        bundle = self.get_valid_bundle('null:', 'white-4')
866
921
 
867
922
    def test_alt_timezone_bundle(self):
868
923
        self.tree1 = self.make_branch_and_memory_tree('b1')
878
933
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
879
934
                          timezone=19800, timestamp=1152544886.0)
880
935
 
881
 
        bundle = self.get_valid_bundle(None, 'tz-1')
882
 
        
 
936
        bundle = self.get_valid_bundle('null:', 'tz-1')
 
937
 
883
938
        rev = bundle.revisions[0]
884
939
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
885
940
        self.assertEqual(19800, rev.timezone)
890
945
        self.tree1 = self.make_branch_and_tree('b1')
891
946
        self.b1 = self.tree1.branch
892
947
        self.tree1.commit('message', rev_id='revid1')
893
 
        bundle = self.get_valid_bundle(None, 'revid1')
894
 
        tree = bundle.revision_tree(self.b1.repository, 'revid1')
 
948
        bundle = self.get_valid_bundle('null:', 'revid1')
 
949
        tree = self.get_bundle_tree(bundle, 'revid1')
895
950
        self.assertEqual('revid1', tree.inventory.root.revision)
896
951
 
897
952
    def test_install_revisions(self):
898
953
        self.tree1 = self.make_branch_and_tree('b1')
899
954
        self.b1 = self.tree1.branch
900
955
        self.tree1.commit('message', rev_id='rev2a')
901
 
        bundle = self.get_valid_bundle(None, 'rev2a')
 
956
        bundle = self.get_valid_bundle('null:', 'rev2a')
902
957
        branch2 = self.make_branch('b2')
903
958
        self.assertFalse(branch2.repository.has_revision('rev2a'))
904
959
        target_revision = bundle.install_revisions(branch2.repository)
913
968
        tree.add([''], ['TREE_ROOT'])
914
969
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
915
970
        self.b1 = tree.branch
916
 
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
 
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')
917
1169
        self.assertContainsRe(bundle_sio.getvalue(),
918
1170
                              '# properties:\n'
919
1171
                              '#   branch-nick: tree\n'
927
1179
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
928
1180
                         rev.properties)
929
1181
 
 
1182
    def get_bundle_tree(self, bundle, revision_id):
 
1183
        repository = self.make_repository('repo')
 
1184
        return bundle.revision_tree(repository, 'revid1')
 
1185
 
930
1186
    def test_bundle_empty_property_alt(self):
931
1187
        """Test serializing revision properties with an empty value.
932
1188
 
941
1197
        tree.add([''], ['TREE_ROOT'])
942
1198
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
943
1199
        self.b1 = tree.branch
944
 
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
 
1200
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
945
1201
        txt = bundle_sio.getvalue()
946
1202
        loc = txt.find('#   empty: ') + len('#   empty:')
947
1203
        # Create a new bundle, which strips the trailing space after empty
970
1226
        tree.commit('One', rev_id='rev1',
971
1227
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
972
1228
        self.b1 = tree.branch
973
 
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
 
1229
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
974
1230
        self.assertContainsRe(bundle_sio.getvalue(),
975
1231
                              '# properties:\n'
976
1232
                              '#   a: 4\n'
1001
1257
        tree.commit('One', rev_id='rev1',
1002
1258
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
1003
1259
        self.b1 = tree.branch
1004
 
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
 
1260
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1005
1261
        self.assertContainsRe(bundle_sio.getvalue(),
1006
1262
                              '# properties:\n'
1007
1263
                              '#   alpha: \xce\xb1\n'
1036
1292
        return format
1037
1293
 
1038
1294
 
1039
 
class MungedBundleTester(TestCaseWithTransport):
 
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):
1040
1643
 
1041
1644
    def build_test_bundle(self):
1042
1645
        wt = self.make_branch_and_tree('b1')
1051
1654
 
1052
1655
        bundle_txt = StringIO()
1053
1656
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
1054
 
                               'a@cset-0-1', bundle_txt)
1055
 
        self.assertEqual(['a@cset-0-2'], rev_ids)
 
1657
                               'a@cset-0-1', bundle_txt, self.format)
 
1658
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
1056
1659
        bundle_txt.seek(0, 0)
1057
1660
        return bundle_txt
1058
1661
 
1087
1690
        bundle = read_bundle(bundle_txt)
1088
1691
        self.check_valid(bundle)
1089
1692
 
 
1693
 
 
1694
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
 
1695
 
 
1696
    format = '0.9'
 
1697
 
1090
1698
    def test_missing_trailing_whitespace(self):
1091
1699
        bundle_txt = self.build_test_bundle()
1092
1700
 
1120
1728
        bundle = read_bundle(bundle_txt)
1121
1729
        self.check_valid(bundle)
1122
1730
 
 
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 = DisconnectingServer()
 
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 DisconnectingHandler(SocketServer.BaseRequestHandler):
 
1848
    """A request handler that immediately closes any connection made to it."""
 
1849
 
 
1850
    def handle(self):
 
1851
        self.request.close()
 
1852
 
 
1853
 
 
1854
class DisconnectingServer(test_server.TestingTCPServerInAThread):
 
1855
 
 
1856
    def __init__(self):
 
1857
        super(DisconnectingServer, self).__init__(
 
1858
            ('127.0.0.1', 0),
 
1859
            test_server.TestingTCPServer,
 
1860
            DisconnectingHandler)
 
1861
 
 
1862
    def get_url(self):
 
1863
        """Return the url of the server"""
 
1864
        return "bzr://%s:%d/" % self.server.server_address