~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Martin Pool
  • Date: 2007-06-18 06:54:24 UTC
  • mto: This revision was merged to the branch mainline in revision 2551.
  • Revision ID: mbp@sourcefrog.net-20070618065424-awsn4t4tv2bi4okt
Remove duplicated commit use case documentation

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
from cStringIO import StringIO
18
18
import os
19
 
import socket
20
19
import sys
21
 
import threading
 
20
import tempfile
22
21
 
23
22
from bzrlib import (
24
23
    bzrdir,
25
 
    diff,
26
24
    errors,
27
25
    inventory,
28
 
    merge,
29
 
    osutils,
30
 
    revision as _mod_revision,
31
 
    symbol_versioning,
32
 
    tests,
 
26
    repository,
33
27
    treebuilder,
34
28
    )
35
 
from bzrlib.bundle import read_mergeable_from_url
 
29
from bzrlib.builtins import _merge_helper
 
30
from bzrlib.bzrdir import BzrDir
36
31
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
37
32
from bzrlib.bundle.bundle_data import BundleTree
38
 
from bzrlib.directory_service import directories
39
 
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
 
33
from bzrlib.bundle.serializer import write_bundle, read_bundle
40
34
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
41
35
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
42
 
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
 
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
43
41
from bzrlib.repofmt import knitrepo
44
 
from bzrlib.tests import (
45
 
    test_read_bundle,
46
 
    test_commit,
47
 
    )
 
42
from bzrlib.osutils import has_symlinks, sha_file
 
43
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
 
44
                          TestCase, TestSkipped)
48
45
from bzrlib.transform import TreeTransform
49
 
from bzrlib.tests import (
50
 
    features,
51
 
    )
52
 
 
53
 
 
54
 
def get_text(vf, key):
55
 
    """Get the fulltext for a given revision id that is present in the vf"""
56
 
    stream = vf.get_record_stream([key], 'unordered', True)
57
 
    record = stream.next()
58
 
    return record.get_bytes_as('fulltext')
59
 
 
60
 
 
61
 
def get_inventory_text(repo, revision_id):
62
 
    """Get the fulltext for the inventory at revision id"""
63
 
    repo.lock_read()
64
 
    try:
65
 
        return get_text(repo.inventories, (revision_id,))
66
 
    finally:
67
 
        repo.unlock()
 
46
from bzrlib.workingtree import WorkingTree
68
47
 
69
48
 
70
49
class MockTree(object):
71
 
 
72
50
    def __init__(self):
73
51
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
74
52
        object.__init__(self)
79
57
 
80
58
    inventory = property(lambda x:x)
81
59
 
82
 
    def all_file_ids(self):
83
 
        return set(self.paths.keys())
 
60
    def __iter__(self):
 
61
        return self.paths.iterkeys()
84
62
 
85
63
    def __getitem__(self, file_id):
86
64
        if file_id == self.root.file_id:
116
94
            ie = InventoryDirectory(file_id, name, parent_id)
117
95
        elif kind == 'file':
118
96
            ie = InventoryFile(file_id, name, parent_id)
119
 
            ie.text_sha1 = text_sha_1
120
 
            ie.text_size = text_size
121
97
        elif kind == 'symlink':
122
98
            ie = InventoryLink(file_id, name, parent_id)
123
99
        else:
124
 
            raise errors.BzrError('unknown kind %r' % kind)
 
100
            raise BzrError('unknown kind %r' % kind)
 
101
        ie.text_sha1 = text_sha_1
 
102
        ie.text_size = text_size
125
103
        return ie
126
104
 
127
105
    def add_dir(self, file_id, path):
128
106
        self.paths[file_id] = path
129
107
        self.ids[path] = file_id
130
 
 
 
108
    
131
109
    def add_file(self, file_id, path, contents):
132
110
        self.add_dir(file_id, path)
133
111
        self.contents[file_id] = contents
147
125
        result.seek(0,0)
148
126
        return result
149
127
 
150
 
    def get_file_revision(self, file_id):
151
 
        return self.inventory[file_id].revision
152
 
 
153
128
    def contents_stats(self, file_id):
154
129
        if file_id not in self.contents:
155
130
            return None, None
156
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
131
        text_sha1 = sha_file(self.get_file(file_id))
157
132
        return text_sha1, len(self.contents[file_id])
158
133
 
159
134
 
160
 
class BTreeTester(tests.TestCase):
 
135
class BTreeTester(TestCase):
161
136
    """A simple unittest tester for the BundleTree class."""
162
137
 
163
138
    def make_tree_1(self):
167
142
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
168
143
        mtree.add_dir("d", "grandparent/alt_parent")
169
144
        return BundleTree(mtree, ''), mtree
170
 
 
 
145
        
171
146
    def test_renames(self):
172
147
        """Ensure that file renames have the proper effect on children"""
173
148
        btree = self.make_tree_1()[0]
174
149
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
175
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
150
        self.assertEqual(btree.old_path("grandparent/parent"), 
176
151
                         "grandparent/parent")
177
152
        self.assertEqual(btree.old_path("grandparent/parent/file"),
178
153
                         "grandparent/parent/file")
185
160
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
186
161
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
187
162
 
188
 
        self.assertTrue(btree.path2id("grandparent2") is None)
189
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
190
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
163
        assert btree.path2id("grandparent2") is None
 
164
        assert btree.path2id("grandparent2/parent") is None
 
165
        assert btree.path2id("grandparent2/parent/file") is None
191
166
 
192
167
        btree.note_rename("grandparent", "grandparent2")
193
 
        self.assertTrue(btree.old_path("grandparent") is None)
194
 
        self.assertTrue(btree.old_path("grandparent/parent") is None)
195
 
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
 
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
196
171
 
197
172
        self.assertEqual(btree.id2path("a"), "grandparent2")
198
173
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
202
177
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
203
178
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
204
179
 
205
 
        self.assertTrue(btree.path2id("grandparent") is None)
206
 
        self.assertTrue(btree.path2id("grandparent/parent") is None)
207
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
180
        assert btree.path2id("grandparent") is None
 
181
        assert btree.path2id("grandparent/parent") is None
 
182
        assert btree.path2id("grandparent/parent/file") is None
208
183
 
209
184
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
210
185
        self.assertEqual(btree.id2path("a"), "grandparent2")
215
190
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
216
191
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
217
192
 
