~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Robert Collins
  • Date: 2007-04-19 02:27:44 UTC
  • mto: This revision was merged to the branch mainline in revision 2426.
  • Revision ID: robertc@robertcollins.net-20070419022744-pfdqz42kp1wizh43
``make docs`` now creates a man page at ``man1/bzr.1`` fixing bug 107388.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

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