/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Aaron Bentley
  • Date: 2007-07-22 17:36:22 UTC
  • mfrom: (2644.2.1 bzr.benchmarks)
  • mto: This revision was merged to the branch mainline in revision 2647.
  • Revision ID: aaron.bentley@utoronto.ca-20070722173622-1p4mw28bzp1qangs
Fix deprecation warnings on benchmarks (Lukáš Lalinský)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 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
26
    repository,
31
27
    revision as _mod_revision,
32
 
    tests,
33
28
    treebuilder,
34
29
    )
35
 
from bzrlib.bundle import read_mergeable_from_url
 
30
from bzrlib.builtins import _merge_helper
 
31
from bzrlib.bzrdir import BzrDir
36
32
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
37
33
from bzrlib.bundle.bundle_data import BundleTree
38
 
from bzrlib.bzrdir import BzrDir
39
 
from bzrlib.directory_service import directories
40
34
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
41
35
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
42
36
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
43
37
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
44
38
from bzrlib.branch import Branch
 
39
from bzrlib.diff import internal_diff
 
40
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
 
41
                           NoSuchFile,)
 
42
from bzrlib.merge import Merge3Merger
45
43
from bzrlib.repofmt import knitrepo
46
 
from bzrlib.tests import (
47
 
    test_read_bundle,
48
 
    test_commit,
49
 
    )
 
44
from bzrlib.osutils import has_symlinks, sha_file
 
45
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
 
46
                          TestCase, TestSkipped, test_commit)
50
47
from bzrlib.transform import TreeTransform
51
 
 
52
 
 
53
 
def get_text(vf, key):
54
 
    """Get the fulltext for a given revision id that is present in the vf"""
55
 
    stream = vf.get_record_stream([key], 'unordered', True)
56
 
    record = stream.next()
57
 
    return record.get_bytes_as('fulltext')
58
 
 
59
 
 
60
 
def get_inventory_text(repo, revision_id):
61
 
    """Get the fulltext for the inventory at revision id"""
62
 
    repo.lock_read()
63
 
    try:
64
 
        return get_text(repo.inventories, (revision_id,))
65
 
    finally:
66
 
        repo.unlock()
 
48
from bzrlib.workingtree import WorkingTree
67
49
 
68
50
 
69
51
class MockTree(object):
117
99
        elif kind == 'symlink':
118
100
            ie = InventoryLink(file_id, name, parent_id)
119
101
        else:
120
 
            raise errors.BzrError('unknown kind %r' % kind)
 
102
            raise BzrError('unknown kind %r' % kind)
121
103
        ie.text_sha1 = text_sha_1
122
104
        ie.text_size = text_size
123
105
        return ie
125
107
    def add_dir(self, file_id, path):
126
108
        self.paths[file_id] = path
127
109
        self.ids[path] = file_id
128
 
 
 
110
    
129
111
    def add_file(self, file_id, path, contents):
130
112
        self.add_dir(file_id, path)
131
113
        self.contents[file_id] = contents
148
130
    def contents_stats(self, file_id):
149
131
        if file_id not in self.contents:
150
132
            return None, None
151
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
133
        text_sha1 = sha_file(self.get_file(file_id))
152
134
        return text_sha1, len(self.contents[file_id])
153
135
 
154
136
 
155
 
class BTreeTester(tests.TestCase):
 
137
class BTreeTester(TestCase):
156
138
    """A simple unittest tester for the BundleTree class."""
157
139
 
158
140
    def make_tree_1(self):
162
144
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
163
145
        mtree.add_dir("d", "grandparent/alt_parent")
164
146
        return BundleTree(mtree, ''), mtree
165
 
 
 
147
        
166
148
    def test_renames(self):
167
149
        """Ensure that file renames have the proper effect on children"""
168
150
        btree = self.make_tree_1()[0]
169
151
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
170
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
152
        self.assertEqual(btree.old_path("grandparent/parent"), 
171
153
                         "grandparent/parent")
172
154
        self.assertEqual(btree.old_path("grandparent/parent/file"),
173
155
                         "grandparent/parent/file")
180
162
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
181
163
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
182
164
 
183
 
        self.assertTrue(btree.path2id("grandparent2") is None)
184
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
185
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
165
        assert btree.path2id("grandparent2") is None
 
166
        assert btree.path2id("grandparent2/parent") is None
 
167
        assert btree.path2id("grandparent2/parent/file") is None
186
168
 
187
169
        btree.note_rename("grandparent", "grandparent2")
188
 
        self.assertTrue(btree.old_path("grandparent") is None)
189
 
        self.assertTrue(btree.old_path("grandparent/parent") is None)
190
 
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
 
170
        assert btree.old_path("grandparent") is None
 