218
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
219
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
193
        assert btree.path2id("grandparent2/parent") is None
 
194
        assert btree.path2id("grandparent2/parent/file") is None
220
195
 
221
 
        btree.note_rename("grandparent/parent/file",
 
196
        btree.note_rename("grandparent/parent/file", 
222
197
                          "grandparent2/parent2/file2")
223
198
        self.assertEqual(btree.id2path("a"), "grandparent2")
224
199
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
228
203
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
229
204
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
230
205
 
231
 
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
 
206
        assert btree.path2id("grandparent2/parent2/file") is None
232
207
 
233
208
    def test_moves(self):
234
209
        """Ensure that file moves have the proper effect on children"""
235
210
        btree = self.make_tree_1()[0]
236
 
        btree.note_rename("grandparent/parent/file",
 
211
        btree.note_rename("grandparent/parent/file", 
237
212
                          "grandparent/alt_parent/file")
238
213
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
239
214
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
240
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
215
        assert btree.path2id("grandparent/parent/file") is None
241
216
 
242
217
    def unified_diff(self, old, new):
243
218
        out = StringIO()
244
 
        diff.internal_diff("old", old, "new", new, out)
 
219
        internal_diff("old", old, "new", new, out)
245
220
        out.seek(0,0)
246
221
        return out.read()
247
222
 
248
223
    def make_tree_2(self):
249
224
        btree = self.make_tree_1()[0]
250
 
        btree.note_rename("grandparent/parent/file",
 
225
        btree.note_rename("grandparent/parent/file", 
251
226
                          "grandparent/alt_parent/file")
252
 
        self.assertTrue(btree.id2path("e") is None)
253
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
227
        assert btree.id2path("e") is None
 
228
        assert btree.path2id("grandparent/parent/file") is None
254
229
        btree.note_id("e", "grandparent/parent/file")
255
230
        return btree
256
231
 
282
257
    def make_tree_3(self):
283
258
        btree, mtree = self.make_tree_1()
284
259
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
285
 
        btree.note_rename("grandparent/parent/file",
 
260
        btree.note_rename("grandparent/parent/file", 
286
261
                          "grandparent/alt_parent/file")
287
 
        btree.note_rename("grandparent/parent/topping",
 
262
        btree.note_rename("grandparent/parent/topping", 
288
263
                          "grandparent/alt_parent/stopping")
289
264
        return btree
290
265
 
314
289
        btree = self.make_tree_1()[0]
315
290
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
316
291
        btree.note_deletion("grandparent/parent/file")
317
 
        self.assertTrue(btree.id2path("c") is None)
318
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
292
        assert btree.id2path("c") is None
 
293
        assert btree.path2id("grandparent/parent/file") is None
319
294
 
320
295
    def sorted_ids(self, tree):
321
296
        ids = list(tree)
329
304
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
330
305
        btree.note_deletion("grandparent/parent/file")
331
306
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
332
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
307
        btree.note_last_changed("grandparent/alt_parent/fool", 
333
308
                                "revisionidiguess")
334
309
        self.assertEqual(self.sorted_ids(btree),
335
310
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
336
311
 
337
312
 
338
 
class BundleTester1(tests.TestCaseWithTransport):
 
313
class BundleTester1(TestCaseWithTransport):
339
314
 
340
315
    def test_mismatched_bundle(self):
341
316
        format = bzrdir.BzrDirMetaFormat1()
342
317
        format.repository_format = knitrepo.RepositoryFormatKnit3()
343
318
        serializer = BundleSerializerV08('0.8')
344
319
        b = self.make_branch('.', format=format)
345
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
320
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
346
321
                          b.repository, [], {}, StringIO())
347
322
 
348
323
    def test_matched_bundle(self):
361
336
        source.commit('one', rev_id='one-id')
362
337
        source.commit('two', rev_id='two-id')
363
338
        text = StringIO()
364
 
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
 
339
        write_bundle(source.branch.repository, 'two-id', None, text, 
365
340
                     format='0.9')
366
341
        text.seek(0)
367
342
 
368
343
        format = bzrdir.BzrDirMetaFormat1()
369
344
        format.repository_format = knitrepo.RepositoryFormatKnit1()
370
345
        target = self.make_branch('target', format=format)
371
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
346
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
372
347
                          target.repository, read_bundle(text))
373
348
 
374
349
 
375
 
class BundleTester(object):
 
350
class V08BundleTester(TestCaseWithTransport):
 
351
 
 
352
    format = '0.8'
376
353
 
377
354
    def bzrdir_format(self):
378
355
        format = bzrdir.BzrDirMetaFormat1()
382
359
    def make_branch_and_tree(self, path, format=None):
383
360
        if format is None:
384
361
            format = self.bzrdir_format()
385
 
        return tests.TestCaseWithTransport.make_branch_and_tree(
386
 
            self, path, format)
 
362
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
387
363
 
388
364
    def make_branch(self, path, format=None):
389
365
        if format is None:
390
366
            format = self.bzrdir_format()
391
 
        return tests.TestCaseWithTransport.make_branch(self, path, format)
 
367
        return TestCaseWithTransport.make_branch(self, path, format)
392
368
 
393
369
    def create_bundle_text(self, base_rev_id, rev_id):
394
370
        bundle_txt = StringIO()
395
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
371
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
396
372
                               bundle_txt, format=self.format)
397
373
        bundle_txt.seek(0)
398
 
        self.assertEqual(bundle_txt.readline(),
 
374
        self.assertEqual(bundle_txt.readline(), 
399
375
                         '# Bazaar revision bundle v%s\n' % self.format)
400
376
        self.assertEqual(bundle_txt.readline(), '#\n')
401
377
 
402
378
        rev = self.b1.repository.get_revision(rev_id)
403
379
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
404
380
                         u'# message:\n')
 
381
 
 
382
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
405
383
        bundle_txt.seek(0)
406
384
        return bundle_txt, rev_ids
407
385
 
409
387
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
410
388
        Make sure that the text generated is valid, and that it
411
389
        can be applied against the base, and generate the same information.
412
 
 
413
 
        :return: The in-memory bundle
 
390
        
 
391
        :return: The in-memory bundle 
414
392
        """
415
393
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
416
394
 
417
 
        # This should also validate the generated bundle
 
395
        # This should also validate the generated bundle 
418
396
        bundle = read_bundle(bundle_txt)
419
397
        repository = self.b1.repository
420
398
        for bundle_rev in bundle.real_revisions:
424
402
            # it
425
403
            branch_rev = repository.get_revision(bundle_rev.revision_id)
426
404
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
427
 
                      'timestamp', 'timezone', 'message', 'committer',
 
405
                      'timestamp', 'timezone', 'message', 'committer', 
428
406
                      'parent_ids', 'properties'):
429
 
                self.assertEqual(getattr(branch_rev, a),
 
407
                self.assertEqual(getattr(branch_rev, a), 
430
408
                                 getattr(bundle_rev, a))
431
 
            self.assertEqual(len(branch_rev.parent_ids),
 
409
            self.assertEqual(len(branch_rev.parent_ids), 
432
410
                             len(bundle_rev.parent_ids))
433
 
        self.assertEqual(rev_ids,
 
411
        self.assertEqual(rev_ids, 
434
412
                         [r.revision_id for r in bundle.real_revisions])
435
413
        self.valid_apply_bundle(base_rev_id, bundle,
436
414
                                   checkout_dir=checkout_dir)
440
418
    def get_invalid_bundle(self, base_rev_id, rev_id):
441
419
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
442
420
        Munge the text so that it's invalid.
443
 
 
 
421
        
444
422
        :return: The in-memory bundle
445
423
        """
446
424
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
447
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
425
        new_text = bundle_txt.getvalue().replace('executable:no', 
448
426
                                               'executable:yes')
449
427
        bundle_txt = StringIO(new_text)
450
428
        bundle = read_bundle(bundle_txt)
451
429
        self.valid_apply_bundle(base_rev_id, bundle)
452
 
        return bundle
 
430
        return bundle 
453
431
 
454
432
    def test_non_bundle(self):
455
 
        self.assertRaises(errors.NotABundle,
456
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
433
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
457
434
 
458
435
    def test_malformed(self):
459
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
436
        self.assertRaises(BadBundle, read_bundle, 
460
437
                          StringIO('# Bazaar revision bundle v'))
461
438
 
462
439
    def test_crlf_bundle(self):
463
440
        try:
464
441
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
465
 
        except errors.BadBundle:
 
442
        except BadBundle:
466
443
            # It is currently permitted for bundles with crlf line endings to
467
444
            # make read_bundle raise a BadBundle, but this should be fixed.
468
445
            # Anything else, especially NotABundle, is an error.
473
450
        """
474
451
 
475
452
        if checkout_dir is None:
476
 
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
 
453
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
477
454
        else:
478
455
            if not os.path.exists(checkout_dir):
479
456
                os.mkdir(checkout_dir)
480
457
        tree = self.make_branch_and_tree(checkout_dir)
481
458
        s = StringIO()
482
 
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
 
459
        ancestors = write_bundle(self.b1.repository, rev_id, None, s,
483
460
                                 format=self.format)
484
461
        s.seek(0)
485
 
        self.assertIsInstance(s.getvalue(), str)
 
462
        assert isinstance(s.getvalue(), str), (
 
463
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
486
464
        install_bundle(tree.branch.repository, read_bundle(s))
487
465
        for ancestor in ancestors:
488
466
            old = self.b1.repository.revision_tree(ancestor)
489
467
            new = tree.branch.repository.revision_tree(ancestor)
490
 
            old.lock_read()
491
 
            new.lock_read()
492
 
            try:
493
 
                # Check that there aren't any inventory level changes
494
 
                delta = new.changes_from(old)
495
 
                self.assertFalse(delta.has_changed(),
496
 
                                 'Revision %s not copied correctly.'
497
 
                                 % (ancestor,))
498
 
 
499
 
                # Now check that the file contents are all correct
500
 
                for inventory_id in old.all_file_ids():
501
 
                    try:
502
 
                        old_file = old.get_file(inventory_id)
503
 
                    except errors.NoSuchFile:
504
 
                        continue
505
 
                    if old_file is None:
506
 
                        continue
507
 
                    self.assertEqual(old_file.read(),
508
 
                                     new.get_file(inventory_id).read())
509
 
            finally:
510
 
                new.unlock()
511
 
                old.unlock()
512
 
        if not _mod_revision.is_null(rev_id):
 
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:
513
486
            rh = self.b1.revision_history()
514
 
            self.applyDeprecated(symbol_versioning.deprecated_in((2, 4, 0)),
515
 
                tree.branch.set_revision_history, rh[:rh.index(rev_id)+1])
 
487
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
516
488
            tree.update()
517
489
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
518
490
            self.assertFalse(delta.has_changed(),
524
496
        sure everything matches the builtin branch.
525
497
        """
526
498
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
527
 
        to_tree.lock_write()
528
 
        try:
529
 
            self._valid_apply_bundle(base_rev_id, info, to_tree)
530
 
        finally:
531
 
            to_tree.unlock()
532
 
 
533
 
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
534
499
        original_parents = to_tree.get_parent_ids()
535
500
        repository = to_tree.branch.repository
536
501
        original_parents = to_tree.get_parent_ids()
537
502
        self.assertIs(repository.has_revision(base_rev_id), True)
538
503
        for rev in info.real_revisions:
539
504
            self.assert_(not repository.has_revision(rev.revision_id),
540
 
                'Revision {%s} present before applying bundle'
 
505
                'Revision {%s} present before applying bundle' 
541
506
                % rev.revision_id)
542
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
507
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
543
508
 
544
509
        for rev in info.real_revisions:
545
510
            self.assert_(repository.has_revision(rev.revision_id),
546
 
                'Missing revision {%s} after applying bundle'
 
511
                'Missing revision {%s} after applying bundle' 
547
512
                % rev.revision_id)
548
513
 
549
514
        self.assert_(to_tree.branch.repository.has_revision(info.target))
555
520
        rev = info.real_revisions[-1]
556
521
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
557
522
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
558
 
 
 
523
        
559
524
        # TODO: make sure the target tree is identical to base tree
560
525
        #       we might also check the working tree.
561
526
 
580
545
        self.tree1 = self.make_branch_and_tree('b1')
581
546
        self.b1 = self.tree1.branch
582
547
 
583
 
        self.build_tree_contents([('b1/one', 'one\n')])
584
 
        self.tree1.add('one', 'one-id')
585
 
        self.tree1.set_root_id('root-id')
 
548
        open('b1/one', 'wb').write('one\n')
 
549
        self.tree1.add('one')
586
550
        self.tree1.commit('add one', rev_id='a@cset-0-1')
587
551
 
588
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
 
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')
589
559
 
590
560
        # Make sure we can handle files with spaces, tabs, other
591
561
        # bogus characters
599
569
                , 'b1/sub/sub/'
600
570
                , 'b1/sub/sub/nonempty.txt'
601
571
                ])
602
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
603
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
572
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
573
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
604
574
        tt = TreeTransform(self.tree1)
605
575
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
606
576
        tt.apply()
607
 
        # have to fix length of file-id so that we can predictably rewrite
608
 
        # a (length-prefixed) record containing it later.
609
 
        self.tree1.add('with space.txt', 'withspace-id')
610
577
        self.tree1.add([
611
 
                  'dir'
 
578
                'with space.txt'
 
579
                , 'dir'
612
580
                , 'dir/filein subdir.c'
613
581
                , 'dir/WithCaps.txt'
614
582
                , 'dir/ pre space'
622
590
 
623
591
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
624
592
 
625
 
        # Check a rollup bundle
626
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
 
593
        # Check a rollup bundle 
 
594
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
627
595
 
628
596
        # Now delete entries
629
597
        self.tree1.remove(
636
604
        tt.set_executability(False, trans_id)
637
605
        tt.apply()
638
606
        self.tree1.commit('removed', rev_id='a@cset-0-3')
639
 
 
 
607
        
640
608
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
641
 
        self.assertRaises((errors.TestamentMismatch,
642
 
            errors.VersionedFileInvalidChecksum,
643
 
            errors.BadBundle), self.get_invalid_bundle,
644
 
            'a@cset-0-2', 'a@cset-0-3')
645
 
        # Check a rollup bundle
646
 
        bundle = self.get_valid_bundle('null:', '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')
647
613
 
648
614
        # Now move the directory
649
615
        self.tree1.rename_one('dir', 'sub/dir')
650
616
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
651
617
 
652
618
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
653
 
        # Check a rollup bundle
654
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
 
619
        # Check a rollup bundle 
 
620
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
655
621
 
656
622
        # Modified files
657
623
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
658
 
        open('b1/sub/dir/ pre space', 'ab').write(
659
 
             '\r\nAdding some\r\nDOS format lines\r\n')
 
624
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
660
625
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
661
 
        self.tree1.rename_one('sub/dir/ pre space',
 
626
        self.tree1.rename_one('sub/dir/ pre space', 
662
627
                              'sub/ start space')
663
628
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
664
629
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
670
635
                          verbose=False)
671
636
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
672
637
        other = self.get_checkout('a@cset-0-5')
673
 
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
674
 
                                       'a@cset-0-5')
675
 
        tree2_inv = get_inventory_text(other.branch.repository,
676
 
                                       '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')
677
641
        self.assertEqualDiff(tree1_inv, tree2_inv)
678
642
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
679
643
        other.commit('rename file', rev_id='a@cset-0-6b')
680
 
        self.tree1.merge_from_branch(other.branch)
 
644
        _merge_helper([other.basedir, -1], [None, None],
 
645
                      this_dir=self.tree1.basedir)
681
646
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
682
647
                          verbose=False)
683
648
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
684
649
 
685
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
686
 
        link_id = 'link-1'
687
 
 
688
 
        self.requireFeature(features.SymlinkFeature)
 
650
    def test_symlink_bundle(self):
 
651
        if not has_symlinks():
 
652
            raise TestSkipped("No symlink support")
689
653
        self.tree1 = self.make_branch_and_tree('b1')
690
654
        self.b1 = self.tree1.branch
691
 
 
692
655
        tt = TreeTransform(self.tree1)
693
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
656
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
694
657
        tt.apply()
695
658
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
696
 
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
697
 
        if getattr(bundle ,'revision_tree', None) is not None:
698
 
            # Not all bundle formats supports revision_tree
699
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
700
 
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
701
 
 
 
659
        self.get_valid_bundle(None, 'l@cset-0-1')
702
660
        tt = TreeTransform(self.tree1)
703
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
661
        trans_id = tt.trans_id_tree_file_id('link-1')
704
662
        tt.adjust_path('link2', tt.root, trans_id)
705
663
        tt.delete_contents(trans_id)
706
 
        tt.create_symlink(new_link_target, trans_id)
 
664
        tt.create_symlink('mars', trans_id)
707
665
        tt.apply()
708
666
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
709
 
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
710
 
        if getattr(bundle ,'revision_tree', None) is not None:
711
 
            # Not all bundle formats supports revision_tree
712
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
713
 
            self.assertEqual(new_link_target,
714
 
                             bund_tree.get_symlink_target(link_id))
715
 
 
 
667
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
716
668
        tt = TreeTransform(self.tree1)
717
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
669
        trans_id = tt.trans_id_tree_file_id('link-1')
718
670
        tt.delete_contents(trans_id)
719
671
        tt.create_symlink('jupiter', trans_id)
720
672
        tt.apply()
721
673
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
722
 
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
723
 
 
 
674
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
724
675
        tt = TreeTransform(self.tree1)
725
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
676
        trans_id = tt.trans_id_tree_file_id('link-1')
726
677
        tt.delete_contents(trans_id)
727
678
        tt.apply()
728
679
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
729
 
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
730
 
 
731
 
    def test_symlink_bundle(self):
732
 
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
733
 
 
734
 
    def test_unicode_symlink_bundle(self):
735
 
        self.requireFeature(features.UnicodeFilenameFeature)
736
 
        self._test_symlink_bundle(u'\N{Euro Sign}link',
737
 
                                  u'bar/\N{Euro Sign}foo',
738
 
                                  u'mars\N{Euro Sign}')
 
680
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
739
681
 
740
682
    def test_binary_bundle(self):
741
683
        self.tree1 = self.make_branch_and_tree('b1')
742
684
        self.b1 = self.tree1.branch
743
685
        tt = TreeTransform(self.tree1)
744
 
 
 
686
        
745
687
        # Add
746
688
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
747
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
748
 
            'binary-2')
 
689
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
749
690
        tt.apply()
750
691
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
751
 
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
692
        self.get_valid_bundle(None, 'b@cset-0-1')
752
693
 
753
694
        # Delete
754
695
        tt = TreeTransform(self.tree1)
778
719
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
779
720
 
780
721
        # Rollup
781
 
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
722
        self.get_valid_bundle(None, 'b@cset-0-4')
782
723
 
783
724
    def test_last_modified(self):
784
725
        self.tree1 = self.make_branch_and_tree('b1')
802
743
        tt.create_file('file2', trans_id)
803
744
        tt.apply()
804
745
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
805
 
        self.tree1.merge_from_branch(other.branch)
 
746
        _merge_helper([other.basedir, -1], [None, None],
 
747
                      this_dir=self.tree1.basedir)
806
748
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
807
749
                          verbose=False)
808
750
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
823
765
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
824
766
                               'a@cset-0-1', bundle_file, format=self.format)
