~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: John Arbash Meinel
  • Date: 2007-07-18 20:30:14 UTC
  • mto: This revision was merged to the branch mainline in revision 2643.
  • Revision ID: john@arbash-meinel.com-20070718203014-u8gpbqn5z9ftx1tu
Lot's of fixes from Martin's comments.
Fix signed/unsigned character issues
Add lots of comments to help understand the code
Add tests for proper Unicode handling (we should abort if we get a Unicode string,
and we should correctly handle utf-8 strings)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
24
    errors,
26
25
    inventory,
27
 
    osutils,
28
26
    repository,
29
 
    revision as _mod_revision,
30
27
    treebuilder,
31
28
    )
32
 
from bzrlib.bundle import read_mergeable_from_url
 
29
from bzrlib.builtins import _merge_helper
 
30
from bzrlib.bzrdir import BzrDir
33
31
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
34
32
from bzrlib.bundle.bundle_data import BundleTree
35
 
from bzrlib.bzrdir import BzrDir
36
 
from bzrlib.directory_service import directories
37
 
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
 
33
from bzrlib.bundle.serializer import write_bundle, read_bundle
38
34
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
39
35
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
40
 
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
41
36
from bzrlib.branch import Branch
42
37
from bzrlib.diff import internal_diff
 
38
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
 
39
                           NoSuchFile,)
43
40
from bzrlib.merge import Merge3Merger
44
41
from bzrlib.repofmt import knitrepo
45
 
from bzrlib.osutils import sha_file, sha_string
46
 
from bzrlib.tests import (
47
 
    SymlinkFeature,
48
 
    TestCase,
49
 
    TestCaseInTempDir,
50
 
    TestCaseWithTransport,
51
 
    TestSkipped,
52
 
    test_read_bundle,
53
 
    test_commit,
54
 
    )
 
42
from bzrlib.osutils import has_symlinks, sha_file
 
43
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
 
44
                          TestCase, TestSkipped)
55
45
from bzrlib.transform import TreeTransform
 
46
from bzrlib.workingtree import WorkingTree
56
47
 
57
48
 
58
49
class MockTree(object):
106
97
        elif kind == 'symlink':
107
98
            ie = InventoryLink(file_id, name, parent_id)
108
99
        else:
109
 
            raise errors.BzrError('unknown kind %r' % kind)
 
100
            raise BzrError('unknown kind %r' % kind)
110
101
        ie.text_sha1 = text_sha_1
111
102
        ie.text_size = text_size
112
103
        return ie
114
105
    def add_dir(self, file_id, path):
115
106
        self.paths[file_id] = path
116
107
        self.ids[path] = file_id
117
 
 
 
108
    
118
109
    def add_file(self, file_id, path, contents):
119
110
        self.add_dir(file_id, path)
120
111
        self.contents[file_id] = contents
151
142
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
152
143
        mtree.add_dir("d", "grandparent/alt_parent")
153
144
        return BundleTree(mtree, ''), mtree
154
 
 
 
145
        
155
146
    def test_renames(self):
156
147
        """Ensure that file renames have the proper effect on children"""
157
148
        btree = self.make_tree_1()[0]
158
149
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
159
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
150
        self.assertEqual(btree.old_path("grandparent/parent"), 
160
151
                         "grandparent/parent")
161
152
        self.assertEqual(btree.old_path("grandparent/parent/file"),
162
153
                         "grandparent/parent/file")
169
160
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
170
161
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
171
162
 
172
 
        self.assertTrue(btree.path2id("grandparent2") is None)
173
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
174
 
        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
175
166
 
176
167
        btree.note_rename("grandparent", "grandparent2")
177
 
        self.assertTrue(btree.old_path("grandparent") is None)
178
 
        self.assertTrue(btree.old_path("grandparent/parent") is None)
179
 
        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
180
171
 
181
172
        self.assertEqual(btree.id2path("a"), "grandparent2")
182
173
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
186
177
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
187
178
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
188
179
 
189
 
        self.assertTrue(btree.path2id("grandparent") is None)
190
 
        self.assertTrue(btree.path2id("grandparent/parent") is None)
191
 
        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
192
183
 
193
184
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
194
185
        self.assertEqual(btree.id2path("a"), "grandparent2")
199
190
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
200
191
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
201
192
 
202
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
203
 
        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
204
195
 