171
        assert btree.old_path("grandparent/parent") is None
 
172
        assert btree.old_path("grandparent/parent/file") is None
191
173
 
192
174
        self.assertEqual(btree.id2path("a"), "grandparent2")
193
175
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
197
179
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
198
180
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
199
181
 
200
 
        self.assertTrue(btree.path2id("grandparent") is None)
201
 
        self.assertTrue(btree.path2id("grandparent/parent") is None)
202
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
182
        assert btree.path2id("grandparent") is None
 
183
        assert btree.path2id("grandparent/parent") is None
 
184
        assert btree.path2id("grandparent/parent/file") is None
203
185
 
204
186
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
205
187
        self.assertEqual(btree.id2path("a"), "grandparent2")
210
192
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
211
193
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
212
194
 
213
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
214
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
195
        assert btree.path2id("grandparent2/parent") is None
 
196
        assert btree.path2id("grandparent2/parent/file") is None
215
197
 
216
 
        btree.note_rename("grandparent/parent/file",
 
198
        btree.note_rename("grandparent/parent/file", 
217
199
                          "grandparent2/parent2/file2")
218
200
        self.assertEqual(btree.id2path("a"), "grandparent2")
219
201
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
223
205
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
224
206
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
225
207
 
226
 
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
 
208
        assert btree.path2id("grandparent2/parent2/file") is None
227
209
 
228
210
    def test_moves(self):
229
211
        """Ensure that file moves have the proper effect on children"""
230
212
        btree = self.make_tree_1()[0]
231
 
        btree.note_rename("grandparent/parent/file",
 
213
        btree.note_rename("grandparent/parent/file", 
232
214
                          "grandparent/alt_parent/file")
233
215
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
234
216
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
235
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
217
        assert btree.path2id("grandparent/parent/file") is None
236
218
 
237
219
    def unified_diff(self, old, new):
238
220
        out = StringIO()
239
 
        diff.internal_diff("old", old, "new", new, out)
 
221
        internal_diff("old", old, "new", new, out)
240
222
        out.seek(0,0)
241
223
        return out.read()
242
224
 
243
225
    def make_tree_2(self):
244
226
        btree = self.make_tree_1()[0]
245
 
        btree.note_rename("grandparent/parent/file",
 
227
        btree.note_rename("grandparent/parent/file", 
246
228
                          "grandparent/alt_parent/file")
247
 
        self.assertTrue(btree.id2path("e") is None)
248
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
229
        assert btree.id2path("e") is None
 
230
        assert btree.path2id("grandparent/parent/file") is None
249
231
        btree.note_id("e", "grandparent/parent/file")
250
232
        return btree
251
233
 
277
259
    def make_tree_3(self):
278
260
        btree, mtree = self.make_tree_1()
279
261
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
280
 
        btree.note_rename("grandparent/parent/file",
 
262
        btree.note_rename("grandparent/parent/file", 
281
263
                          "grandparent/alt_parent/file")
282
 
        btree.note_rename("grandparent/parent/topping",
 
264
        btree.note_rename("grandparent/parent/topping", 
283
265
                          "grandparent/alt_parent/stopping")
284
266
        return btree
285
267
 
309
291
        btree = self.make_tree_1()[0]
310
292
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
311
293
        btree.note_deletion("grandparent/parent/file")
312
 
        self.assertTrue(btree.id2path("c") is None)
313
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
294
        assert btree.id2path("c") is None
 
295
        assert btree.path2id("grandparent/parent/file") is None
314
296
 
315
297
    def sorted_ids(self, tree):
316
298
        ids = list(tree)
324
306
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
325
307
        btree.note_deletion("grandparent/parent/file")
326
308
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
327
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
309
        btree.note_last_changed("grandparent/alt_parent/fool", 
328
310
                                "revisionidiguess")
329
311
        self.assertEqual(self.sorted_ids(btree),
330
312
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
331
313
 
332
314
 
333
 
class BundleTester1(tests.TestCaseWithTransport):
 
315
class BundleTester1(TestCaseWithTransport):
334
316
 
335
317
    def test_mismatched_bundle(self):
336
318
        format = bzrdir.BzrDirMetaFormat1()
337
319
        format.repository_format = knitrepo.RepositoryFormatKnit3()
338
320
        serializer = BundleSerializerV08('0.8')
339
321
        b = self.make_branch('.', format=format)
340
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
322
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
341
323
                          b.repository, [], {}, StringIO())
342
324
 
343
325
    def test_matched_bundle(self):
363
345
        format = bzrdir.BzrDirMetaFormat1()
364
346
        format.repository_format = knitrepo.RepositoryFormatKnit1()
365
347
        target = self.make_branch('target', format=format)
366
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
348
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
367
349
                          target.repository, read_bundle(text))
368
350
 
369
351
 
377
359
    def make_branch_and_tree(self, path, format=None):
378
360
        if format is None:
379
361
            format = self.bzrdir_format()
380
 
        return tests.TestCaseWithTransport.make_branch_and_tree(
381
 
            self, path, format)
 
362
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
382
363
 
383
364
    def make_branch(self, path, format=None):
384
365
        if format is None:
385
366
            format = self.bzrdir_format()
386
 
        return tests.TestCaseWithTransport.make_branch(self, path, format)
 
367
        return TestCaseWithTransport.make_branch(self, path, format)
387
368
 
388
369
    def create_bundle_text(self, base_rev_id, rev_id):
389
370
        bundle_txt = StringIO()
390
 
        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, 
391
372
                               bundle_txt, format=self.format)
392
373
        bundle_txt.seek(0)
393
 
        self.assertEqual(bundle_txt.readline(),
 
374
        self.assertEqual(bundle_txt.readline(), 
394
375
                         '# Bazaar revision bundle v%s\n' % self.format)
395
376
        self.assertEqual(bundle_txt.readline(), '#\n')
396
377
 
404
385
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
405
386
        Make sure that the text generated is valid, and that it
406
387
        can be applied against the base, and generate the same information.
407
 
 
408
 
        :return: The in-memory bundle
 
388
        
 
389
        :return: The in-memory bundle 
409
390
        """
410
391
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
411
392
 
412
 
        # This should also validate the generated bundle
 
393
        # This should also validate the generated bundle 
413
394
        bundle = read_bundle(bundle_txt)
414
395
        repository = self.b1.repository
415
396
        for bundle_rev in bundle.real_revisions:
419
400
            # it
420
401
            branch_rev = repository.get_revision(bundle_rev.revision_id)
421
402
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
422
 
                      'timestamp', 'timezone', 'message', 'committer',
 
403
                      'timestamp', 'timezone', 'message', 'committer', 
423
404
                      'parent_ids', 'properties'):
424
 
                self.assertEqual(getattr(branch_rev, a),
 
405
                self.assertEqual(getattr(branch_rev, a), 
425
406
                                 getattr(bundle_rev, a))
426
 
            self.assertEqual(len(branch_rev.parent_ids),
 
407
            self.assertEqual(len(branch_rev.parent_ids), 
427
408
                             len(bundle_rev.parent_ids))
428
 
        self.assertEqual(rev_ids,
 
409
        self.assertEqual(rev_ids, 
429
410
                         [r.revision_id for r in bundle.real_revisions])
430
411
        self.valid_apply_bundle(base_rev_id, bundle,
431
412
                                   checkout_dir=checkout_dir)
435
416
    def get_invalid_bundle(self, base_rev_id, rev_id):
436
417
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
437
418
        Munge the text so that it's invalid.
438
 
 
 
419
        
439
420
        :return: The in-memory bundle
440
421
        """
441
422
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
442
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
423
        new_text = bundle_txt.getvalue().replace('executable:no', 
443
424
                                               'executable:yes')
444
425
        bundle_txt = StringIO(new_text)
445
426
        bundle = read_bundle(bundle_txt)
446
427
        self.valid_apply_bundle(base_rev_id, bundle)
447
 
        return bundle
 
428
        return bundle 
448
429
 
449
430
    def test_non_bundle(self):
450
 
        self.assertRaises(errors.NotABundle,
451
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
431
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
452
432
 
453
433
    def test_malformed(self):
454
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
434
        self.assertRaises(BadBundle, read_bundle, 
455
435
                          StringIO('# Bazaar revision bundle v'))
456
436
 
457
437
    def test_crlf_bundle(self):
458
438
        try:
459
439
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
460
 
        except errors.BadBundle:
 
440
        except BadBundle:
461
441
            # It is currently permitted for bundles with crlf line endings to
462
442
            # make read_bundle raise a BadBundle, but this should be fixed.
463
443
            # Anything else, especially NotABundle, is an error.
468
448
        """
469
449
 
470
450
        if checkout_dir is None:
471
 
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
 
451
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
472
452
        else:
473
453
            if not os.path.exists(checkout_dir):
474
454
                os.mkdir(checkout_dir)
477
457
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
478
458
                                 format=self.format)
479
459
        s.seek(0)
480
 
        self.assertIsInstance(s.getvalue(), str)
 
460
        assert isinstance(s.getvalue(), str), (
 
461
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
481
462
        install_bundle(tree.branch.repository, read_bundle(s))
482
463
        for ancestor in ancestors:
483
464
            old = self.b1.repository.revision_tree(ancestor)
484
465
            new = tree.branch.repository.revision_tree(ancestor)
485
 
            old.lock_read()
486
 
            new.lock_read()
487
 
            try:
488
 
                # Check that there aren't any inventory level changes
489
 
                delta = new.changes_from(old)
490
 
                self.assertFalse(delta.has_changed(),
491
 
                                 'Revision %s not copied correctly.'
492
 
                                 % (ancestor,))
493
 
 
494
 
                # Now check that the file contents are all correct
495
 
                for inventory_id in old:
496
 
                    try:
497
 
                        old_file = old.get_file(inventory_id)
498
 
                    except errors.NoSuchFile:
499
 
                        continue
500
 
                    if old_file is None:
501
 
                        continue
502
 
                    self.assertEqual(old_file.read(),
503
 
                                     new.get_file(inventory_id).read())
504
 
            finally:
505
 
                new.unlock()
506
 
                old.unlock()
 
466
 
 
467
            # Check that there aren't any inventory level changes
 
468
            delta = new.changes_from(old)
 
469
            self.assertFalse(delta.has_changed(),
 
470
                             'Revision %s not copied correctly.'
 
471
                             % (ancestor,))
 
472
 
 
473
            # Now check that the file contents are all correct
 
474
            for inventory_id in old:
 
475
                try:
 
476
                    old_file = old.get_file(inventory_id)
 
477
                except NoSuchFile:
 
478
                    continue
 
479
                if old_file is None:
 
480
                    continue
 
481
                self.assertEqual(old_file.read(),
 
482
                                 new.get_file(inventory_id).read())
507
483
        if not _mod_revision.is_null(rev_id):
508
484
            rh = self.b1.revision_history()
509
485
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
518
494
        sure everything matches the builtin branch.
519
495
        """
520
496
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
521
 
        to_tree.lock_write()
522
 
        try:
523
 
            self._valid_apply_bundle(base_rev_id, info, to_tree)
524
 
        finally:
525
 
            to_tree.unlock()
526
 
 
527
 
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
528
497
        original_parents = to_tree.get_parent_ids()
529
498
        repository = to_tree.branch.repository
530
499
        original_parents = to_tree.get_parent_ids()
531
500
        self.assertIs(repository.has_revision(base_rev_id), True)
532
501
        for rev in info.real_revisions:
533
502
            self.assert_(not repository.has_revision(rev.revision_id),
534
 
                'Revision {%s} present before applying bundle'
 
503
                'Revision {%s} present before applying bundle' 
535
504
                % rev.revision_id)
536
 
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
 
505
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
537
506
 
538
507
        for rev in info.real_revisions:
539
508
            self.assert_(repository.has_revision(rev.revision_id),
540
 
                'Missing revision {%s} after applying bundle'
 
509
                'Missing revision {%s} after applying bundle' 
541
510
                % rev.revision_id)
542
511
 
543
512
        self.assert_(to_tree.branch.repository.has_revision(info.target))
549
518
        rev = info.real_revisions[-1]
550
519
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
551
520
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
552
 
 
 
521
        
553
522
        # TODO: make sure the target tree is identical to base tree
554
523
        #       we might also check the working tree.
555
524
 
574
543
        self.tree1 = self.make_branch_and_tree('b1')
575
544
        self.b1 = self.tree1.branch
576
545
 
577
 
        self.build_tree_contents([('b1/one', 'one\n')])
578
 
        self.tree1.add('one', 'one-id')
579
 
        self.tree1.set_root_id('root-id')
 
546
        open('b1/one', 'wb').write('one\n')
 
547
        self.tree1.add('one')
580
548
        self.tree1.commit('add one', rev_id='a@cset-0-1')
581
549
 
582
550
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
593
561
                , 'b1/sub/sub/'
594
562
                , 'b1/sub/sub/nonempty.txt'
595
563
                ])
596
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
597
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
564
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
565
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
598
566
        tt = TreeTransform(self.tree1)
599
567
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
600
568
        tt.apply()
616
584
 
617
585
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
618
586
 
619
 
        # Check a rollup bundle
 
587
        # Check a rollup bundle 
620
588
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
621
589
 
622
590
        # Now delete entries
630
598
        tt.set_executability(False, trans_id)
631
599
        tt.apply()
632
600
        self.tree1.commit('removed', rev_id='a@cset-0-3')
633
 
 
 
601
        
634
602
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
635
 
        self.assertRaises((errors.TestamentMismatch,
636
 
            errors.VersionedFileInvalidChecksum,
637
 
            errors.BadBundle), self.get_invalid_bundle,
 
603
        self.assertRaises((TestamentMismatch,
 
604
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
638
605
            'a@cset-0-2', 'a@cset-0-3')
639
 
        # Check a rollup bundle
 
606
        # Check a rollup bundle 
640
607
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
641
608
 
642
609
        # Now move the directory
644
611
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
645
612
 
646
613
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
647
 
        # Check a rollup bundle
 
614
        # Check a rollup bundle 
648
615
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
649
616
 
650
617
        # Modified files
652
619
        open('b1/sub/dir/ pre space', 'ab').write(
653
620
             '\r\nAdding some\r\nDOS format lines\r\n')
654
621
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
655
 
        self.tree1.rename_one('sub/dir/ pre space',
 
622
        self.tree1.rename_one('sub/dir/ pre space', 
656
623
                              'sub/ start space')
657
624
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
658
625
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
664
631
                          verbose=False)
665
632
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
666
633
        other = self.get_checkout('a@cset-0-5')
667
 
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
668
 
                                       'a@cset-0-5')
669
 
        tree2_inv = get_inventory_text(other.branch.repository,
670
 
                                       'a@cset-0-5')
 
634
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
 
635
            'a@cset-0-5')
 
636
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
671
637
        self.assertEqualDiff(tree1_inv, tree2_inv)
672
638
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
673
639
        other.commit('rename file', rev_id='a@cset-0-6b')
674
 
        self.tree1.merge_from_branch(other.branch)
 
640
        _merge_helper([other.basedir, -1], [None, None],
 
641
                      this_dir=self.tree1.basedir)
675
642
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
676
643
                          verbose=False)
677
644
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
678
645
 
679
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
680
 
        link_id = 'link-1'
681
 
 
682
 
        self.requireFeature(tests.SymlinkFeature)
 
646
    def test_symlink_bundle(self):
 
647
        if not has_symlinks():
 
648
            raise TestSkipped("No symlink support")
683
649
        self.tree1 = self.make_branch_and_tree('b1')
684
650
        self.b1 = self.tree1.branch
685
 
 
686
651
        tt = TreeTransform(self.tree1)
687
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
652
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
688
653
        tt.apply()
689
654
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
690
 
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
691
 
        if getattr(bundle ,'revision_tree', None) is not None:
692
 
            # Not all bundle formats supports revision_tree
693
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
694
 
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
695
 
 
 
655
        self.get_valid_bundle('null:', 'l@cset-0-1')
696
656
        tt = TreeTransform(self.tree1)
697
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
657
        trans_id = tt.trans_id_tree_file_id('link-1')
698
658
        tt.adjust_path('link2', tt.root, trans_id)
699
659
        tt.delete_contents(trans_id)
700
 
        tt.create_symlink(new_link_target, trans_id)
 
660
        tt.create_symlink('mars', trans_id)
701
661
        tt.apply()
702
662
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
703
 
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
704
 
        if getattr(bundle ,'revision_tree', None) is not None:
705
 
            # Not all bundle formats supports revision_tree
706
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
707
 
            self.assertEqual(new_link_target,
708
 
                             bund_tree.get_symlink_target(link_id))
709
 
 
 
663
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
710
664
        tt = TreeTransform(self.tree1)
711
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
665
        trans_id = tt.trans_id_tree_file_id('link-1')
712
666
        tt.delete_contents(trans_id)
713
667
        tt.create_symlink('jupiter', trans_id)
714
668
        tt.apply()
715
669
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
716
 
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
717
 
 
 
670
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
718
671
        tt = TreeTransform(self.tree1)
719
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
672
        trans_id = tt.trans_id_tree_file_id('link-1')
720
673
        tt.delete_contents(trans_id)
721
674
        tt.apply()
722
675
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
723
 
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
724
 
 
725
 
    def test_symlink_bundle(self):
726
 
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
727
 
 
728
 
    def test_unicode_symlink_bundle(self):
729
 
        self.requireFeature(tests.UnicodeFilenameFeature)
730
 
        self._test_symlink_bundle(u'\N{Euro Sign}link',
731
 
                                  u'bar/\N{Euro Sign}foo',
732
 
                                  u'mars\N{Euro Sign}')
 
676
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
733
677
 
734
678
    def test_binary_bundle(self):
735
679
        self.tree1 = self.make_branch_and_tree('b1')
736
680
        self.b1 = self.tree1.branch
737
681
        tt = TreeTransform(self.tree1)
738
 
 
 
682
        
739
683
        # Add
740
684
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
741
685
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
796
740
        tt.create_file('file2', trans_id)
797
741
        tt.apply()
798
742
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
799
 
        self.tree1.merge_from_branch(other.branch)
 
743
        _merge_helper([other.basedir, -1], [None, None],
 
744
                      this_dir=self.tree1.basedir)
800
745
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
801
746
                          verbose=False)
802
747
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
833
778
        return bundle_file.getvalue()
834
779
 
835
780
    def test_unicode_bundle(self):
836
 
        self.requireFeature(tests.UnicodeFilenameFeature)
837
781
        # Handle international characters
838
782
        os.mkdir('b1')
839
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
783
        try:
 
784
            f = open(u'b1/with Dod\xe9', 'wb')
 
785
        except UnicodeEncodeError:
 
786
            raise TestSkipped("Filesystem doesn't support unicode")
840
787
 
841
788
        self.tree1 = self.make_branch_and_tree('b1')
842
789
        self.b1 = self.tree1.branch
846
793
            u'William Dod\xe9\n').encode('utf-8'))