825
767
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
826
 
        self.assertContainsRe(self.get_raw(bundle_file), 'one')
827
 
        self.assertContainsRe(self.get_raw(bundle_file), 'three')
828
 
 
829
 
    def test_bundle_same_basis(self):
830
 
        """Ensure using the basis as the target doesn't cause an error"""
831
 
        self.tree1 = self.make_branch_and_tree('b1')
832
 
        self.tree1.commit('add file', rev_id='a@cset-0-1')
833
 
        bundle_file = StringIO()
834
 
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-1',
835
 
                               'a@cset-0-1', bundle_file)
836
 
 
837
 
    @staticmethod
838
 
    def get_raw(bundle_file):
839
 
        return bundle_file.getvalue()
 
768
        self.assertContainsRe(bundle_file.getvalue(), 'one')
 
769
        self.assertContainsRe(bundle_file.getvalue(), 'three')
840
770
 
841
771
    def test_unicode_bundle(self):
842
 
        self.requireFeature(features.UnicodeFilenameFeature)
843
772
        # Handle international characters
844
773
        os.mkdir('b1')
845
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
774
        try:
 
775
            f = open(u'b1/with Dod\xe9', 'wb')
 
776
        except UnicodeEncodeError:
 
777
            raise TestSkipped("Filesystem doesn't support unicode")