205
 
        btree.note_rename("grandparent/parent/file",
 
196
        btree.note_rename("grandparent/parent/file", 
206
197
                          "grandparent2/parent2/file2")
207
198
        self.assertEqual(btree.id2path("a"), "grandparent2")
208
199
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
212
203
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
213
204
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
214
205
 
215
 
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
 
206
        assert btree.path2id("grandparent2/parent2/file") is None
216
207
 
217
208
    def test_moves(self):
218
209
        """Ensure that file moves have the proper effect on children"""
219
210
        btree = self.make_tree_1()[0]
220
 
        btree.note_rename("grandparent/parent/file",
 
211
        btree.note_rename("grandparent/parent/file", 
221
212
                          "grandparent/alt_parent/file")
222
213
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
223
214
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
224
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
215
        assert btree.path2id("grandparent/parent/file") is None
225
216
 
226
217
    def unified_diff(self, old, new):
227
218
        out = StringIO()
231
222
 
232
223
    def make_tree_2(self):
233
224
        btree = self.make_tree_1()[0]
234
 
        btree.note_rename("grandparent/parent/file",
 
225
        btree.note_rename("grandparent/parent/file", 
235
226
                          "grandparent/alt_parent/file")
236
 
        self.assertTrue(btree.id2path("e") is None)
237
 
        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
238
229
        btree.note_id("e", "grandparent/parent/file")
239
230
        return btree
240
231
 
266
257
    def make_tree_3(self):
267
258
        btree, mtree = self.make_tree_1()
268
259
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
269
 
        btree.note_rename("grandparent/parent/file",
 
260
        btree.note_rename("grandparent/parent/file", 
270
261
                          "grandparent/alt_parent/file")
271
 
        btree.note_rename("grandparent/parent/topping",
 
262
        btree.note_rename("grandparent/parent/topping", 
272
263
                          "grandparent/alt_parent/stopping")
273
264
        return btree
274
265
 
298
289
        btree = self.make_tree_1()[0]
299
290
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
300
291
        btree.note_deletion("grandparent/parent/file")
301
 
        self.assertTrue(btree.id2path("c") is None)
302
 
        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
303
294
 
304
295
    def sorted_ids(self, tree):
305
296
        ids = list(tree)
313
304
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
314
305
        btree.note_deletion("grandparent/parent/file")
315
306
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
316
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
307
        btree.note_last_changed("grandparent/alt_parent/fool", 
317
308
                                "revisionidiguess")
318
309
        self.assertEqual(self.sorted_ids(btree),
319
310
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
326
317
        format.repository_format = knitrepo.RepositoryFormatKnit3()
327
318
        serializer = BundleSerializerV08('0.8')
328
319
        b = self.make_branch('.', format=format)
329
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
320
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
330
321
                          b.repository, [], {}, StringIO())
331
322
 
332
323
    def test_matched_bundle(self):
345
336
        source.commit('one', rev_id='one-id')
346
337
        source.commit('two', rev_id='two-id')
347
338
        text = StringIO()
348
 
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
 
339
        write_bundle(source.branch.repository, 'two-id', None, text, 
349
340
                     format='0.9')
350
341
        text.seek(0)
351
342
 
352
343
        format = bzrdir.BzrDirMetaFormat1()
353
344
        format.repository_format = knitrepo.RepositoryFormatKnit1()
354
345
        target = self.make_branch('target', format=format)
355
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
346
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
356
347
                          target.repository, read_bundle(text))
357
348
 
358
349
 
359
 
class BundleTester(object):
 
350
class V08BundleTester(TestCaseWithTransport):
 
351
 
 
352
    format = '0.8'
360
353
 
361
354
    def bzrdir_format(self):
362
355
        format = bzrdir.BzrDirMetaFormat1()
375
368
 
376
369
    def create_bundle_text(self, base_rev_id, rev_id):
377
370
        bundle_txt = StringIO()
378
 
        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, 
379
372
                               bundle_txt, format=self.format)
380
373
        bundle_txt.seek(0)
381
 
        self.assertEqual(bundle_txt.readline(),
 
374
        self.assertEqual(bundle_txt.readline(), 
382
375
                         '# Bazaar revision bundle v%s\n' % self.format)
383
376
        self.assertEqual(bundle_txt.readline(), '#\n')
384
377
 
385
378
        rev = self.b1.repository.get_revision(rev_id)
386
379
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
387
380
                         u'# message:\n')
 
381
 
 
382
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
388
383
        bundle_txt.seek(0)
389
384
        return bundle_txt, rev_ids
390
385
 
392
387
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
393
388
        Make sure that the text generated is valid, and that it
394
389
        can be applied against the base, and generate the same information.
395
 
 
396
 
        :return: The in-memory bundle
 
390
        
 
391
        :return: The in-memory bundle 
397
392
        """
398
393
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
399
394
 
400
 
        # This should also validate the generated bundle
 
395
        # This should also validate the generated bundle 
401
396
        bundle = read_bundle(bundle_txt)
402
397
        repository = self.b1.repository
403
398
        for bundle_rev in bundle.real_revisions:
407
402
            # it
408
403
            branch_rev = repository.get_revision(bundle_rev.revision_id)
409
404
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
410
 
                      'timestamp', 'timezone', 'message', 'committer',
 
405
                      'timestamp', 'timezone', 'message', 'committer', 
411
406
                      'parent_ids', 'properties'):
412
 
                self.assertEqual(getattr(branch_rev, a),
 
407
                self.assertEqual(getattr(branch_rev, a), 
413
408
                                 getattr(bundle_rev, a))
414
 
            self.assertEqual(len(branch_rev.parent_ids),
 
409
            self.assertEqual(len(branch_rev.parent_ids), 
415
410
                             len(bundle_rev.parent_ids))
416
 
        self.assertEqual(rev_ids,
 
411
        self.assertEqual(rev_ids, 
417
412
                         [r.revision_id for r in bundle.real_revisions])
418
413
        self.valid_apply_bundle(base_rev_id, bundle,
419
414
                                   checkout_dir=checkout_dir)
423
418
    def get_invalid_bundle(self, base_rev_id, rev_id):
424
419
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
425
420
        Munge the text so that it's invalid.
426
 
 
 
421
        
427
422
        :return: The in-memory bundle
428
423
        """
429
424
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
430
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
425
        new_text = bundle_txt.getvalue().replace('executable:no', 
431
426
                                               'executable:yes')
432
427
        bundle_txt = StringIO(new_text)
433
428
        bundle = read_bundle(bundle_txt)
434
429
        self.valid_apply_bundle(base_rev_id, bundle)
435
 
        return bundle
 
430
        return bundle 
436
431
 
437
432
    def test_non_bundle(self):
438
 
        self.assertRaises(errors.NotABundle,
439
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
433
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
440
434
 
441
435
    def test_malformed(self):
442
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
436
        self.assertRaises(BadBundle, read_bundle, 
443
437
                          StringIO('# Bazaar revision bundle v'))
444
438
 
445
439
    def test_crlf_bundle(self):
446
440
        try:
447
441
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
448
 
        except errors.BadBundle:
 
442
        except BadBundle:
449
443
            # It is currently permitted for bundles with crlf line endings to
450
444
            # make read_bundle raise a BadBundle, but this should be fixed.
451
445
            # Anything else, especially NotABundle, is an error.
456
450
        """
457
451
 
458
452
        if checkout_dir is None:
459
 
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
 
453
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
460
454
        else:
461
455
            if not os.path.exists(checkout_dir):
462
456
                os.mkdir(checkout_dir)
463
457
        tree = self.make_branch_and_tree(checkout_dir)
464
458
        s = StringIO()
465
 
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
 
459
        ancestors = write_bundle(self.b1.repository, rev_id, None, s,
466
460
                                 format=self.format)
467
461
        s.seek(0)
468
 
        self.assertIsInstance(s.getvalue(), str)
 
462
        assert isinstance(s.getvalue(), str), (
 
463
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
469
464
        install_bundle(tree.branch.repository, read_bundle(s))
470
465
        for ancestor in ancestors:
471
466
            old = self.b1.repository.revision_tree(ancestor)
472
467
            new = tree.branch.repository.revision_tree(ancestor)
473
 
            old.lock_read()
474
 
            new.lock_read()
475
 
            try:
476
 
                # Check that there aren't any inventory level changes
477
 
                delta = new.changes_from(old)
478
 
                self.assertFalse(delta.has_changed(),
479
 
                                 'Revision %s not copied correctly.'
480
 
                                 % (ancestor,))
481
 
 
482
 
                # Now check that the file contents are all correct
483
 
                for inventory_id in old:
484
 
                    try:
485
 
                        old_file = old.get_file(inventory_id)
486
 
                    except errors.NoSuchFile:
487
 
                        continue
488
 
                    if old_file is None:
489
 
                        continue
490
 
                    self.assertEqual(old_file.read(),
491
 
                                     new.get_file(inventory_id).read())
492
 
            finally:
493
 
                new.unlock()
494
 
                old.unlock()
495
 
        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:
496
486
            rh = self.b1.revision_history()
497
487
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
498
488
            tree.update()
506
496
        sure everything matches the builtin branch.
507
497
        """
508
498
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
509
 
        to_tree.lock_write()
510
 
        try:
511
 
            self._valid_apply_bundle(base_rev_id, info, to_tree)
512
 
        finally:
513
 
            to_tree.unlock()
514
 
 
515
 
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
516
499
        original_parents = to_tree.get_parent_ids()
517
500
        repository = to_tree.branch.repository
518
501
        original_parents = to_tree.get_parent_ids()
519
502
        self.assertIs(repository.has_revision(base_rev_id), True)
520
503
        for rev in info.real_revisions:
521
504
            self.assert_(not repository.has_revision(rev.revision_id),
522
 
                'Revision {%s} present before applying bundle'
 
505
                'Revision {%s} present before applying bundle' 
523
506
                % rev.revision_id)
524
507
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
525
508
 
526
509
        for rev in info.real_revisions:
527
510
            self.assert_(repository.has_revision(rev.revision_id),
528
 
                'Missing revision {%s} after applying bundle'
 
511
                'Missing revision {%s} after applying bundle' 
529
512
                % rev.revision_id)
530
513
 
531
514
        self.assert_(to_tree.branch.repository.has_revision(info.target))
537
520
        rev = info.real_revisions[-1]
538
521
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
539
522
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
540
 
 
 
523
        
541
524
        # TODO: make sure the target tree is identical to base tree
542
525
        #       we might also check the working tree.
543
526
 
566
549
        self.tree1.add('one')
567
550
        self.tree1.commit('add one', rev_id='a@cset-0-1')
568
551
 
569
 
        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')
570
559
 
571
560
        # Make sure we can handle files with spaces, tabs, other
572
561
        # bogus characters
585
574
        tt = TreeTransform(self.tree1)
586
575
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
587
576
        tt.apply()
588
 
        # have to fix length of file-id so that we can predictably rewrite
589
 
        # a (length-prefixed) record containing it later.
590
 
        self.tree1.add('with space.txt', 'withspace-id')
591
577
        self.tree1.add([
592
 
                  'dir'
 
578
                'with space.txt'
 
579
                , 'dir'
593
580
                , 'dir/filein subdir.c'
594
581
                , 'dir/WithCaps.txt'
595
582
                , 'dir/ pre space'
603
590
 
604
591
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
605
592
 
606
 
        # Check a rollup bundle
607
 
        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')
608
595
 
609
596
        # Now delete entries
610
597
        self.tree1.remove(
617
604
        tt.set_executability(False, trans_id)
618
605
        tt.apply()
619
606
        self.tree1.commit('removed', rev_id='a@cset-0-3')
620
 
 
 
607
        
621
608
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
622
 
        self.assertRaises((errors.TestamentMismatch,
623
 
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
624
 
            'a@cset-0-2', 'a@cset-0-3')
625
 
        # Check a rollup bundle
626
 
        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')
627
613
 
628
614
        # Now move the directory
629
615
        self.tree1.rename_one('dir', 'sub/dir')
630
616
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
631
617
 
632
618
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
633
 
        # Check a rollup bundle
634
 
        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')
635
621
 
636
622
        # Modified files
637
623
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
638
 
        open('b1/sub/dir/ pre space', 'ab').write(
639
 
             '\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')
640
625
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
641
 
        self.tree1.rename_one('sub/dir/ pre space',
 
626
        self.tree1.rename_one('sub/dir/ pre space', 
642
627
                              'sub/ start space')
643
628
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
644
629
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
656
641
        self.assertEqualDiff(tree1_inv, tree2_inv)
657
642
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
658
643
        other.commit('rename file', rev_id='a@cset-0-6b')
659
 
        self.tree1.merge_from_branch(other.branch)
 
644
        _merge_helper([other.basedir, -1], [None, None],
 
645
                      this_dir=self.tree1.basedir)
660
646
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
661
647
                          verbose=False)
662
648
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
663
649
 
664
650
    def test_symlink_bundle(self):
665
 
        self.requireFeature(SymlinkFeature)
 
651
        if not has_symlinks():
 
652
            raise TestSkipped("No symlink support")
666
653
        self.tree1 = self.make_branch_and_tree('b1')
667
654
        self.b1 = self.tree1.branch
668
655
        tt = TreeTransform(self.tree1)
669
656
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
670
657
        tt.apply()
671
658
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
672
 
        self.get_valid_bundle('null:', 'l@cset-0-1')
 
659
        self.get_valid_bundle(None, 'l@cset-0-1')
673
660
        tt = TreeTransform(self.tree1)
674
661
        trans_id = tt.trans_id_tree_file_id('link-1')
675
662
        tt.adjust_path('link2', tt.root, trans_id)
696
683
        self.tree1 = self.make_branch_and_tree('b1')
697
684
        self.b1 = self.tree1.branch
698
685
        tt = TreeTransform(self.tree1)
699
 
 
 
686
        
700
687
        # Add
701
688
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
702
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
703
 
            'binary-2')
 
689
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
704
690
        tt.apply()
705
691
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
706
 
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
692
        self.get_valid_bundle(None, 'b@cset-0-1')
707
693
 
708
694
        # Delete
709
695
        tt = TreeTransform(self.tree1)
733
719
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
734
720
 
735
721
        # Rollup
736
 
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
722
        self.get_valid_bundle(None, 'b@cset-0-4')
737
723
 
738
724
    def test_last_modified(self):
739
725
        self.tree1 = self.make_branch_and_tree('b1')
757
743
        tt.create_file('file2', trans_id)
758
744
        tt.apply()
759
745
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
760
 
        self.tree1.merge_from_branch(other.branch)
 
746
        _merge_helper([other.basedir, -1], [None, None],
 
747
                      this_dir=self.tree1.basedir)
761
748
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
762
749
                          verbose=False)
763
750
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
778
765
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
779
766
                               'a@cset-0-1', bundle_file, format=self.format)
780
767
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
781
 
        self.assertContainsRe(self.get_raw(bundle_file), 'one')
782
 
        self.assertContainsRe(self.get_raw(bundle_file), 'three')
783
 
 
784
 
    def test_bundle_same_basis(self):
785
 
        """Ensure using the basis as the target doesn't cause an error"""
786
 
        self.tree1 = self.make_branch_and_tree('b1')
787
 
        self.tree1.commit('add file', rev_id='a@cset-0-1')
788
 
        bundle_file = StringIO()
789
 
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-1',
790
 
                               'a@cset-0-1', bundle_file)
791
 
 
792
 
    @staticmethod
793
 
    def get_raw(bundle_file):
794
 
        return bundle_file.getvalue()
 
768
        self.assertContainsRe(bundle_file.getvalue(), 'one')
 
769
        self.assertContainsRe(bundle_file.getvalue(), 'three')
795
770
 
796
771
    def test_unicode_bundle(self):
797
772
        # Handle international characters
798
773
        os.mkdir('b1')
799
774
        try:
800
 
            f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
775
            f = open(u'b1/with Dod\xe9', 'wb')
801
776
        except UnicodeEncodeError:
802
777
            raise TestSkipped("Filesystem doesn't support unicode")
803
778
 
809
784
            u'William Dod\xe9\n').encode('utf-8'))
810
785
        f.close()
811
786
 
812
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
787
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
813
788
        self.tree1.commit(u'i18n commit from William Dod\xe9',
814
789
                          rev_id='i18n-1', committer=u'William Dod\xe9')
815
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
 
816
801
        # Add
817
 
        bundle = self.get_valid_bundle('null:', 'i18n-1')
 
802
        bundle = self.get_valid_bundle(None, 'i18n-1')
818
803
 
819
804
        # Modified
820
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
805
        f = open(u'b1/with Dod\xe9', 'wb')
821
806
        f.write(u'Modified \xb5\n'.encode('utf8'))
822
807
        f.close()
823
808
        self.tree1.commit(u'modified', rev_id='i18n-2')
824
809
 
825
810
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
826
 
 
 
811
        
827
812
        # Renamed
828
 
        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')
829
814
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
830
815
                          committer=u'Erik B\xe5gfors')
831
816
 
832
817
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
833
818
 
834
819
        # Removed
835
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
820
        self.tree1.remove([u'B\xe5gfors'])
836
821
        self.tree1.commit(u'removed', rev_id='i18n-4')
837
822
 
838
823
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
839
824
 
840
825
        # Rollup
841
 
        bundle = self.get_valid_bundle('null:', 'i18n-4')
 
826
        bundle = self.get_valid_bundle(None, 'i18n-4')
842
827
 
843
828
 
844
829
    def test_whitespace_bundle(self):
856
841
        # Added
857
842
        self.tree1.commit('funky whitespace', rev_id='white-1')
858
843
 
859
 
        bundle = self.get_valid_bundle('null:', 'white-1')
 
844
        bundle = self.get_valid_bundle(None, 'white-1')
860
845
 
861
846
        # Modified
862
847
        open('b1/trailing space ', 'ab').write('add some text\n')
875
860
        self.tree1.commit('removed', rev_id='white-4')
876
861
 
877
862
        bundle = self.get_valid_bundle('white-3', 'white-4')
878
 
 
 
863
        
879
864
        # Now test a complet roll-up
880
 
        bundle = self.get_valid_bundle('null:', 'white-4')
 
865
        bundle = self.get_valid_bundle(None, 'white-4')
881
866
 
882
867
    def test_alt_timezone_bundle(self):
883
868
        self.tree1 = self.make_branch_and_memory_tree('b1')
893
878
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
894
879
                          timezone=19800, timestamp=1152544886.0)