847
794
        f.close()
848
795
 
849
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
796
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
850
797
        self.tree1.commit(u'i18n commit from William Dod\xe9',
851
798
                          rev_id='i18n-1', committer=u'William Dod\xe9')
852
799
 
 
800
        if sys.platform == 'darwin':
 
801
            # On Mac the '\xe9' gets changed to 'e\u0301'
 
802
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
 
803
                             sorted(os.listdir(u'b1')))
 
804
            delta = self.tree1.changes_from(self.tree1.basis_tree())
 
805
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
 
806
                             delta.removed)
 
807
            self.knownFailure("Mac OSX doesn't preserve unicode"
 
808
                              " combining characters.")
 
809
 
853
810
        # Add
854
811
        bundle = self.get_valid_bundle('null:', 'i18n-1')
855
812
 
856
813
        # Modified
857
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
814
        f = open(u'b1/with Dod\xe9', 'wb')
858
815
        f.write(u'Modified \xb5\n'.encode('utf8'))
859
816
        f.close()
860
817
        self.tree1.commit(u'modified', rev_id='i18n-2')
861
818
 
862
819
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
863
 
 
 
820
        
864
821
        # Renamed
865
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
822
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
866
823
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
867
824
                          committer=u'Erik B\xe5gfors')
868
825
 