846
778
 
847
779
        self.tree1 = self.make_branch_and_tree('b1')
848
780
        self.b1 = self.tree1.branch
852
784
            u'William Dod\xe9\n').encode('utf-8'))
853
785
        f.close()
854
786
 
855
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
787
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
856
788
        self.tree1.commit(u'i18n commit from William Dod\xe9',
857
789
                          rev_id='i18n-1', committer=u'William Dod\xe9')
858
790
 
 
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
 
859
801
        # Add
860
 
        bundle = self.get_valid_bundle('null:', 'i18n-1')
 
802
        bundle = self.get_valid_bundle(None, 'i18n-1')
861
803
 
862
804
        # Modified
863
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
805
        f = open(u'b1/with Dod\xe9', 'wb')
864
806
        f.write(u'Modified \xb5\n'.encode('utf8'))
865
807
        f.close()
866
808
        self.tree1.commit(u'modified', rev_id='i18n-2')
867
809
 
868
810
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
869
 
 
 
811
        
870
812
        # Renamed
871
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
813
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
872
814
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
873
815
                          committer=u'Erik B\xe5gfors')
874
816
 
875
817
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
876
818
 
877
819
        # Removed
878
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
820
        self.tree1.remove([u'B\xe5gfors'])
879
821
        self.tree1.commit(u'removed', rev_id='i18n-4')