895
880
 
896
 
        bundle = self.get_valid_bundle('null:', 'tz-1')
897
 
 
 
881
        bundle = self.get_valid_bundle(None, 'tz-1')
 
882
        
898
883
        rev = bundle.revisions[0]
899
884
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
900
885
        self.assertEqual(19800, rev.timezone)
905
890
        self.tree1 = self.make_branch_and_tree('b1')
906
891
        self.b1 = self.tree1.branch
907
892
        self.tree1.commit('message', rev_id='revid1')
908
 
        bundle = self.get_valid_bundle('null:', 'revid1')
909
 
        tree = self.get_bundle_tree(bundle, 'revid1')
 
893
        bundle = self.get_valid_bundle(None, 'revid1')
 
894
        tree = bundle.revision_tree(self.b1.repository, 'revid1')
910
895
        self.assertEqual('revid1', tree.inventory.root.revision)
911
896
 
912
897
    def test_install_revisions(self):
913
898
        self.tree1 = self.make_branch_and_tree('b1')
914
899
        self.b1 = self.tree1.branch
915
900
        self.tree1.commit('message', rev_id='rev2a')
916
 
        bundle = self.get_valid_bundle('null:', 'rev2a')
 
901
        bundle = self.get_valid_bundle(None, 'rev2a')