869
826
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
870
827
 
871
828
        # Removed
872
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
829
        self.tree1.remove([u'B\xe5gfors'])
873
830
        self.tree1.commit(u'removed', rev_id='i18n-4')
874
831
 
875
832
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
880
837
 
881
838
    def test_whitespace_bundle(self):
882
839
        if sys.platform in ('win32', 'cygwin'):
883
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
884
 
                                    ' with tabs or trailing spaces')
 
840
            raise TestSkipped('Windows doesn\'t support filenames'
 
841
                              ' with tabs or trailing spaces')
885
842
        self.tree1 = self.make_branch_and_tree('b1')
886
843
        self.b1 = self.tree1.branch
887
844
 
912
869
        self.tree1.commit('removed', rev_id='white-4')
913
870
 
914
871
        bundle = self.get_valid_bundle('white-3', 'white-4')
915
 
 
 
872
        
916
873
        # Now test a complet roll-up
917
874
        bundle = self.get_valid_bundle('null:', 'white-4')
918
875
 
931
888
                          timezone=19800, timestamp=1152544886.0)
932
889
 
933
890
        bundle = self.get_valid_bundle('null:', 'tz-1')
934
 
 
 
891
        
935
892
        rev = bundle.revisions[0]
936
893
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
937
894
        self.assertEqual(19800, rev.timezone)