880
822
 
881
823
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
882
824
 
883
825
        # Rollup
884
 
        bundle = self.get_valid_bundle('null:', 'i18n-4')
 
826
        bundle = self.get_valid_bundle(None, 'i18n-4')
885
827
 
886
828
 
887
829
    def test_whitespace_bundle(self):
888
830
        if sys.platform in ('win32', 'cygwin'):
889
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
890
 
                                    ' with tabs or trailing spaces')
 
831
            raise TestSkipped('Windows doesn\'t support filenames'
 
832
                              ' with tabs or trailing spaces')
891
833
        self.tree1 = self.make_branch_and_tree('b1')
892
834
        self.b1 = self.tree1.branch
893
835
 
899
841
        # Added
900
842
        self.tree1.commit('funky whitespace', rev_id='white-1')
901
843
 
902
 
        bundle = self.get_valid_bundle('null:', 'white-1')
 
844
        bundle = self.get_valid_bundle(None, 'white-1')
903
845
 
904
846
        # Modified
905
847
        open('b1/trailing space ', 'ab').write('add some text\n')
918
860
        self.tree1.commit('removed', rev_id='white-4')
919
861
 
920
862
        bundle = self.get_valid_bundle('white-3', 'white-4')
921
 
 
 
863
        
922
864
        # Now test a complet roll-up
923
 
        bundle = self.get_valid_bundle('null:', 'white-4')
 
865
        bundle = self.get_valid_bundle(None, 'white-4')
924
866
 
925
867
    def test_alt_timezone_bundle(self):
926
868
        self.tree1 = self.make_branch_and_memory_tree('b1')
936
878
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
937
879
                          timezone=19800, timestamp=1152544886.0)
938
880
 
939
 
        bundle = self.get_valid_bundle('null:', 'tz-1')
940
 
 
 
881
        bundle = self.get_valid_bundle(None, 'tz-1')
 
882
        
941
883
        rev = bundle.revisions[0]
942
884
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
943
885
        self.assertEqual(19800, rev.timezone)
948
890
        self.tree1 = self.make_branch_and_tree('b1')
949
891
        self.b1 = self.tree1.branch
950
892
        self.tree1.commit('message', rev_id='revid1')
951
 
        bundle = self.get_valid_bundle('null:', 'revid1')
952
 
        tree = self.get_bundle_tree(bundle, 'revid1')
 
893
        bundle = self.get_valid_bundle(None, 'revid1')
 
894
        tree = bundle.revision_tree(self.b1.repository, 'revid1')
953
895
        self.assertEqual('revid1', tree.inventory.root.revision)
954
896
 
955
897
    def test_install_revisions(self):
956
898
        self.tree1 = self.make_branch_and_tree('b1')
957
899
        self.b1 = self.tree1.branch
958
900
        self.tree1.commit('message', rev_id='rev2a')
959
 
        bundle = self.get_valid_bundle('null:', 'rev2a')
 
901
        bundle = self.get_valid_bundle(None, 'rev2a')
960
902
        branch2 = self.make_branch('b2')
961
903
        self.assertFalse(branch2.repository.has_revision('rev2a'))
