~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Aaron Bentley
  • Date: 2006-09-29 18:58:43 UTC
  • mto: This revision was merged to the branch mainline in revision 2127.
  • Revision ID: abentley@panoramicfeedback.com-20060929185843-6841128d849c46a2
Get page generation working

Show diffs side-by-side

added added

removed removed

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