917
902
        branch2 = self.make_branch('b2')
918
903
        self.assertFalse(branch2.repository.has_revision('rev2a'))
919
904
        target_revision = bundle.install_revisions(branch2.repository)
928
913
        tree.add([''], ['TREE_ROOT'])
929
914
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
930
915
        self.b1 = tree.branch
931
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
932
 
        bundle = read_bundle(bundle_sio)
933
 
        revision_info = bundle.revisions[0]
934
 
        self.assertEqual('rev1', revision_info.revision_id)
935
 
        rev = revision_info.as_revision()
936
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
937
 
                         rev.properties)
938
 
 
939
 
    def test_bundle_sorted_properties(self):
940
 
        """For stability the writer should write properties in sorted order."""
941
 
        tree = self.make_branch_and_memory_tree('tree')
942
 
        tree.lock_write()
943
 
        self.addCleanup(tree.unlock)
944
 
 
945
 
        tree.add([''], ['TREE_ROOT'])
946
 
        tree.commit('One', rev_id='rev1',
947
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
948
 
        self.b1 = tree.branch
949
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
950
 
        bundle = read_bundle(bundle_sio)
951
 
        revision_info = bundle.revisions[0]
952
 
        self.assertEqual('rev1', revision_info.revision_id)
953
 
        rev = revision_info.as_revision()
954
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
955
 
                          'd':'1'}, rev.properties)