962
904
        target_revision = bundle.install_revisions(branch2.repository)
971
913
        tree.add([''], ['TREE_ROOT'])
972
914
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
973
915
        self.b1 = tree.branch
974
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
975
 
        bundle = read_bundle(bundle_sio)
976
 
        revision_info = bundle.revisions[0]
977
 
        self.assertEqual('rev1', revision_info.revision_id)
978
 
        rev = revision_info.as_revision()
979
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
980
 
                         rev.properties)
981
 
 
982
 
    def test_bundle_sorted_properties(self):
983
 
        """For stability the writer should write properties in sorted order."""
984
 
        tree = self.make_branch_and_memory_tree('tree')
985
 
        tree.lock_write()
986
 
        self.addCleanup(tree.unlock)
987
 
 
988
 
        tree.add([''], ['TREE_ROOT'])
989
 
        tree.commit('One', rev_id='rev1',
990
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
991
 
        self.b1 = tree.branch
992
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
993
 
        bundle = read_bundle(bundle_sio)
994
 
        revision_info = bundle.revisions[0]
995
 
        self.assertEqual('rev1', revision_info.revision_id)
996
 
        rev = revision_info.as_revision()
997
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
998
 
                          'd':'1'}, rev.properties)
999
 
 
1000
 
    def test_bundle_unicode_properties(self):
1001
 
        """We should be able to round trip a non-ascii property."""
1002
 
        tree = self.make_branch_and_memory_tree('tree')
1003
 
        tree.lock_write()
1004
 
        self.addCleanup(tree.unlock)
1005
 
 
1006
 
        tree.add([''], ['TREE_ROOT'])
1007
 
        # Revisions themselves do not require anything about revision property
1008
 
        # keys, other than that they are a basestring, and do not contain
1009
 
        # whitespace.
1010
 
        # However, Testaments assert than they are str(), and thus should not
1011
 
        # be Unicode.
1012
 
        tree.commit('One', rev_id='rev1',
1013
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
1014
 
        self.b1 = tree.branch
1015
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1016
 
        bundle = read_bundle(bundle_sio)
1017
 
        revision_info = bundle.revisions[0]
1018
 
        self.assertEqual('rev1', revision_info.revision_id)
1019
 
        rev = revision_info.as_revision()
1020
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1021
 
                          'alpha':u'\u03b1'}, rev.properties)
1022
 
 
1023
 
    def test_bundle_with_ghosts(self):
1024
 
        tree = self.make_branch_and_tree('tree')
1025
 
        self.b1 = tree.branch
1026
 
        self.build_tree_contents([('tree/file', 'content1')])
1027
 
        tree.add(['file'])
1028
 
        tree.commit('rev1')
1029
 
        self.build_tree_contents([('tree/file', 'content2')])
1030
 
        tree.add_parent_tree_id('ghost')
1031
 
        tree.commit('rev2', rev_id='rev2')
1032
 
        bundle = self.get_valid_bundle('null:', 'rev2')
1033
 
 
1034
 
    def make_simple_tree(self, format=None):
1035
 
        tree = self.make_branch_and_tree('b1', format=format)
1036
 
        self.b1 = tree.branch
1037
 
        self.build_tree(['b1/file'])
1038
 
        tree.add('file')
1039
 
        return tree
1040
 
 
1041
 
    def test_across_serializers(self):
1042
 
        tree = self.make_simple_tree('knit')
1043
 
        tree.commit('hello', rev_id='rev1')
1044
 
        tree.commit('hello', rev_id='rev2')
1045
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1046
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1047
 
        bundle.install_revisions(repo)
1048
 
        inv_text = repo._get_inventory_xml('rev2')
1049
 
        self.assertNotContainsRe(inv_text, 'format="5"')
1050
 
        self.assertContainsRe(inv_text, 'format="7"')
1051
 
 
1052
 
    def make_repo_with_installed_revisions(self):
1053
 
        tree = self.make_simple_tree('knit')
1054
 
        tree.commit('hello', rev_id='rev1')
1055
 
        tree.commit('hello', rev_id='rev2')
1056
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1057
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1058
 
        bundle.install_revisions(repo)
1059
 
        return repo
1060
 
 
1061
 
    def test_across_models(self):
1062
 
        repo = self.make_repo_with_installed_revisions()
1063
 
        inv = repo.get_inventory('rev2')
1064
 
        self.assertEqual('rev2', inv.root.revision)
1065
 
        root_id = inv.root.file_id
1066
 
        repo.lock_read()
1067
 
        self.addCleanup(repo.unlock)