1039
996
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1040
997
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1041
998
        bundle.install_revisions(repo)
1042
 
        inv_text = repo._get_inventory_xml('rev2')
 
999
        inv_text = repo.get_inventory_xml('rev2')
1043
1000
        self.assertNotContainsRe(inv_text, 'format="5"')
1044
1001
        self.assertContainsRe(inv_text, 'format="7"')
1045
1002
 
1046
 
    def make_repo_with_installed_revisions(self):
1047
 
        tree = self.make_simple_tree('knit')
1048
 
        tree.commit('hello', rev_id='rev1')
1049
 
        tree.commit('hello', rev_id='rev2')
1050
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1051
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1052
 
        bundle.install_revisions(repo)
1053
 
        return repo
1054
 
 
1055
1003
    def test_across_models(self):
1056
 
        repo = self.make_repo_with_installed_revisions()
 
1004
        tree = self.make_simple_tree('knit')
 
1005
        tree.commit('hello', rev_id='rev1')
 
1006
        tree.commit('hello', rev_id='rev2')
 
1007
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1008
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1009
        bundle.install_revisions(repo)
1057
1010
        inv = repo.get_inventory('rev2')
1058
1011
        self.assertEqual('rev2', inv.root.revision)
1059
 
        root_id = inv.root.file_id