956
 
 
957
 
    def test_bundle_unicode_properties(self):
958
 
        """We should be able to round trip a non-ascii property."""
959
 
        tree = self.make_branch_and_memory_tree('tree')
960
 
        tree.lock_write()
961
 
        self.addCleanup(tree.unlock)
962
 
 
963
 
        tree.add([''], ['TREE_ROOT'])
964
 
        # Revisions themselves do not require anything about revision property
965
 
        # keys, other than that they are a basestring, and do not contain
966
 
        # whitespace.
967
 
        # However, Testaments assert than they are str(), and thus should not
968
 
        # be Unicode.
969
 
        tree.commit('One', rev_id='rev1',
970
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
971
 
        self.b1 = tree.branch
972
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
973
 
        bundle = read_bundle(bundle_sio)
974
 
        revision_info = bundle.revisions[0]
975
 
        self.assertEqual('rev1', revision_info.revision_id)
976
 
        rev = revision_info.as_revision()
977
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
978
 
                          'alpha':u'\u03b1'}, rev.properties)
979
 
 
980
 
    def test_bundle_with_ghosts(self):
981
 
        tree = self.make_branch_and_tree('tree')
982
 
        self.b1 = tree.branch
983
 
        self.build_tree_contents([('tree/file', 'content1')])
984
 
        tree.add(['file'])
985
 
        tree.commit('rev1')
986
 
        self.build_tree_contents([('tree/file', 'content2')])
987
 
        tree.add_parent_tree_id('ghost')
988
 
        tree.commit('rev2', rev_id='rev2')
989
 
        bundle = self.get_valid_bundle('null:', 'rev2')
990
 
 
991
 
    def make_simple_tree(self, format=None):
992
 
        tree = self.make_branch_and_tree('b1', format=format)
993
 
        self.b1 = tree.branch
994
 
        self.build_tree(['b1/file'])
995
 
        tree.add('file')
996
 
        return tree
997
 
 
998
 
    def test_across_serializers(self):
999
 
        tree = self.make_simple_tree('knit')
1000
 
        tree.commit('hello', rev_id='rev1')
1001
 
        tree.commit('hello', rev_id='rev2')
1002
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1003
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1004
 
        bundle.install_revisions(repo)
1005
 
        inv_text = repo.get_inventory_xml('rev2')
1006
 
        self.assertNotContainsRe(inv_text, 'format="5"')
1007
 
        self.assertContainsRe(inv_text, 'format="7"')
1008
 
 
1009
 
    def make_repo_with_installed_revisions(self):
1010
 
        tree = self.make_simple_tree('knit')
1011
 
        tree.commit('hello', rev_id='rev1')
1012
 
        tree.commit('hello', rev_id='rev2')
1013
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1014
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1015
 
        bundle.install_revisions(repo)
1016
 
        return repo
1017
 
 
1018
 
    def test_across_models(self):
1019
 
        repo = self.make_repo_with_installed_revisions()
1020
 
        inv = repo.get_inventory('rev2')
1021
 
        self.assertEqual('rev2', inv.root.revision)
1022
 
        root_id = inv.root.file_id
1023
 
        repo.lock_read()
1024
 
        self.addCleanup(repo.unlock)