1068
 
        self.assertEqual({(root_id, 'rev1'):(),
1069
 
            (root_id, 'rev2'):((root_id, 'rev1'),)},
1070
 
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
1071
 
 
1072
 
    def test_inv_hash_across_serializers(self):
1073
 
        repo = self.make_repo_with_installed_revisions()
1074
 
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
1075
 
        xml = repo._get_inventory_xml('rev2')
1076
 
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1077
 
 
1078
 
    def test_across_models_incompatible(self):
1079
 
        tree = self.make_simple_tree('dirstate-with-subtree')
1080
 
        tree.commit('hello', rev_id='rev1')
1081
 
        tree.commit('hello', rev_id='rev2')
1082
 
        try:
1083
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1084
 
        except errors.IncompatibleBundleFormat:
1085
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1086
 
        repo = self.make_repository('repo', format='knit')
1087
 
        bundle.install_revisions(repo)
1088
 
 
1089
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1090
 
        self.assertRaises(errors.IncompatibleRevision,
1091
 
                          bundle.install_revisions, repo)
1092
 
 
1093
 
    def test_get_merge_request(self):
1094
 
        tree = self.make_simple_tree()
1095
 
        tree.commit('hello', rev_id='rev1')
1096
 
        tree.commit('hello', rev_id='rev2')
1097
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1098
 
        result = bundle.get_merge_request(tree.branch.repository)
1099
 
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
1100
 
 
1101
 
    def test_with_subtree(self):
1102
 
        tree = self.make_branch_and_tree('tree',
1103
 
                                         format='dirstate-with-subtree')
1104
 
        self.b1 = tree.branch
1105
 
        subtree = self.make_branch_and_tree('tree/subtree',
1106
 
                                            format='dirstate-with-subtree')
1107
 
        tree.add('subtree')
1108
 
        tree.commit('hello', rev_id='rev1')
1109
 
        try:
1110
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1111
 
        except errors.IncompatibleBundleFormat:
1112
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1113
 
        if isinstance(bundle, v09.BundleInfo09):
1114
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
1115
 
        repo = self.make_repository('repo', format='knit')
1116
 
        self.assertRaises(errors.IncompatibleRevision,
1117
 
                          bundle.install_revisions, repo)
1118
 
        repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
1119
 
        bundle.install_revisions(repo2)
1120
 
 
1121
 
    def test_revision_id_with_slash(self):
1122
 
        self.tree1 = self.make_branch_and_tree('tree')
1123
 
        self.b1 = self.tree1.branch
1124
 
        try:
1125
 
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1126
 
        except ValueError:
1127
 
            raise tests.TestSkipped(
1128
 
                "Repository doesn't support revision ids with slashes")
1129
 
        bundle = self.get_valid_bundle('null:', 'rev/id')
1130
 
 
1131
 
    def test_skip_file(self):
1132
 
        """Make sure we don't accidentally write to the wrong versionedfile"""
1133
 
        self.tree1 = self.make_branch_and_tree('tree')
1134
 
        self.b1 = self.tree1.branch
1135
 
        # rev1 is not present in bundle, done by fetch
1136
 
        self.build_tree_contents([('tree/file2', 'contents1')])
1137
 
        self.tree1.add('file2', 'file2-id')
1138
 
        self.tree1.commit('rev1', rev_id='reva')
1139
 
        self.build_tree_contents([('tree/file3', 'contents2')])
1140
 
        # rev2 is present in bundle, and done by fetch
1141
 
        # having file1 in the bunle causes file1's versionedfile to be opened.
1142
 
        self.tree1.add('file3', 'file3-id')
1143
 
        self.tree1.commit('rev2')
1144
 
        # Updating file2 should not cause an attempt to add to file1's vf
1145
 
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
1146
 
        self.build_tree_contents([('tree/file2', 'contents3')])
1147
 
        self.tree1.commit('rev3', rev_id='rev3')
1148
 
        bundle = self.get_valid_bundle('reva', 'rev3')
1149
 
        if getattr(bundle, 'get_bundle_reader', None) is None:
1150
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
1151
 
        # be sure that file1 comes before file2
1152
 
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1153
 
            if f == 'file3-id':
1154
 
                break
1155
 
            self.assertNotEqual(f, 'file2-id')
1156
 
        bundle.install_revisions(target.branch.repository)
1157
 
 
1158
 
 
1159
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
1160
 
 
1161
 
    format = '0.8'
1162
 
 
1163
 
    def test_bundle_empty_property(self):
1164
 
        """Test serializing revision properties with an empty value."""
1165
 
        tree = self.make_branch_and_memory_tree('tree')
1166
 
        tree.lock_write()
1167
 
        self.addCleanup(tree.unlock)
1168
 
        tree.add([''], ['TREE_ROOT'])
1169
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1170
 
        self.b1 = tree.branch
1171
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
916
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
1172
917
        self.assertContainsRe(bundle_sio.getvalue(),
1173
918
                              '# properties:\n'
1174
919
                              '#   branch-nick: tree\n'
1182
927
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
1183
928
                         rev.properties)
1184
929
 
1185
 
    def get_bundle_tree(self, bundle, revision_id):
1186
 
        repository = self.make_repository('repo')
1187
 
        return bundle.revision_tree(repository, 'revid1')
1188
 
 
1189
930
    def test_bundle_empty_property_alt(self):
1190
931
        """Test serializing revision properties with an empty value.
1191
932
 
1200
941
        tree.add([''], ['TREE_ROOT'])
1201
942
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1202
943
        self.b1 = tree.branch
1203
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
944
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
1204
945
        txt = bundle_sio.getvalue()
1205
946
        loc = txt.find('#   empty: ') + len('#   empty:')
1206
947
        # Create a new bundle, which strips the trailing space after empty
1229
970
        tree.commit('One', rev_id='rev1',
1230
971
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
1231
972
        self.b1 = tree.branch
1232
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
973
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
1233
974
        self.assertContainsRe(bundle_sio.getvalue(),
1234
975
                              '# properties:\n'
1235
976
                              '#   a: 4\n'
1260
1001
        tree.commit('One', rev_id='rev1',
1261
1002
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
1262
1003
        self.b1 = tree.branch
1263
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1004
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
1264
1005
        self.assertContainsRe(bundle_sio.getvalue(),
1265
1006
                              '# properties:\n'
1266
1007
                              '#   alpha: \xce\xb1\n'
1295
1036
        return format
1296
1037
 
1297
1038
 
1298
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
1299
 
 
1300
 
    format = '4'
1301
 
 
1302
 
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
1303
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1304
 
        Make sure that the text generated is valid, and that it
1305
 
        can be applied against the base, and generate the same information.
1306
 
 
1307
 
        :return: The in-memory bundle
1308
 
        """
1309
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1310
 
 
1311
 
        # This should also validate the generated bundle
1312
 
        bundle = read_bundle(bundle_txt)
1313
 
        repository = self.b1.repository
1314
 
        for bundle_rev in bundle.real_revisions:
1315
 
            # These really should have already been checked when we read the
1316
 
            # bundle, since it computes the sha1 hash for the revision, which
1317
 
            # only will match if everything is okay, but lets be explicit about
1318
 
            # it
1319
 
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1320
 
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1321
 
                      'timestamp', 'timezone', 'message', 'committer',
1322
 
                      'parent_ids', 'properties'):
1323
 
                self.assertEqual(getattr(branch_rev, a),
1324
 
                                 getattr(bundle_rev, a))
1325
 
            self.assertEqual(len(branch_rev.parent_ids),
1326
 
                             len(bundle_rev.parent_ids))
1327
 
        self.assertEqual(set(rev_ids),
1328
 
                         set([r.revision_id for r in bundle.real_revisions]))
1329
 
        self.valid_apply_bundle(base_rev_id, bundle,
1330
 
                                   checkout_dir=checkout_dir)
1331
 
 
1332
 
        return bundle
1333
 
 
1334
 
    def get_invalid_bundle(self, base_rev_id, rev_id):
1335
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1336
 
        Munge the text so that it's invalid.
1337
 
 
1338
 
        :return: The in-memory bundle