1060
 
        repo.lock_read()
1061
 
        self.addCleanup(repo.unlock)
1062
 
        self.assertEqual({(root_id, 'rev1'):(),
1063
 
            (root_id, 'rev2'):((root_id, 'rev1'),)},
1064
 
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
1065
 
 
1066
 
    def test_inv_hash_across_serializers(self):
1067
 
        repo = self.make_repo_with_installed_revisions()
1068
 
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
1069
 
        xml = repo._get_inventory_xml('rev2')
1070
 
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
 
1012
        root_vf = repo.weave_store.get_weave(inv.root.file_id,
 
1013
                                             repo.get_transaction())
 
1014
        self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
1071
1015
 
1072
1016
    def test_across_models_incompatible(self):
1073
1017
        tree = self.make_simple_tree('dirstate-with-subtree')
1076
1020
        try:
1077
1021
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1078
1022
        except errors.IncompatibleBundleFormat:
1079
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1023
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1080
1024
        repo = self.make_repository('repo', format='knit')
1081
1025
        bundle.install_revisions(repo)
1082
1026
 
1103
1047
        try:
1104
1048
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1105
1049
        except errors.IncompatibleBundleFormat:
1106
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
 
1050
            raise TestSkipped("Format 0.8 doesn't work with knit3")
1107
1051
        if isinstance(bundle, v09.BundleInfo09):
1108
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
 
1052
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
1109
1053
        repo = self.make_repository('repo', format='knit')
1110
1054
        self.assertRaises(errors.IncompatibleRevision,
1111
1055
                          bundle.install_revisions, repo)
1118
1062
        try:
1119
1063
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1120
1064
        except ValueError:
1121
 
            raise tests.TestSkipped(
1122
 
                "Repository doesn't support revision ids with slashes")
 
1065
            raise TestSkipped("Repository doesn't support revision ids with"
 
1066
                              " slashes")
1123
1067
        bundle = self.get_valid_bundle('null:', 'rev/id')
1124
1068
 
1125
 
    def test_skip_file(self):
1126
 
        """Make sure we don't accidentally write to the wrong versionedfile"""
1127
 
        self.tree1 = self.make_branch_and_tree('tree')
1128
 
        self.b1 = self.tree1.branch
1129
 
        # rev1 is not present in bundle, done by fetch
1130
 
        self.build_tree_contents([('tree/file2', 'contents1')])
1131
 
        self.tree1.add('file2', 'file2-id')
1132
 
        self.tree1.commit('rev1', rev_id='reva')
1133
 
        self.build_tree_contents([('tree/file3', 'contents2')])
1134
 
        # rev2 is present in bundle, and done by fetch
1135
 
        # having file1 in the bunle causes file1's versionedfile to be opened.
1136
 
        self.tree1.add('file3', 'file3-id')
1137
 
        self.tree1.commit('rev2')
1138
 
        # Updating file2 should not cause an attempt to add to file1's vf
1139
 
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
1140
 
        self.build_tree_contents([('tree/file2', 'contents3')])
1141
 
        self.tree1.commit('rev3', rev_id='rev3')
1142
 
        bundle = self.get_valid_bundle('reva', 'rev3')
1143
 
        if getattr(bundle, 'get_bundle_reader', None) is None:
1144
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
1145
 
        # be sure that file1 comes before file2
1146
 
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1147
 
            if f == 'file3-id':
1148
 
                break
1149
 
            self.assertNotEqual(f, 'file2-id')
1150
 
        bundle.install_revisions(target.branch.repository)
1151
 
 
1152
 
 
1153
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1069
 
 
1070
class V08BundleTester(BundleTester, TestCaseWithTransport):
1154
1071
 
1155
1072
    format = '0.8'
1156
1073
 
1289
1206
        return format
1290
1207
 
1291
1208
 
1292
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
 
1209
class V4BundleTester(BundleTester, TestCaseWithTransport):
1293
1210
 
1294
1211
    format = '4'
1295
1212
 
1297
1214
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1298
1215
        Make sure that the text generated is valid, and that it
1299
1216
        can be applied against the base, and generate the same information.
1300
 
 
1301
 
        :return: The in-memory bundle
 
1217
        
 
1218
        :return: The in-memory bundle 
1302
1219
        """
1303
1220
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1304
1221
 
1305
 
        # This should also validate the generated bundle
 
1222
        # This should also validate the generated bundle 
1306
1223
        bundle = read_bundle(bundle_txt)
1307
1224
        repository = self.b1.repository
1308
1225
        for bundle_rev in bundle.real_revisions:
1312
1229
            # it
1313
1230
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1314
1231
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1315
 
                      'timestamp', 'timezone', 'message', 'committer',
 
1232
                      'timestamp', 'timezone', 'message', 'committer', 
1316
1233
                      'parent_ids', 'properties'):
1317
 
                self.assertEqual(getattr(branch_rev, a),
 
1234
                self.assertEqual(getattr(branch_rev, a), 
1318
1235
                                 getattr(bundle_rev, a))
1319
 
            self.assertEqual(len(branch_rev.parent_ids),
 
1236
            self.assertEqual(len(branch_rev.parent_ids), 
1320
1237
                             len(bundle_rev.parent_ids))
1321
1238
        self.assertEqual(set(rev_ids),
1322
1239
                         set([r.revision_id for r in bundle.real_revisions]))
1336
1253
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1337
1254
        new_text = new_text.replace('<file file_id="exe-1"',
1338
1255
                                    '<file executable="y" file_id="exe-1"')
1339
 
        new_text = new_text.replace('B260', 'B275')
 
1256
        new_text = new_text.replace('B372', 'B387')
1340
1257
        bundle_txt = StringIO()
1341
1258
        bundle_txt.write(serializer._get_bundle_header('4'))
1342
1259
        bundle_txt.write('\n')
1348
1265
 
1349
1266
    def create_bundle_text(self, base_rev_id, rev_id):
1350
1267
        bundle_txt = StringIO()
1351
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
1268
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
1352
1269
                               bundle_txt, format=self.format)
1353
1270
        bundle_txt.seek(0)
1354
 
        self.assertEqual(bundle_txt.readline(),
 
1271
        self.assertEqual(bundle_txt.readline(), 
1355
1272
                         '# Bazaar revision bundle v%s\n' % self.format)
1356
1273
        self.assertEqual(bundle_txt.readline(), '#\n')
1357
1274
        rev = self.b1.repository.get_revision(rev_id)
1377
1294
        tree2 = self.make_branch_and_tree('target')
1378
1295
        target_repo = tree2.branch.repository
1379
1296
        install_bundle(target_repo, serializer.read(s))
1380
 
        target_repo.lock_read()
1381
 
        self.addCleanup(target_repo.unlock)
1382
 
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1383
 
        repo_texts = dict((i, ''.join(content)) for i, content
1384
 
                          in target_repo.iter_files_bytes(
1385
 
                                [('fileid-2', 'rev1', '1'),
1386
 
                                 ('fileid-2', 'rev2', '2')]))
1387
 
        self.assertEqual({'1':'contents1\nstatic\n',
1388
 
                          '2':'contents2\nstatic\n'},
1389
 
                         repo_texts)
 
1297
        vf = target_repo.weave_store.get_weave('fileid-2',
 
1298
            target_repo.get_transaction())
 
1299
        self.assertEqual('contents1\nstatic\n', vf.get_text('rev1'))
 
1300
        self.assertEqual('contents2\nstatic\n', vf.get_text('rev2'))
1390
1301
        rtree = target_repo.revision_tree('rev2')
1391
 
        inventory_vf = target_repo.inventories
1392
 
        # If the inventory store has a graph, it must match the revision graph.
1393
 
        self.assertSubset(
1394
 
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
1395
 
            [None, (('rev1',),)])
 
1302
        inventory_vf = target_repo.get_inventory_weave()
 
1303
        self.assertEqual(['rev1'], inventory_vf.get_parents('rev2'))
1396
1304
        self.assertEqual('changed file',
1397
1305
                         target_repo.get_revision('rev2').message)
1398
1306
 
1448
1356
        return 'metaweave'
1449
1357
 
1450
1358
 
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
1359
class MungedBundleTester(object):
1646
1360
 
1647
1361
    def build_test_bundle(self):
1694
1408
        self.check_valid(bundle)
1695
1409
 
1696
1410
 
1697
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
 
1411
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
1698
1412
 
1699
1413
    format = '0.9'
1700
1414
 
1732
1446
        self.check_valid(bundle)
1733
1447
 
1734
1448
 
1735
 
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
 
1449
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
1736
1450
 
1737
1451
    format = '4'
1738
1452
 
1739
1453
 
1740
 
class TestBundleWriterReader(tests.TestCase):
 
1454
class TestBundleWriterReader(TestCase):
1741
1455
 
1742
1456
    def test_roundtrip_record(self):
1743
1457
        fileobj = StringIO()
1748
1462
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1749
1463
        writer.end()
1750
1464
        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()
 
1465
        record_iter = v4.BundleReader(fileobj).iter_records()
1772
1466
        record = record_iter.next()
1773
1467
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1774
1468
            'info', None, None), record)
1805
1499
        record = record_iter.next()
1806
1500
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1807
1501
            '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()
 
1502
        self.assertRaises(BadBundle, record_iter.next)