1025
 
        self.assertEqual({(root_id, 'rev1'):(),
1026
 
            (root_id, 'rev2'):((root_id, 'rev1'),)},
1027
 
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
1028
 
 
1029
 
    def test_inv_hash_across_serializers(self):
1030
 
        repo = self.make_repo_with_installed_revisions()
1031
 
        recorded_inv_sha1 = repo.get_inventory_sha1('rev2')
1032
 
        xml = repo.get_inventory_xml('rev2')
1033
 
        self.assertEqual(sha_string(xml), recorded_inv_sha1)
1034
 
 
1035
 
    def test_across_models_incompatible(self):
1036
 
        tree = self.make_simple_tree('dirstate-with-subtree')
1037
 
        tree.commit('hello', rev_id='rev1')
1038
 
        tree.commit('hello', rev_id='rev2')
1039
 
        try:
1040
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1041
 
        except errors.IncompatibleBundleFormat:
1042
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1043
 
        repo = self.make_repository('repo', format='knit')
1044
 
        bundle.install_revisions(repo)
1045
 
 
1046
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1047
 
        self.assertRaises(errors.IncompatibleRevision,
1048
 
                          bundle.install_revisions, repo)
1049
 
 
1050
 
    def test_get_merge_request(self):
1051
 
        tree = self.make_simple_tree()
1052
 
        tree.commit('hello', rev_id='rev1')
1053
 
        tree.commit('hello', rev_id='rev2')
1054
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1055
 
        result = bundle.get_merge_request(tree.branch.repository)
1056
 
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
1057
 
 
1058
 
    def test_with_subtree(self):
1059
 
        tree = self.make_branch_and_tree('tree',
1060
 
                                         format='dirstate-with-subtree')
1061
 
        self.b1 = tree.branch
1062
 
        subtree = self.make_branch_and_tree('tree/subtree',
1063
 
                                            format='dirstate-with-subtree')
1064
 
        tree.add('subtree')
1065
 
        tree.commit('hello', rev_id='rev1')
1066
 
        try:
1067
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1068
 
        except errors.IncompatibleBundleFormat:
1069
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1070
 
        if isinstance(bundle, v09.BundleInfo09):
1071
 
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
1072
 
        repo = self.make_repository('repo', format='knit')
1073
 
        self.assertRaises(errors.IncompatibleRevision,
1074
 
                          bundle.install_revisions, repo)
1075
 
        repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
1076
 
        bundle.install_revisions(repo2)
1077
 
 
1078
 
    def test_revision_id_with_slash(self):
1079
 
        self.tree1 = self.make_branch_and_tree('tree')
1080
 
        self.b1 = self.tree1.branch
1081
 
        try:
1082
 
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1083
 
        except ValueError:
1084
 
            raise TestSkipped("Repository doesn't support revision ids with"
1085
 
                              " slashes")
1086
 
        bundle = self.get_valid_bundle('null:', 'rev/id')
1087
 
 
1088
 
    def test_skip_file(self):
1089
 
        """Make sure we don't accidentally write to the wrong versionedfile"""
1090
 
        self.tree1 = self.make_branch_and_tree('tree')
1091
 
        self.b1 = self.tree1.branch
1092
 
        # rev1 is not present in bundle, done by fetch
1093
 
        self.build_tree_contents([('tree/file2', 'contents1')])
1094
 
        self.tree1.add('file2', 'file2-id')
1095
 
        self.tree1.commit('rev1', rev_id='reva')
1096
 
        self.build_tree_contents([('tree/file3', 'contents2')])
1097
 
        # rev2 is present in bundle, and done by fetch
1098
 
        # having file1 in the bunle causes file1's versionedfile to be opened.
1099
 
        self.tree1.add('file3', 'file3-id')
1100
 
        self.tree1.commit('rev2')
1101
 
        # Updating file2 should not cause an attempt to add to file1's vf
1102
 
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
1103
 
        self.build_tree_contents([('tree/file2', 'contents3')])
1104
 
        self.tree1.commit('rev3', rev_id='rev3')
1105
 
        bundle = self.get_valid_bundle('reva', 'rev3')
1106
 
        if getattr(bundle, 'get_bundle_reader', None) is None:
1107
 
            raise TestSkipped('Bundle format cannot provide reader')
1108
 
        # be sure that file1 comes before file2
1109
 
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1110
 
            if f == 'file3-id':
1111
 
                break
1112
 
            self.assertNotEqual(f, 'file2-id')
1113
 
        bundle.install_revisions(target.branch.repository)
1114
 
 
1115
 
 
1116
 
class V08BundleTester(BundleTester, TestCaseWithTransport):
1117
 
 
1118
 
    format = '0.8'
1119
 
 
1120
 
    def test_bundle_empty_property(self):
1121
 
        """Test serializing revision properties with an empty value."""
1122
 
        tree = self.make_branch_and_memory_tree('tree')
1123
 
        tree.lock_write()
1124
 
        self.addCleanup(tree.unlock)
1125
 
        tree.add([''], ['TREE_ROOT'])
1126
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1127
 
        self.b1 = tree.branch
1128
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
916
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
1129
917
        self.assertContainsRe(bundle_sio.getvalue(),
1130
918
                              '# properties:\n'
1131
919
                              '#   branch-nick: tree\n'
1139
927
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
1140
928
                         rev.properties)
1141
929
 
1142
 
    def get_bundle_tree(self, bundle, revision_id):
1143
 
        repository = self.make_repository('repo')
1144
 
        return bundle.revision_tree(repository, 'revid1')
1145
 
 
1146
930
    def test_bundle_empty_property_alt(self):
1147
931
        """Test serializing revision properties with an empty value.
1148
932
 
1157
941
        tree.add([''], ['TREE_ROOT'])
1158
942
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1159
943
        self.b1 = tree.branch
1160
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
944
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
1161
945
        txt = bundle_sio.getvalue()
1162
946
        loc = txt.find('#   empty: ') + len('#   empty:')
1163
947
        # Create a new bundle, which strips the trailing space after empty
1186
970
        tree.commit('One', rev_id='rev1',
1187
971
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
1188
972
        self.b1 = tree.branch
1189
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
973
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
1190
974
        self.assertContainsRe(bundle_sio.getvalue(),
1191
975
                              '# properties:\n'
1192
976
                              '#   a: 4\n'
1217
1001
        tree.commit('One', rev_id='rev1',
1218
1002
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
1219
1003
        self.b1 = tree.branch
1220
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1004
        bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
1221
1005
        self.assertContainsRe(bundle_sio.getvalue(),
1222
1006
                              '# properties:\n'
1223
1007
                              '#   alpha: \xce\xb1\n'
1252
1036
        return format
1253
1037
 
1254
1038
 
1255
 
class V4BundleTester(BundleTester, TestCaseWithTransport):
1256
 
 
1257
 
    format = '4'
1258
 
 
1259
 
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
1260
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1261
 
        Make sure that the text generated is valid, and that it
1262
 
        can be applied against the base, and generate the same information.
1263
 
 
1264
 
        :return: The in-memory bundle
1265
 
        """
1266
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1267
 
 
1268
 
        # This should also validate the generated bundle
1269
 
        bundle = read_bundle(bundle_txt)
1270
 
        repository = self.b1.repository
1271
 
        for bundle_rev in bundle.real_revisions:
1272
 
            # These really should have already been checked when we read the
1273
 
            # bundle, since it computes the sha1 hash for the revision, which
1274
 
            # only will match if everything is okay, but lets be explicit about
1275
 
            # it
1276
 
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1277
 
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1278
 
                      'timestamp', 'timezone', 'message', 'committer',
1279
 
                      'parent_ids', 'properties'):
1280
 
                self.assertEqual(getattr(branch_rev, a),
1281
 
                                 getattr(bundle_rev, a))
1282
 
            self.assertEqual(len(branch_rev.parent_ids),
1283
 
                             len(bundle_rev.parent_ids))
1284
 
        self.assertEqual(set(rev_ids),
1285
 
                         set([r.revision_id for r in bundle.real_revisions]))
1286
 
        self.valid_apply_bundle(base_rev_id, bundle,
1287
 
                                   checkout_dir=checkout_dir)
1288
 
 
1289
 
        return bundle
1290
 
 
1291
 
    def get_invalid_bundle(self, base_rev_id, rev_id):
1292
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1293
 
        Munge the text so that it's invalid.
1294
 
 
1295
 
        :return: The in-memory bundle