1339
 
        """
1340
 
        from bzrlib.bundle import serializer
1341
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1342
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1343
 
        new_text = new_text.replace('<file file_id="exe-1"',
1344
 
                                    '<file executable="y" file_id="exe-1"')
1345
 
        new_text = new_text.replace('B260', 'B275')
1346
 
        bundle_txt = StringIO()
1347
 
        bundle_txt.write(serializer._get_bundle_header('4'))
1348
 
        bundle_txt.write('\n')
1349
 
        bundle_txt.write(new_text.encode('bz2'))
1350
 
        bundle_txt.seek(0)
1351
 
        bundle = read_bundle(bundle_txt)
1352
 
        self.valid_apply_bundle(base_rev_id, bundle)
1353
 
        return bundle
1354
 
 
1355
 
    def create_bundle_text(self, base_rev_id, rev_id):
1356
 
        bundle_txt = StringIO()
1357
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1358
 
                               bundle_txt, format=self.format)
1359
 
        bundle_txt.seek(0)
1360
 
        self.assertEqual(bundle_txt.readline(),
1361
 
                         '# Bazaar revision bundle v%s\n' % self.format)
1362
 
        self.assertEqual(bundle_txt.readline(), '#\n')
1363
 
        rev = self.b1.repository.get_revision(rev_id)
1364
 
        bundle_txt.seek(0)
1365
 
        return bundle_txt, rev_ids
1366
 
 
1367
 
    def get_bundle_tree(self, bundle, revision_id):
1368
 
        repository = self.make_repository('repo')
1369
 
        bundle.install_revisions(repository)
1370
 
        return repository.revision_tree(revision_id)
1371
 
 
1372
 
    def test_creation(self):
1373
 
        tree = self.make_branch_and_tree('tree')
1374
 
        self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
1375
 
        tree.add('file', 'fileid-2')
1376
 
        tree.commit('added file', rev_id='rev1')
1377
 
        self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
1378
 
        tree.commit('changed file', rev_id='rev2')
1379
 
        s = StringIO()
1380
 
        serializer = BundleSerializerV4('1.0')
1381
 
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
1382
 
        s.seek(0)
1383
 
        tree2 = self.make_branch_and_tree('target')
1384
 
        target_repo = tree2.branch.repository
1385
 
        install_bundle(target_repo, serializer.read(s))
1386
 
        target_repo.lock_read()
1387
 
        self.addCleanup(target_repo.unlock)
1388
 
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1389
 
        repo_texts = dict((i, ''.join(content)) for i, content
1390
 
                          in target_repo.iter_files_bytes(
1391
 
                                [('fileid-2', 'rev1', '1'),
1392
 
                                 ('fileid-2', 'rev2', '2')]))
1393
 
        self.assertEqual({'1':'contents1\nstatic\n',
1394
 
                          '2':'contents2\nstatic\n'},
1395
 
                         repo_texts)
1396
 
        rtree = target_repo.revision_tree('rev2')
1397
 
        inventory_vf = target_repo.inventories
1398
 
        # If the inventory store has a graph, it must match the revision graph.
1399
 
        self.assertSubset(
1400
 
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
1401
 
            [None, (('rev1',),)])
1402
 
        self.assertEqual('changed file',
1403
 
                         target_repo.get_revision('rev2').message)
1404
 
 
1405
 
    @staticmethod
1406
 
    def get_raw(bundle_file):
1407
 
        bundle_file.seek(0)
1408
 
        line = bundle_file.readline()
1409
 
        line = bundle_file.readline()
1410
 
        lines = bundle_file.readlines()
1411
 
        return ''.join(lines).decode('bz2')
1412
 
 
1413
 
    def test_copy_signatures(self):
1414
 
        tree_a = self.make_branch_and_tree('tree_a')
1415
 
        import bzrlib.gpg
1416
 
        import bzrlib.commit as commit
1417
 
        oldstrategy = bzrlib.gpg.GPGStrategy
1418
 
        branch = tree_a.branch
1419
 
        repo_a = branch.repository
1420
 
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1421
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
1422
 
        try:
1423
 
            from bzrlib.testament import Testament
1424
 
            # monkey patch gpg signing mechanism
1425
 
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1426
 
            new_config = test_commit.MustSignConfig(branch)
1427
 
            commit.Commit(config=new_config).commit(message="base",
1428
 
                                                    allow_pointless=True,
1429
 
                                                    rev_id='B',
1430
 
                                                    working_tree=tree_a)
1431
 
            def sign(text):
1432
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
1433
 
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
1434
 
        finally:
1435
 
            bzrlib.gpg.GPGStrategy = oldstrategy
1436
 
        tree_b = self.make_branch_and_tree('tree_b')
1437
 
        repo_b = tree_b.branch.repository
1438
 
        s = StringIO()
1439
 
        serializer = BundleSerializerV4('4')
1440
 
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
1441
 
        s.seek(0)
1442
 
        install_bundle(repo_b, serializer.read(s))
1443
 
        self.assertTrue(repo_b.has_signature_for_revision_id('B'))
1444
 
        self.assertEqual(repo_b.get_signature_text('B'),
1445
 
                         repo_a.get_signature_text('B'))
1446
 
        s.seek(0)
1447
 
        # ensure repeat installs are harmless
1448
 
        install_bundle(repo_b, serializer.read(s))
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):
 
1039
class MungedBundleTester(TestCaseWithTransport):
1646
1040
 
1647
1041
    def build_test_bundle(self):
1648
1042
        wt = self.make_branch_and_tree('b1')
1657
1051
 
1658
1052
        bundle_txt = StringIO()
1659
1053
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
1660
 
                               'a@cset-0-1', bundle_txt, self.format)
1661
 
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
 
1054
                               'a@cset-0-1', bundle_txt)
 
1055
        self.assertEqual(['a@cset-0-2'], rev_ids)
1662
1056
        bundle_txt.seek(0, 0)
1663
1057
        return bundle_txt
1664
1058
 
1693
1087
        bundle = read_bundle(bundle_txt)
1694
1088
        self.check_valid(bundle)
1695
1089
 
1696
 
 
1697
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
1698
 
 
1699
 
    format = '0.9'
1700
 
 
1701
1090
    def test_missing_trailing_whitespace(self):
1702
1091
        bundle_txt = self.build_test_bundle()
1703
1092
 
1731
1120
        bundle = read_bundle(bundle_txt)
1732
1121
        self.check_valid(bundle)
1733
1122
 
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()