~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Martin Packman
  • Date: 2012-03-27 17:32:19 UTC
  • mto: (6437.54.3 2.5)
  • mto: This revision was merged to the branch mainline in revision 6525.
  • Revision ID: martin.packman@canonical.com-20120327173219-401pil42gke8j0xh
Fall back to sys.prefix not /usr when looking for .mo files

Show diffs side-by-side

added added

removed removed

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