1296
 
        """
1297
 
        from bzrlib.bundle import serializer
1298
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1299
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1300
 
        new_text = new_text.replace('<file file_id="exe-1"',
1301
 
                                    '<file executable="y" file_id="exe-1"')
1302
 
        new_text = new_text.replace('B222', 'B237')
1303
 
        bundle_txt = StringIO()
1304
 
        bundle_txt.write(serializer._get_bundle_header('4'))
1305
 
        bundle_txt.write('\n')
1306
 
        bundle_txt.write(new_text.encode('bz2'))
1307
 
        bundle_txt.seek(0)
1308
 
        bundle = read_bundle(bundle_txt)
1309
 
        self.valid_apply_bundle(base_rev_id, bundle)
1310
 
        return bundle
1311
 
 
1312
 
    def create_bundle_text(self, base_rev_id, rev_id):
1313
 
        bundle_txt = StringIO()
1314
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1315
 
                               bundle_txt, format=self.format)
1316
 
        bundle_txt.seek(0)
1317
 
        self.assertEqual(bundle_txt.readline(),
1318
 
                         '# Bazaar revision bundle v%s\n' % self.format)
1319
 
        self.assertEqual(bundle_txt.readline(), '#\n')
1320
 
        rev = self.b1.repository.get_revision(rev_id)
1321
 
        bundle_txt.seek(0)
1322
 
        return bundle_txt, rev_ids
1323
 
 
1324
 
    def get_bundle_tree(self, bundle, revision_id):
1325
 
        repository = self.make_repository('repo')
1326
 
        bundle.install_revisions(repository)
1327
 
        return repository.revision_tree(revision_id)
1328
 
 
1329
 
    def test_creation(self):
1330
 
        tree = self.make_branch_and_tree('tree')
1331
 
        self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
1332
 
        tree.add('file', 'fileid-2')
1333
 
        tree.commit('added file', rev_id='rev1')
1334
 
        self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
1335
 
        tree.commit('changed file', rev_id='rev2')
1336
 
        s = StringIO()
1337
 
        serializer = BundleSerializerV4('1.0')
1338
 
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
1339
 
        s.seek(0)
1340
 
        tree2 = self.make_branch_and_tree('target')
1341
 
        target_repo = tree2.branch.repository
1342
 
        install_bundle(target_repo, serializer.read(s))
1343
 
        target_repo.lock_read()
1344
 
        self.addCleanup(target_repo.unlock)
1345
 
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1346
 
        repo_texts = dict((i, ''.join(content)) for i, content
1347
 
                          in target_repo.iter_files_bytes(
1348
 
                                [('fileid-2', 'rev1', '1'),
1349
 
                                 ('fileid-2', 'rev2', '2')]))
1350
 
        self.assertEqual({'1':'contents1\nstatic\n',
1351
 
                          '2':'contents2\nstatic\n'},
1352
 
                         repo_texts)
1353
 
        rtree = target_repo.revision_tree('rev2')
1354
 
        inventory_vf = target_repo.inventories
1355
 
        # If the inventory store has a graph, it must match the revision graph.
1356
 
        self.assertSubset(
1357
 
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
1358
 
            [None, (('rev1',),)])
1359
 
        self.assertEqual('changed file',
1360
 
                         target_repo.get_revision('rev2').message)
1361
 
 
1362
 
    @staticmethod
1363
 
    def get_raw(bundle_file):
1364
 
        bundle_file.seek(0)
1365
 
        line = bundle_file.readline()
1366
 
        line = bundle_file.readline()
1367
 
        lines = bundle_file.readlines()
1368
 
        return ''.join(lines).decode('bz2')
1369
 
 
1370
 
    def test_copy_signatures(self):
1371
 
        tree_a = self.make_branch_and_tree('tree_a')
1372
 
        import bzrlib.gpg
1373
 
        import bzrlib.commit as commit
1374
 
        oldstrategy = bzrlib.gpg.GPGStrategy
1375
 
        branch = tree_a.branch
1376
 
        repo_a = branch.repository
1377
 
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1378
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
1379
 
        try:
1380
 
            from bzrlib.testament import Testament
1381
 
            # monkey patch gpg signing mechanism
1382
 
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1383
 
            new_config = test_commit.MustSignConfig(branch)
1384
 
            commit.Commit(config=new_config).commit(message="base",
1385
 
                                                    allow_pointless=True,
1386
 
                                                    rev_id='B',
1387
 
                                                    working_tree=tree_a)
1388
 
            def sign(text):
1389
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
1390
 
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
1391
 
        finally:
1392
 
            bzrlib.gpg.GPGStrategy = oldstrategy
1393
 
        tree_b = self.make_branch_and_tree('tree_b')
1394
 
        repo_b = tree_b.branch.repository
1395
 
        s = StringIO()
1396
 
        serializer = BundleSerializerV4('4')
1397
 
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
1398
 
        s.seek(0)
1399
 
        install_bundle(repo_b, serializer.read(s))
1400
 
        self.assertTrue(repo_b.has_signature_for_revision_id('B'))
1401
 
        self.assertEqual(repo_b.get_signature_text('B'),
1402
 
                         repo_a.get_signature_text('B'))
1403
 
        s.seek(0)
1404
 
        # ensure repeat installs are harmless
1405
 
        install_bundle(repo_b, serializer.read(s))
1406
 
 
1407
 
 
1408
 
class V4WeaveBundleTester(V4BundleTester):
1409
 
 
1410
 
    def bzrdir_format(self):
1411
 
        return 'metaweave'
1412
 
 
1413
 
 
1414
 
class MungedBundleTester(object):
 
1039
class MungedBundleTester(TestCaseWithTransport):
1415
1040
 
1416
1041
    def build_test_bundle(self):
1417
1042
        wt = self.make_branch_and_tree('b1')
1426
1051
 
1427
1052
        bundle_txt = StringIO()
1428
1053
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
1429
 
                               'a@cset-0-1', bundle_txt, self.format)
1430
 
        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)
1431
1056
        bundle_txt.seek(0, 0)
1432
1057
        return bundle_txt
1433
1058
 
1462
1087
        bundle = read_bundle(bundle_txt)
1463
1088
        self.check_valid(bundle)
1464
1089
 
1465
 
 
1466
 
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
1467
 
 
1468
 
    format = '0.9'
1469
 
 
1470
1090
    def test_missing_trailing_whitespace(self):
1471
1091
        bundle_txt = self.build_test_bundle()
1472
1092
 
1500
1120
        bundle = read_bundle(bundle_txt)
1501
1121
        self.check_valid(bundle)
1502
1122
 
1503
 
 
1504
 
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
1505
 
 
1506
 
    format = '4'
1507
 
 
1508
 
 
1509
 
class TestBundleWriterReader(TestCase):
1510
 
 
1511
 
    def test_roundtrip_record(self):
1512
 
        fileobj = StringIO()
1513
 
        writer = v4.BundleWriter(fileobj)
1514
 
        writer.begin()
1515
 
        writer.add_info_record(foo='bar')
1516
 
        writer._add_record("Record body", {'parents': ['1', '3'],
1517
 
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1518
 
        writer.end()
1519
 
        fileobj.seek(0)
1520
 
        reader = v4.BundleReader(fileobj, stream_input=True)
1521
 
        record_iter = reader.iter_records()
1522
 
        record = record_iter.next()
1523
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1524
 
            'info', None, None), record)
1525
 
        record = record_iter.next()
1526
 
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1527
 
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1528
 
                          record)
1529
 
 
1530
 
    def test_roundtrip_record_memory_hungry(self):
1531
 
        fileobj = StringIO()
1532
 
        writer = v4.BundleWriter(fileobj)
1533
 
        writer.begin()
1534
 
        writer.add_info_record(foo='bar')
1535
 
        writer._add_record("Record body", {'parents': ['1', '3'],
1536
 
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1537
 
        writer.end()
1538
 
        fileobj.seek(0)
1539
 
        reader = v4.BundleReader(fileobj, stream_input=False)
1540
 
        record_iter = reader.iter_records()
1541
 
        record = record_iter.next()
1542
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1543
 
            'info', None, None), record)
1544
 
        record = record_iter.next()
1545
 
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1546
 
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1547
 
                          record)
1548
 
 
1549
 
    def test_encode_name(self):
1550
 
        self.assertEqual('revision/rev1',
1551
 
            v4.BundleWriter.encode_name('revision', 'rev1'))
1552
 
        self.assertEqual('file/rev//1/file-id-1',
1553
 
            v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
1554
 
        self.assertEqual('info',
1555
 
            v4.BundleWriter.encode_name('info', None, None))
1556
 
 
1557
 
    def test_decode_name(self):
1558
 
        self.assertEqual(('revision', 'rev1', None),
1559
 
            v4.BundleReader.decode_name('revision/rev1'))
1560
 
        self.assertEqual(('file', 'rev/1', 'file-id-1'),
1561
 
            v4.BundleReader.decode_name('file/rev//1/file-id-1'))
1562
 
        self.assertEqual(('info', None, None),
1563
 
                         v4.BundleReader.decode_name('info'))
1564
 
 
1565
 
    def test_too_many_names(self):
1566
 
        fileobj = StringIO()
1567
 
        writer = v4.BundleWriter(fileobj)
1568
 
        writer.begin()
1569
 
        writer.add_info_record(foo='bar')
1570
 
        writer._container.add_bytes_record('blah', ['two', 'names'])
1571
 
        writer.end()
1572
 
        fileobj.seek(0)
1573
 
        record_iter = v4.BundleReader(fileobj).iter_records()
1574
 
        record = record_iter.next()
1575
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1576
 
            'info', None, None), record)
1577
 
        self.assertRaises(errors.BadBundle, record_iter.next)
1578
 
 
1579
 
 
1580
 
class TestReadMergeableFromUrl(TestCaseWithTransport):
1581
 
 
1582
 
    def test_read_mergeable_skips_local(self):
1583
 
        """A local bundle named like the URL should not be read.
1584
 
        """
1585
 
        out, wt = test_read_bundle.create_bundle_file(self)
1586
 
        class FooService(object):
1587
 
            """A directory service that always returns source"""
1588
 
 
1589
 
            def look_up(self, name, url):
1590
 
                return 'source'
1591
 
        directories.register('foo:', FooService, 'Testing directory service')
1592
 
        self.addCleanup(lambda: directories.remove('foo:'))
1593
 
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1594
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1595
 
                          'foo:bar')
1596
 
 
1597
 
    def test_smart_server_connection_reset(self):
1598
 
        """If a smart server connection fails during the attempt to read a
1599
 
        bundle, then the ConnectionReset error should be propagated.
1600
 
        """
1601
 
        # Instantiate a server that will provoke a ConnectionReset
1602
 
        sock_server = _DisconnectingTCPServer()
1603
 
        sock_server.setUp()
1604
 
        self.addCleanup(sock_server.tearDown)
1605
 
        # We don't really care what the url is since the server will close the
1606
 
        # connection without interpreting it
1607
 
        url = sock_server.get_url()
1608
 
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1609
 
 
1610
 
 
1611
 
class _DisconnectingTCPServer(object):
1612
 
    """A TCP server that immediately closes any connection made to it."""
1613
 
 
1614
 
    def setUp(self):
1615
 
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1616
 
        self.sock.bind(('127.0.0.1', 0))
1617
 
        self.sock.listen(1)
1618
 
        self.port = self.sock.getsockname()[1]
1619
 
        self.thread = threading.Thread(
1620
 
            name='%s (port %d)' % (self.__class__.__name__, self.port),
1621
 
            target=self.accept_and_close)
1622
 
        self.thread.start()
1623
 
 
1624
 
    def accept_and_close(self):
1625
 
        conn, addr = self.sock.accept()
1626
 
        conn.shutdown(socket.SHUT_RDWR)
1627
 
        conn.close()
1628
 
 
1629
 
    def get_url(self):
1630
 
        return 'bzr://127.0.0.1:%d/' % (self.port,)
1631
 
 
1632
 
    def tearDown(self):
1633
 
        try:
1634
 
            # make sure the thread dies by connecting to the listening socket,
1635
 
            # just in case the test failed to do so.
1636
 
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1637
 
            conn.connect(self.sock.getsockname())
1638
 
            conn.close()
1639
 
        except socket.error:
1640
 
            pass
1641
 
        self.sock.close()
1642
 
        self.thread.join()
1643