~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Florent Gallaire
  • Date: 2017-03-17 10:39:02 UTC
  • mto: This revision was merged to the branch mainline in revision 6622.
  • Revision ID: fgallaire@gmail.com-20170317103902-xsmafws9vn8rczx9
Fix for Windows and 32-bit platforms buggy gmtime().

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-2013, 2016 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,
27
29
    revision as _mod_revision,
 
30
    tests,
28
31
    treebuilder,
29
32
    )
30
 
from bzrlib.builtins import _merge_helper
31
 
from bzrlib.bzrdir import BzrDir
 
33
from bzrlib.bundle import read_mergeable_from_url
32
34
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
33
35
from bzrlib.bundle.bundle_data import BundleTree
 
36
from bzrlib.directory_service import directories
34
37
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
35
38
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
36
39
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
37
40
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
38
 
from bzrlib.branch import Branch
39
 
from bzrlib.diff import internal_diff
40
 
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
41
 
                           NoSuchFile,)
42
 
from bzrlib.merge import Merge3Merger
43
41
from bzrlib.repofmt import knitrepo
44
 
from bzrlib.osutils import has_symlinks, sha_file
45
 
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
46
 
                          TestCase, TestSkipped, test_commit)
 
42
from bzrlib.tests import (
 
43
    features,
 
44
    test_commit,
 
45
    test_read_bundle,
 
46
    test_server,
 
47
    )
47
48
from bzrlib.transform import TreeTransform
48
 
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()
49
65
 
50
66
 
51
67
class MockTree(object):
 
68
 
52
69
    def __init__(self):
53
70
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
54
71
        object.__init__(self)
58
75
        self.root = InventoryDirectory(ROOT_ID, '', None)
59
76
 
60
77
    inventory = property(lambda x:x)
61
 
 
62
 
    def __iter__(self):
63
 
        return self.paths.iterkeys()
 
78
    root_inventory = property(lambda x:x)
 
79
 
 
80
    def get_root_id(self):
 
81
        return self.root.file_id
 
82
 
 
83
    def all_file_ids(self):
 
84
        return set(self.paths.keys())
 
85
 
 
86
    def is_executable(self, file_id):
 
87
        # Not all the files are executable.
 
88
        return False
64
89
 
65
90
    def __getitem__(self, file_id):
66
91
        if file_id == self.root.file_id:
78
103
        for path, file_id in self.ids.iteritems():
79
104
            yield path, self[file_id]
80
105
 
81
 
    def get_file_kind(self, file_id):
 
106
    def kind(self, file_id):
82
107
        if file_id in self.contents:
83
108
            kind = 'file'
84
109
        else:
86
111
        return kind
87
112
 
88
113
    def make_entry(self, file_id, path):
89
 
        from bzrlib.inventory import (InventoryEntry, InventoryFile
90
 
                                    , InventoryDirectory, InventoryLink)
 
114
        from bzrlib.inventory import (InventoryFile , InventoryDirectory,
 
115
            InventoryLink)
91
116
        name = os.path.basename(path)
92
 
        kind = self.get_file_kind(file_id)
 
117
        kind = self.kind(file_id)
93
118
        parent_id = self.parent_id(file_id)
94
119
        text_sha_1, text_size = self.contents_stats(file_id)
95
120
        if kind == 'directory':
96
121
            ie = InventoryDirectory(file_id, name, parent_id)
97
122
        elif kind == 'file':
98
123
            ie = InventoryFile(file_id, name, parent_id)
 
124
            ie.text_sha1 = text_sha_1
 
125
            ie.text_size = text_size
99
126
        elif kind == 'symlink':
100
127
            ie = InventoryLink(file_id, name, parent_id)
101
128
        else:
102
 
            raise BzrError('unknown kind %r' % kind)
103
 
        ie.text_sha1 = text_sha_1
104
 
        ie.text_size = text_size
 
129
            raise errors.BzrError('unknown kind %r' % kind)
105
130
        return ie
106
131
 
107
132
    def add_dir(self, file_id, path):
108
133
        self.paths[file_id] = path
109
134
        self.ids[path] = file_id
110
 
    
 
135
 
111
136
    def add_file(self, file_id, path, contents):
112
137
        self.add_dir(file_id, path)
113
138
        self.contents[file_id] = contents
127
152
        result.seek(0,0)
128
153
        return result
129
154
 
 
155
    def get_file_revision(self, file_id):
 
156
        return self.inventory[file_id].revision
 
157
 
 
158
    def get_file_size(self, file_id):
 
159
        return self.inventory[file_id].text_size
 
160
 
 
161
    def get_file_sha1(self, file_id):
 
162
        return self.inventory[file_id].text_sha1
 
163
 
130
164
    def contents_stats(self, file_id):
131
165
        if file_id not in self.contents:
132
166
            return None, None
133
 
        text_sha1 = sha_file(self.get_file(file_id))
 
167
        text_sha1 = osutils.sha_file(self.get_file(file_id))
134
168
        return text_sha1, len(self.contents[file_id])
135
169
 
136
170
 
137
 
class BTreeTester(TestCase):
 
171
class BTreeTester(tests.TestCase):
138
172
    """A simple unittest tester for the BundleTree class."""
139
173
 
140
174
    def make_tree_1(self):
144
178
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
145
179
        mtree.add_dir("d", "grandparent/alt_parent")
146
180
        return BundleTree(mtree, ''), mtree
147
 
        
 
181
 
148
182
    def test_renames(self):
149
183
        """Ensure that file renames have the proper effect on children"""
150
184
        btree = self.make_tree_1()[0]
151
185
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
152
 
        self.assertEqual(btree.old_path("grandparent/parent"), 
 
186
        self.assertEqual(btree.old_path("grandparent/parent"),
153
187
                         "grandparent/parent")
154
188
        self.assertEqual(btree.old_path("grandparent/parent/file"),
155
189
                         "grandparent/parent/file")
162
196
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
163
197
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
164
198
 
165
 
        assert btree.path2id("grandparent2") is None
166
 
        assert btree.path2id("grandparent2/parent") is None
167
 
        assert btree.path2id("grandparent2/parent/file") is None
 
199
        self.assertTrue(btree.path2id("grandparent2") is None)
 
200
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
201
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
168
202
 
169
203
        btree.note_rename("grandparent", "grandparent2")
170
 
        assert btree.old_path("grandparent") is None
171
 
        assert btree.old_path("grandparent/parent") is None
172
 
        assert btree.old_path("grandparent/parent/file") is None
 
204
        self.assertTrue(btree.old_path("grandparent") is None)
 
205
        self.assertTrue(btree.old_path("grandparent/parent") is None)
 
206
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
173
207
 
174
208
        self.assertEqual(btree.id2path("a"), "grandparent2")
175
209
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
179
213
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
180
214
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
181
215
 
182
 
        assert btree.path2id("grandparent") is None
183
 
        assert btree.path2id("grandparent/parent") is None
184
 
        assert btree.path2id("grandparent/parent/file") is None
 
216
        self.assertTrue(btree.path2id("grandparent") is None)
 
217
        self.assertTrue(btree.path2id("grandparent/parent") is None)
 
218
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
185
219
 
186
220
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
187
221
        self.assertEqual(btree.id2path("a"), "grandparent2")
192
226
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
193
227
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
194
228
 
195
 
        assert btree.path2id("grandparent2/parent") is None
196
 
        assert btree.path2id("grandparent2/parent/file") is None
 
229
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
230
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
197
231
 
198
 
        btree.note_rename("grandparent/parent/file", 
 
232
        btree.note_rename("grandparent/parent/file",
199
233
                          "grandparent2/parent2/file2")
200
234
        self.assertEqual(btree.id2path("a"), "grandparent2")
201
235
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
205
239
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
206
240
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
207
241
 
208
 
        assert btree.path2id("grandparent2/parent2/file") is None
 
242
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
209
243
 
210
244
    def test_moves(self):
211
245
        """Ensure that file moves have the proper effect on children"""
212
246
        btree = self.make_tree_1()[0]
213
 
        btree.note_rename("grandparent/parent/file", 
 
247
        btree.note_rename("grandparent/parent/file",
214
248
                          "grandparent/alt_parent/file")
215
249
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
216
250
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
217
 
        assert btree.path2id("grandparent/parent/file") is None
 
251
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
218
252
 
219
253
    def unified_diff(self, old, new):
220
254
        out = StringIO()
221
 
        internal_diff("old", old, "new", new, out)
 
255
        diff.internal_diff("old", old, "new", new, out)
222
256
        out.seek(0,0)
223
257
        return out.read()
224
258
 
225
259
    def make_tree_2(self):
226
260
        btree = self.make_tree_1()[0]
227
 
        btree.note_rename("grandparent/parent/file", 
 
261
        btree.note_rename("grandparent/parent/file",
228
262
                          "grandparent/alt_parent/file")
229
 
        assert btree.id2path("e") is None
230
 
        assert btree.path2id("grandparent/parent/file") is None
 
263
        self.assertTrue(btree.id2path("e") is None)
 
264
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
231
265
        btree.note_id("e", "grandparent/parent/file")
232
266
        return btree
233
267
 
259
293
    def make_tree_3(self):
260
294
        btree, mtree = self.make_tree_1()
261
295
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
262
 
        btree.note_rename("grandparent/parent/file", 
 
296
        btree.note_rename("grandparent/parent/file",
263
297
                          "grandparent/alt_parent/file")
264
 
        btree.note_rename("grandparent/parent/topping", 
 
298
        btree.note_rename("grandparent/parent/topping",
265
299
                          "grandparent/alt_parent/stopping")
266
300
        return btree
267
301
 
291
325
        btree = self.make_tree_1()[0]
292
326
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
293
327
        btree.note_deletion("grandparent/parent/file")
294
 
        assert btree.id2path("c") is None
295
 
        assert btree.path2id("grandparent/parent/file") is None
 
328
        self.assertTrue(btree.id2path("c") is None)
 
329
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
296
330
 
297
331
    def sorted_ids(self, tree):
298
 
        ids = list(tree)
 
332
        ids = list(tree.all_file_ids())
299
333
        ids.sort()
300
334
        return ids
301
335
 
306
340
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
307
341
        btree.note_deletion("grandparent/parent/file")
308
342
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
309
 
        btree.note_last_changed("grandparent/alt_parent/fool", 
 
343
        btree.note_last_changed("grandparent/alt_parent/fool",
310
344
                                "revisionidiguess")
311
345
        self.assertEqual(self.sorted_ids(btree),
312
346
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
313
347
 
314
348
 
315
 
class BundleTester1(TestCaseWithTransport):
 
349
class BundleTester1(tests.TestCaseWithTransport):
316
350
 
317
351
    def test_mismatched_bundle(self):
318
352
        format = bzrdir.BzrDirMetaFormat1()
319
353
        format.repository_format = knitrepo.RepositoryFormatKnit3()
320
354
        serializer = BundleSerializerV08('0.8')
321
355
        b = self.make_branch('.', format=format)
322
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
 
356
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
323
357
                          b.repository, [], {}, StringIO())
324
358
 
325
359
    def test_matched_bundle(self):
345
379
        format = bzrdir.BzrDirMetaFormat1()
346
380
        format.repository_format = knitrepo.RepositoryFormatKnit1()
347
381
        target = self.make_branch('target', format=format)
348
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
 
382
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
349
383
                          target.repository, read_bundle(text))
350
384
 
351
385
 
359
393
    def make_branch_and_tree(self, path, format=None):
360
394
        if format is None:
361
395
            format = self.bzrdir_format()
362
 
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
 
396
        return tests.TestCaseWithTransport.make_branch_and_tree(
 
397
            self, path, format)
363
398
 
364
399
    def make_branch(self, path, format=None):
365
400
        if format is None:
366
401
            format = self.bzrdir_format()
367
 
        return TestCaseWithTransport.make_branch(self, path, format)
 
402
        return tests.TestCaseWithTransport.make_branch(self, path, format)
368
403
 
369
404
    def create_bundle_text(self, base_rev_id, rev_id):
370
405
        bundle_txt = StringIO()
371
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
406
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
372
407
                               bundle_txt, format=self.format)
373
408
        bundle_txt.seek(0)
374
 
        self.assertEqual(bundle_txt.readline(), 
 
409
        self.assertEqual(bundle_txt.readline(),
375
410
                         '# Bazaar revision bundle v%s\n' % self.format)
376
411
        self.assertEqual(bundle_txt.readline(), '#\n')
377
412
 
385
420
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
386
421
        Make sure that the text generated is valid, and that it
387
422
        can be applied against the base, and generate the same information.
388
 
        
389
 
        :return: The in-memory bundle 
 
423
 
 
424
        :return: The in-memory bundle
390
425
        """
391
426
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
392
427
 
393
 
        # This should also validate the generated bundle 
 
428
        # This should also validate the generated bundle
394
429
        bundle = read_bundle(bundle_txt)
395
430
        repository = self.b1.repository
396
431
        for bundle_rev in bundle.real_revisions:
400
435
            # it
401
436
            branch_rev = repository.get_revision(bundle_rev.revision_id)
402
437
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
403
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
438
                      'timestamp', 'timezone', 'message', 'committer',
404
439
                      'parent_ids', 'properties'):
405
 
                self.assertEqual(getattr(branch_rev, a), 
 
440
                self.assertEqual(getattr(branch_rev, a),
406
441
                                 getattr(bundle_rev, a))
407
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
442
            self.assertEqual(len(branch_rev.parent_ids),
408
443
                             len(bundle_rev.parent_ids))
409
 
        self.assertEqual(rev_ids, 
 
444
        self.assertEqual(rev_ids,
410
445
                         [r.revision_id for r in bundle.real_revisions])
411
446
        self.valid_apply_bundle(base_rev_id, bundle,
412
447
                                   checkout_dir=checkout_dir)
416
451
    def get_invalid_bundle(self, base_rev_id, rev_id):
417
452
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
418
453
        Munge the text so that it's invalid.
419
 
        
 
454
 
420
455
        :return: The in-memory bundle
421
456
        """
422
457
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
423
 
        new_text = bundle_txt.getvalue().replace('executable:no', 
 
458
        new_text = bundle_txt.getvalue().replace('executable:no',
424
459
                                               'executable:yes')
425
460
        bundle_txt = StringIO(new_text)
426
461
        bundle = read_bundle(bundle_txt)
427
462
        self.valid_apply_bundle(base_rev_id, bundle)
428
 
        return bundle 
 
463
        return bundle
429
464
 
430
465
    def test_non_bundle(self):
431
 
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
 
466
        self.assertRaises(errors.NotABundle,
 
467
                          read_bundle, StringIO('#!/bin/sh\n'))
432
468
 
433
469
    def test_malformed(self):
434
 
        self.assertRaises(BadBundle, read_bundle, 
 
470
        self.assertRaises(errors.BadBundle, read_bundle,
435
471
                          StringIO('# Bazaar revision bundle v'))
436
472
 
437
473
    def test_crlf_bundle(self):
438
474
        try:
439
475
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
440
 
        except BadBundle:
 
476
        except errors.BadBundle:
441
477
            # It is currently permitted for bundles with crlf line endings to
442
478
            # make read_bundle raise a BadBundle, but this should be fixed.
443
479
            # Anything else, especially NotABundle, is an error.
448
484
        """
449
485
 
450
486
        if checkout_dir is None:
451
 
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
 
487
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
452
488
        else:
453
489
            if not os.path.exists(checkout_dir):
454
490
                os.mkdir(checkout_dir)
457
493
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
458
494
                                 format=self.format)
459
495
        s.seek(0)
460
 
        assert isinstance(s.getvalue(), str), (
461
 
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
 
496
        self.assertIsInstance(s.getvalue(), str)
462
497
        install_bundle(tree.branch.repository, read_bundle(s))
463
498
        for ancestor in ancestors:
464
499
            old = self.b1.repository.revision_tree(ancestor)
465
500
            new = tree.branch.repository.revision_tree(ancestor)
466
 
 
467
 
            # Check that there aren't any inventory level changes
468
 
            delta = new.changes_from(old)
469
 
            self.assertFalse(delta.has_changed(),
470
 
                             'Revision %s not copied correctly.'
471
 
                             % (ancestor,))
472
 
 
473
 
            # Now check that the file contents are all correct
474
 
            for inventory_id in old:
475
 
                try:
476
 
                    old_file = old.get_file(inventory_id)
477
 
                except NoSuchFile:
478
 
                    continue
479
 
                if old_file is None:
480
 
                    continue
481
 
                self.assertEqual(old_file.read(),
482
 
                                 new.get_file(inventory_id).read())
 
501
            old.lock_read()
 
502
            new.lock_read()
 
503
            try:
 
504
                # Check that there aren't any inventory level changes
 
505
                delta = new.changes_from(old)
 
506
                self.assertFalse(delta.has_changed(),
 
507
                                 'Revision %s not copied correctly.'
 
508
                                 % (ancestor,))
 
509
 
 
510
                # Now check that the file contents are all correct
 
511
                for inventory_id in old.all_file_ids():
 
512
                    try:
 
513
                        old_file = old.get_file(inventory_id)
 
514
                    except errors.NoSuchFile:
 
515
                        continue
 
516
                    if old_file is None:
 
517
                        continue
 
518
                    self.assertEqual(old_file.read(),
 
519
                                     new.get_file(inventory_id).read())
 
520
            finally:
 
521
                new.unlock()
 
522
                old.unlock()
483
523
        if not _mod_revision.is_null(rev_id):
484
 
            rh = self.b1.revision_history()
485
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
 
524
            tree.branch.generate_revision_history(rev_id)
486
525
            tree.update()
487
526
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
488
527
            self.assertFalse(delta.has_changed(),
494
533
        sure everything matches the builtin branch.
495
534
        """
496
535
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
 
536
        to_tree.lock_write()
 
537
        try:
 
538
            self._valid_apply_bundle(base_rev_id, info, to_tree)
 
539
        finally:
 
540
            to_tree.unlock()
 
541
 
 
542
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
497
543
        original_parents = to_tree.get_parent_ids()
498
544
        repository = to_tree.branch.repository
499
545
        original_parents = to_tree.get_parent_ids()
500
546
        self.assertIs(repository.has_revision(base_rev_id), True)
501
547
        for rev in info.real_revisions:
502
 
            self.assert_(not repository.has_revision(rev.revision_id),
503
 
                'Revision {%s} present before applying bundle' 
504
 
                % rev.revision_id)
505
 
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
 
548
            self.assertTrue(not repository.has_revision(rev.revision_id),
 
549
                            'Revision {%s} present before applying bundle'
 
550
                            % rev.revision_id)
 
551
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
506
552
 
507
553
        for rev in info.real_revisions:
508
 
            self.assert_(repository.has_revision(rev.revision_id),
509
 
                'Missing revision {%s} after applying bundle' 
510
 
                % rev.revision_id)
 
554
            self.assertTrue(repository.has_revision(rev.revision_id),
 
555
                            'Missing revision {%s} after applying bundle'
 
556
                            % rev.revision_id)
511
557
 
512
 
        self.assert_(to_tree.branch.repository.has_revision(info.target))
 
558
        self.assertTrue(to_tree.branch.repository.has_revision(info.target))
513
559
        # Do we also want to verify that all the texts have been added?
514
560
 
515
561
        self.assertEqual(original_parents + [info.target],
516
 
            to_tree.get_parent_ids())
 
562
                         to_tree.get_parent_ids())
517
563
 
518
564
        rev = info.real_revisions[-1]
519
565
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
520
566
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
521
 
        
 
567
 
522
568
        # TODO: make sure the target tree is identical to base tree
523
569
        #       we might also check the working tree.
524
570
 
543
589
        self.tree1 = self.make_branch_and_tree('b1')
544
590
        self.b1 = self.tree1.branch
545
591
 
546
 
        open('b1/one', 'wb').write('one\n')
547
 
        self.tree1.add('one')
 
592
        self.build_tree_contents([('b1/one', 'one\n')])
 
593
        self.tree1.add('one', 'one-id')
 
594
        self.tree1.set_root_id('root-id')
548
595
        self.tree1.commit('add one', rev_id='a@cset-0-1')
549
596
 
550
597
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
561
608
                , 'b1/sub/sub/'
562
609
                , 'b1/sub/sub/nonempty.txt'
563
610
                ])
564
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
565
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
 
611
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
 
612
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
566
613
        tt = TreeTransform(self.tree1)
567
614
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
568
615
        tt.apply()
584
631
 
585
632
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
586
633
 
587
 
        # Check a rollup bundle 
 
634
        # Check a rollup bundle
588
635
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
589
636
 
590
637
        # Now delete entries
598
645
        tt.set_executability(False, trans_id)
599
646
        tt.apply()
600
647
        self.tree1.commit('removed', rev_id='a@cset-0-3')
601
 
        
 
648
 
602
649
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
603
 
        self.assertRaises((TestamentMismatch,
604
 
            errors.VersionedFileInvalidChecksum), self.get_invalid_bundle,
 
650
        self.assertRaises((errors.TestamentMismatch,
 
651
            errors.VersionedFileInvalidChecksum,
 
652
            errors.BadBundle), self.get_invalid_bundle,
605
653
            'a@cset-0-2', 'a@cset-0-3')
606
 
        # Check a rollup bundle 
 
654
        # Check a rollup bundle
607
655
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
608
656
 
609
657
        # Now move the directory
611
659
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
612
660
 
613
661
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
614
 
        # Check a rollup bundle 
 
662
        # Check a rollup bundle
615
663
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
616
664
 
617
665
        # Modified files
618
 
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
619
 
        open('b1/sub/dir/ pre space', 'ab').write(
 
666
        with open('b1/sub/dir/WithCaps.txt', 'ab') as f: f.write('\nAdding some text\n')
 
667
        with open('b1/sub/dir/ pre space', 'ab') as f: f.write(
620
668
             '\r\nAdding some\r\nDOS format lines\r\n')
621
 
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
622
 
        self.tree1.rename_one('sub/dir/ pre space', 
 
669
        with open('b1/sub/dir/nolastnewline.txt', 'ab') as f: f.write('\n')
 
670
        self.tree1.rename_one('sub/dir/ pre space',
623
671
                              'sub/ start space')
624
672
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
625
673
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
631
679
                          verbose=False)
632
680
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
633
681
        other = self.get_checkout('a@cset-0-5')
634
 
        tree1_inv = self.tree1.branch.repository.get_inventory_xml(
635
 
            'a@cset-0-5')
636
 
        tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
 
682
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
 
683
                                       'a@cset-0-5')
 
684
        tree2_inv = get_inventory_text(other.branch.repository,
 
685
                                       'a@cset-0-5')
637
686
        self.assertEqualDiff(tree1_inv, tree2_inv)
638
687
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
639
688
        other.commit('rename file', rev_id='a@cset-0-6b')
640
 
        _merge_helper([other.basedir, -1], [None, None],
641
 
                      this_dir=self.tree1.basedir)
 
689
        self.tree1.merge_from_branch(other.branch)
642
690
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
643
691
                          verbose=False)
644
692
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
645
693
 
646
 
    def test_symlink_bundle(self):
647
 
        if not has_symlinks():
648
 
            raise TestSkipped("No symlink support")
 
694
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
 
695
        link_id = 'link-1'
 
696
 
 
697
        self.requireFeature(features.SymlinkFeature)
649
698
        self.tree1 = self.make_branch_and_tree('b1')
650
699
        self.b1 = self.tree1.branch
 
700
 
651
701
        tt = TreeTransform(self.tree1)
652
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
 
702
        tt.new_symlink(link_name, tt.root, link_target, link_id)
653
703
        tt.apply()
654
704
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
655
 
        self.get_valid_bundle('null:', 'l@cset-0-1')
 
705
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
 
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-1')
 
709
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
 
710
 
656
711
        tt = TreeTransform(self.tree1)
657
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
712
        trans_id = tt.trans_id_tree_file_id(link_id)
658
713
        tt.adjust_path('link2', tt.root, trans_id)
659
714
        tt.delete_contents(trans_id)
660
 
        tt.create_symlink('mars', trans_id)
 
715
        tt.create_symlink(new_link_target, trans_id)
661
716
        tt.apply()
662
717
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
663
 
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
718
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
719
        if getattr(bundle ,'revision_tree', None) is not None:
 
720
            # Not all bundle formats supports revision_tree
 
721
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
 
722
            self.assertEqual(new_link_target,
 
723
                             bund_tree.get_symlink_target(link_id))
 
724
 
664
725
        tt = TreeTransform(self.tree1)
665
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
726
        trans_id = tt.trans_id_tree_file_id(link_id)
666
727
        tt.delete_contents(trans_id)
667
728
        tt.create_symlink('jupiter', trans_id)
668
729
        tt.apply()
669
730
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
670
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
731
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
732
 
671
733
        tt = TreeTransform(self.tree1)
672
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
734
        trans_id = tt.trans_id_tree_file_id(link_id)
673
735
        tt.delete_contents(trans_id)
674
736
        tt.apply()
675
737
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
676
 
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
738
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
739
 
 
740
    def test_symlink_bundle(self):
 
741
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
 
742
 
 
743
    def test_unicode_symlink_bundle(self):
 
744
        self.requireFeature(features.UnicodeFilenameFeature)
 
745
        self._test_symlink_bundle(u'\N{Euro Sign}link',
 
746
                                  u'bar/\N{Euro Sign}foo',
 
747
                                  u'mars\N{Euro Sign}')
677
748
 
678
749
    def test_binary_bundle(self):
679
750
        self.tree1 = self.make_branch_and_tree('b1')
680
751
        self.b1 = self.tree1.branch
681
752
        tt = TreeTransform(self.tree1)
682
 
        
 
753
 
683
754
        # Add
684
755
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
685
756
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
740
811
        tt.create_file('file2', trans_id)
741
812
        tt.apply()
742
813
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
743
 
        _merge_helper([other.basedir, -1], [None, None],
744
 
                      this_dir=self.tree1.basedir)
 
814
        self.tree1.merge_from_branch(other.branch)
745
815
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
746
816
                          verbose=False)
747
817
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
751
821
        self.tree1 = self.make_branch_and_tree('b1')
752
822
        self.b1 = self.tree1.branch
753
823
 
754
 
        open('b1/one', 'wb').write('one\n')
 
824
        with open('b1/one', 'wb') as f: f.write('one\n')
755
825
        self.tree1.add('one')
756
826
        self.tree1.commit('add file', rev_id='a@cset-0-1')
757
 
        open('b1/one', 'wb').write('two\n')
 
827
        with open('b1/one', 'wb') as f: f.write('two\n')
758
828
        self.tree1.commit('modify', rev_id='a@cset-0-2')
759
 
        open('b1/one', 'wb').write('three\n')
 
829
        with open('b1/one', 'wb') as f: f.write('three\n')
760
830
        self.tree1.commit('modify', rev_id='a@cset-0-3')
761
831
        bundle_file = StringIO()
762
832
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
778
848
        return bundle_file.getvalue()
779
849
 
780
850
    def test_unicode_bundle(self):
 
851
        self.requireFeature(features.UnicodeFilenameFeature)
781
852
        # Handle international characters
782
853
        os.mkdir('b1')
783
 
        try:
784
 
            f = open(u'b1/with Dod\xe9', 'wb')
785
 
        except UnicodeEncodeError:
786
 
            raise TestSkipped("Filesystem doesn't support unicode")
 
854
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
787
855
 
788
856
        self.tree1 = self.make_branch_and_tree('b1')
789
857
        self.b1 = self.tree1.branch
793
861
            u'William Dod\xe9\n').encode('utf-8'))
794
862
        f.close()
795
863
 
796
 
        self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
 
864
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
797
865
        self.tree1.commit(u'i18n commit from William Dod\xe9',
798
866
                          rev_id='i18n-1', committer=u'William Dod\xe9')
799
867
 
800
 
        if sys.platform == 'darwin':
801
 
            # On Mac the '\xe9' gets changed to 'e\u0301'
802
 
            self.assertEqual([u'.bzr', u'with Dode\u0301'],
803
 
                             sorted(os.listdir(u'b1')))
804
 
            delta = self.tree1.changes_from(self.tree1.basis_tree())
805
 
            self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
806
 
                             delta.removed)
807
 
            self.knownFailure("Mac OSX doesn't preserve unicode"
808
 
                              " combining characters.")
809
 
 
810
868
        # Add
811
869
        bundle = self.get_valid_bundle('null:', 'i18n-1')
812
870
 
813
871
        # Modified
814
 
        f = open(u'b1/with Dod\xe9', 'wb')
 
872
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
815
873
        f.write(u'Modified \xb5\n'.encode('utf8'))
816
874
        f.close()
817
875
        self.tree1.commit(u'modified', rev_id='i18n-2')
818
876
 
819
877
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
820
 
        
 
878
 
821
879
        # Renamed
822
 
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
 
880
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
823
881
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
824
882
                          committer=u'Erik B\xe5gfors')
825
883
 
826
884
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
827
885
 
828
886
        # Removed
829
 
        self.tree1.remove([u'B\xe5gfors'])
 
887
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
830
888
        self.tree1.commit(u'removed', rev_id='i18n-4')
831
889
 
832
890
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
837
895
 
838
896
    def test_whitespace_bundle(self):
839
897
        if sys.platform in ('win32', 'cygwin'):
840
 
            raise TestSkipped('Windows doesn\'t support filenames'
841
 
                              ' with tabs or trailing spaces')
 
898
            raise tests.TestSkipped('Windows doesn\'t support filenames'
 
899
                                    ' with tabs or trailing spaces')
842
900
        self.tree1 = self.make_branch_and_tree('b1')
843
901
        self.b1 = self.tree1.branch
844
902
 
853
911
        bundle = self.get_valid_bundle('null:', 'white-1')
854
912
 
855
913
        # Modified
856
 
        open('b1/trailing space ', 'ab').write('add some text\n')
 
914
        with open('b1/trailing space ', 'ab') as f: f.write('add some text\n')
857
915
        self.tree1.commit('add text', rev_id='white-2')
858
916
 
859
917
        bundle = self.get_valid_bundle('white-1', 'white-2')
869
927
        self.tree1.commit('removed', rev_id='white-4')
870
928
 
871
929
        bundle = self.get_valid_bundle('white-3', 'white-4')
872
 
        
 
930
 
873
931
        # Now test a complet roll-up
874
932
        bundle = self.get_valid_bundle('null:', 'white-4')
875
933
 
888
946
                          timezone=19800, timestamp=1152544886.0)
889
947
 
890
948
        bundle = self.get_valid_bundle('null:', 'tz-1')
891
 
        
 
949
 
892
950
        rev = bundle.revisions[0]
893
951
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
894
952
        self.assertEqual(19800, rev.timezone)
901
959
        self.tree1.commit('message', rev_id='revid1')
902
960
        bundle = self.get_valid_bundle('null:', 'revid1')
903
961
        tree = self.get_bundle_tree(bundle, 'revid1')
904
 
        self.assertEqual('revid1', tree.inventory.root.revision)
 
962
        root_revision = tree.get_file_revision(tree.get_root_id())
 
963
        self.assertEqual('revid1', root_revision)
905
964
 
906
965
    def test_install_revisions(self):
907
966
        self.tree1 = self.make_branch_and_tree('b1')
996
1055
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
997
1056
        repo = self.make_repository('repo', format='dirstate-with-subtree')
998
1057
        bundle.install_revisions(repo)
999
 
        inv_text = repo.get_inventory_xml('rev2')
 
1058
        inv_text = repo._get_inventory_xml('rev2')
1000
1059
        self.assertNotContainsRe(inv_text, 'format="5"')
1001
1060
        self.assertContainsRe(inv_text, 'format="7"')
1002
1061
 
 
1062
    def make_repo_with_installed_revisions(self):
 
1063
        tree = self.make_simple_tree('knit')
 
1064
        tree.commit('hello', rev_id='rev1')
 
1065
        tree.commit('hello', rev_id='rev2')
 
1066
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1067
        repo = self.make_repository('repo', format='dirstate-with-subtree')
 
1068
        bundle.install_revisions(repo)
 
1069
        return repo
 
1070
 
1003
1071
    def test_across_models(self):
1004
 
        tree = self.make_simple_tree('knit')
1005
 
        tree.commit('hello', rev_id='rev1')
1006
 
        tree.commit('hello', rev_id='rev2')
1007
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1008
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1009
 
        bundle.install_revisions(repo)
 
1072
        repo = self.make_repo_with_installed_revisions()
1010
1073
        inv = repo.get_inventory('rev2')
1011
1074
        self.assertEqual('rev2', inv.root.revision)
1012
 
        root_vf = repo.weave_store.get_weave(inv.root.file_id,
1013
 
                                             repo.get_transaction())
1014
 
        self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
 
1075
        root_id = inv.root.file_id
 
1076
        repo.lock_read()
 
1077
        self.addCleanup(repo.unlock)
 
1078
        self.assertEqual({(root_id, 'rev1'):(),
 
1079
            (root_id, 'rev2'):((root_id, 'rev1'),)},
 
1080
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
 
1081
 
 
1082
    def test_inv_hash_across_serializers(self):
 
1083
        repo = self.make_repo_with_installed_revisions()
 
1084
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
 
1085
        xml = repo._get_inventory_xml('rev2')
 
1086
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1015
1087
 
1016
1088
    def test_across_models_incompatible(self):
1017
1089
        tree = self.make_simple_tree('dirstate-with-subtree')
1020
1092
        try:
1021
1093
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1022
1094
        except errors.IncompatibleBundleFormat:
1023
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1095
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1024
1096
        repo = self.make_repository('repo', format='knit')
1025
1097
        bundle.install_revisions(repo)
1026
1098
 
1047
1119
        try:
1048
1120
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1049
1121
        except errors.IncompatibleBundleFormat:
1050
 
            raise TestSkipped("Format 0.8 doesn't work with knit3")
 
1122
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1051
1123
        if isinstance(bundle, v09.BundleInfo09):
1052
 
            raise TestSkipped("Format 0.9 doesn't work with subtrees")
 
1124
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
1053
1125
        repo = self.make_repository('repo', format='knit')
1054
1126
        self.assertRaises(errors.IncompatibleRevision,
1055
1127
                          bundle.install_revisions, repo)
1062
1134
        try:
1063
1135
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1064
1136
        except ValueError:
1065
 
            raise TestSkipped("Repository doesn't support revision ids with"
1066
 
                              " slashes")
 
1137
            raise tests.TestSkipped(
 
1138
                "Repository doesn't support revision ids with slashes")
1067
1139
        bundle = self.get_valid_bundle('null:', 'rev/id')
1068
1140
 
1069
 
 
1070
 
class V08BundleTester(BundleTester, TestCaseWithTransport):
 
1141
    def test_skip_file(self):
 
1142
        """Make sure we don't accidentally write to the wrong versionedfile"""
 
1143
        self.tree1 = self.make_branch_and_tree('tree')
 
1144
        self.b1 = self.tree1.branch
 
1145
        # rev1 is not present in bundle, done by fetch
 
1146
        self.build_tree_contents([('tree/file2', 'contents1')])
 
1147
        self.tree1.add('file2', 'file2-id')
 
1148
        self.tree1.commit('rev1', rev_id='reva')
 
1149
        self.build_tree_contents([('tree/file3', 'contents2')])
 
1150
        # rev2 is present in bundle, and done by fetch
 
1151
        # having file1 in the bunle causes file1's versionedfile to be opened.
 
1152
        self.tree1.add('file3', 'file3-id')
 
1153
        self.tree1.commit('rev2')
 
1154
        # Updating file2 should not cause an attempt to add to file1's vf
 
1155
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
 
1156
        self.build_tree_contents([('tree/file2', 'contents3')])
 
1157
        self.tree1.commit('rev3', rev_id='rev3')
 
1158
        bundle = self.get_valid_bundle('reva', 'rev3')
 
1159
        if getattr(bundle, 'get_bundle_reader', None) is None:
 
1160
            raise tests.TestSkipped('Bundle format cannot provide reader')
 
1161
        # be sure that file1 comes before file2
 
1162
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
 
1163
            if f == 'file3-id':
 
1164
                break
 
1165
            self.assertNotEqual(f, 'file2-id')
 
1166
        bundle.install_revisions(target.branch.repository)
 
1167
 
 
1168
 
 
1169
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
1071
1170
 
1072
1171
    format = '0.8'
1073
1172
 
1206
1305
        return format
1207
1306
 
1208
1307
 
1209
 
class V4BundleTester(BundleTester, TestCaseWithTransport):
 
1308
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
1210
1309
 
1211
1310
    format = '4'
1212
1311
 
1214
1313
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1215
1314
        Make sure that the text generated is valid, and that it
1216
1315
        can be applied against the base, and generate the same information.
1217
 
        
1218
 
        :return: The in-memory bundle 
 
1316
 
 
1317
        :return: The in-memory bundle
1219
1318
        """
1220
1319
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1221
1320
 
1222
 
        # This should also validate the generated bundle 
 
1321
        # This should also validate the generated bundle
1223
1322
        bundle = read_bundle(bundle_txt)
1224
1323
        repository = self.b1.repository
1225
1324
        for bundle_rev in bundle.real_revisions:
1229
1328
            # it
1230
1329
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1231
1330
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1232
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
1331
                      'timestamp', 'timezone', 'message', 'committer',
1233
1332
                      'parent_ids', 'properties'):
1234
 
                self.assertEqual(getattr(branch_rev, a), 
 
1333
                self.assertEqual(getattr(branch_rev, a),
1235
1334
                                 getattr(bundle_rev, a))
1236
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
1335
            self.assertEqual(len(branch_rev.parent_ids),
1237
1336
                             len(bundle_rev.parent_ids))
1238
1337
        self.assertEqual(set(rev_ids),
1239
1338
                         set([r.revision_id for r in bundle.real_revisions]))
1253
1352
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1254
1353
        new_text = new_text.replace('<file file_id="exe-1"',
1255
1354
                                    '<file executable="y" file_id="exe-1"')
1256
 
        new_text = new_text.replace('B372', 'B387')
 
1355
        new_text = new_text.replace('B260', 'B275')
1257
1356
        bundle_txt = StringIO()
1258
1357
        bundle_txt.write(serializer._get_bundle_header('4'))
1259
1358
        bundle_txt.write('\n')
1265
1364
 
1266
1365
    def create_bundle_text(self, base_rev_id, rev_id):
1267
1366
        bundle_txt = StringIO()
1268
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
 
1367
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1269
1368
                               bundle_txt, format=self.format)
1270
1369
        bundle_txt.seek(0)
1271
 
        self.assertEqual(bundle_txt.readline(), 
 
1370
        self.assertEqual(bundle_txt.readline(),
1272
1371
                         '# Bazaar revision bundle v%s\n' % self.format)
1273
1372
        self.assertEqual(bundle_txt.readline(), '#\n')
1274
1373
        rev = self.b1.repository.get_revision(rev_id)
1294
1393
        tree2 = self.make_branch_and_tree('target')
1295
1394
        target_repo = tree2.branch.repository
1296
1395
        install_bundle(target_repo, serializer.read(s))
1297
 
        vf = target_repo.weave_store.get_weave('fileid-2',
1298
 
            target_repo.get_transaction())
1299
 
        self.assertEqual('contents1\nstatic\n', vf.get_text('rev1'))
1300
 
        self.assertEqual('contents2\nstatic\n', vf.get_text('rev2'))
 
1396
        target_repo.lock_read()
 
1397
        self.addCleanup(target_repo.unlock)
 
1398
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
 
1399
        repo_texts = dict((i, ''.join(content)) for i, content
 
1400
                          in target_repo.iter_files_bytes(
 
1401
                                [('fileid-2', 'rev1', '1'),
 
1402
                                 ('fileid-2', 'rev2', '2')]))
 
1403
        self.assertEqual({'1':'contents1\nstatic\n',
 
1404
                          '2':'contents2\nstatic\n'},
 
1405
                         repo_texts)
1301
1406
        rtree = target_repo.revision_tree('rev2')
1302
 
        inventory_vf = target_repo.get_inventory_weave()
1303
 
        self.assertEqual(['rev1'], inventory_vf.get_parents('rev2'))
 
1407
        inventory_vf = target_repo.inventories
 
1408
        # If the inventory store has a graph, it must match the revision graph.
 
1409
        self.assertSubset(
 
1410
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
 
1411
            [None, (('rev1',),)])
1304
1412
        self.assertEqual('changed file',
1305
1413
                         target_repo.get_revision('rev2').message)
1306
1414
 
1320
1428
        branch = tree_a.branch
1321
1429
        repo_a = branch.repository
1322
1430
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1323
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1431
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
1324
1432
        try:
1325
1433
            from bzrlib.testament import Testament
1326
1434
            # monkey patch gpg signing mechanism
1327
1435
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1328
 
            new_config = test_commit.MustSignConfig(branch)
1329
 
            commit.Commit(config=new_config).commit(message="base",
 
1436
            new_config = test_commit.MustSignConfig()
 
1437
            commit.Commit(config_stack=new_config).commit(message="base",
1330
1438
                                                    allow_pointless=True,
1331
1439
                                                    rev_id='B',
1332
1440
                                                    working_tree=tree_a)
1350
1458
        install_bundle(repo_b, serializer.read(s))
1351
1459
 
1352
1460
 
1353
 
class V4WeaveBundleTester(V4BundleTester):
 
1461
class V4_2aBundleTester(V4BundleTester):
1354
1462
 
1355
1463
    def bzrdir_format(self):
1356
 
        return 'metaweave'
 
1464
        return '2a'
 
1465
 
 
1466
    def get_invalid_bundle(self, base_rev_id, rev_id):
 
1467
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
 
1468
        Munge the text so that it's invalid.
 
1469
 
 
1470
        :return: The in-memory bundle
 
1471
        """
 
1472
        from bzrlib.bundle import serializer
 
1473
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
 
1474
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1475
        # We are going to be replacing some text to set the executable bit on a
 
1476
        # file. Make sure the text replacement actually works correctly.
 
1477
        self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
 
1478
        new_text = new_text.replace('<file file_id="exe-1"',
 
1479
                                    '<file executable="y" file_id="exe-1"')
 
1480
        new_text = new_text.replace('B244', 'B259')
 
1481
        bundle_txt = StringIO()
 
1482
        bundle_txt.write(serializer._get_bundle_header('4'))
 
1483
        bundle_txt.write('\n')
 
1484
        bundle_txt.write(new_text.encode('bz2'))
 
1485
        bundle_txt.seek(0)
 
1486
        bundle = read_bundle(bundle_txt)
 
1487
        self.valid_apply_bundle(base_rev_id, bundle)
 
1488
        return bundle
 
1489
 
 
1490
    def make_merged_branch(self):
 
1491
        builder = self.make_branch_builder('source')
 
1492
        builder.start_series()
 
1493
        builder.build_snapshot('a@cset-0-1', None, [
 
1494
            ('add', ('', 'root-id', 'directory', None)),
 
1495
            ('add', ('file', 'file-id', 'file', 'original content\n')),
 
1496
            ])
 
1497
        builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
 
1498
            ('modify', ('file-id', 'new-content\n')),
 
1499
            ])
 
1500
        builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
 
1501
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1502
            ])
 
1503
        builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
 
1504
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
 
1505
            ])
 
1506
        builder.finish_series()
 
1507
        self.b1 = builder.get_branch()
 
1508
        self.b1.lock_read()
 
1509
        self.addCleanup(self.b1.unlock)
 
1510
 
 
1511
    def make_bundle_just_inventories(self, base_revision_id,
 
1512
                                     target_revision_id,
 
1513
                                     revision_ids):
 
1514
        sio = StringIO()
 
1515
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
 
1516
                                         self.b1.repository, sio)
 
1517
        writer.bundle.begin()
 
1518
        writer._add_inventory_mpdiffs_from_serializer(revision_ids)
 
1519
        writer.bundle.end()
 
1520
        sio.seek(0)
 
1521
        return sio
 
1522
 
 
1523
    def test_single_inventory_multiple_parents_as_xml(self):
 
1524
        self.make_merged_branch()
 
1525
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1526
                                                ['a@cset-0-3'])
 
1527
        reader = v4.BundleReader(sio, stream_input=False)
 
1528
        records = list(reader.iter_records())
 
1529
        self.assertEqual(1, len(records))
 
1530
        (bytes, metadata, repo_kind, revision_id,
 
1531
         file_id) = records[0]
 
1532
        self.assertIs(None, file_id)
 
1533
        self.assertEqual('a@cset-0-3', revision_id)
 
1534
        self.assertEqual('inventory', repo_kind)
 
1535
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1536
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1537
                          'storage_kind': 'mpdiff',
 
1538
                         }, metadata)
 
1539
        # We should have an mpdiff that takes some lines from both parents.
 
1540
        self.assertEqualDiff(
 
1541
            'i 1\n'
 
1542
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1543
            '\n'
 
1544
            'c 0 1 1 2\n'
 
1545
            'c 1 3 3 2\n', bytes)
 
1546
 
 
1547
    def test_single_inv_no_parents_as_xml(self):
 
1548
        self.make_merged_branch()
 
1549
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
 
1550
                                                ['a@cset-0-1'])
 
1551
        reader = v4.BundleReader(sio, stream_input=False)
 
1552
        records = list(reader.iter_records())
 
1553
        self.assertEqual(1, len(records))
 
1554
        (bytes, metadata, repo_kind, revision_id,
 
1555
         file_id) = records[0]
 
1556
        self.assertIs(None, file_id)
 
1557
        self.assertEqual('a@cset-0-1', revision_id)
 
1558
        self.assertEqual('inventory', repo_kind)
 
1559
        self.assertEqual({'parents': [],
 
1560
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
 
1561
                          'storage_kind': 'mpdiff',
 
1562
                         }, metadata)
 
1563
        # We should have an mpdiff that takes some lines from both parents.
 
1564
        self.assertEqualDiff(
 
1565
            'i 4\n'
 
1566
            '<inventory format="10" revision_id="a@cset-0-1">\n'
 
1567
            '<directory file_id="root-id" name=""'
 
1568
                ' revision="a@cset-0-1" />\n'
 
1569
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1570
                ' revision="a@cset-0-1"'
 
1571
                ' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
 
1572
                ' text_size="17" />\n'
 
1573
            '</inventory>\n'
 
1574
            '\n', bytes)
 
1575
 
 
1576
    def test_multiple_inventories_as_xml(self):
 
1577
        self.make_merged_branch()
 
1578
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
 
1579
            ['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
 
1580
        reader = v4.BundleReader(sio, stream_input=False)
 
1581
        records = list(reader.iter_records())
 
1582
        self.assertEqual(3, len(records))
 
1583
        revision_ids = [rev_id for b, m, k, rev_id, f in records]
 
1584
        self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
 
1585
                         revision_ids)
 
1586
        metadata_2a = records[0][1]
 
1587
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1588
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
 
1589
                          'storage_kind': 'mpdiff',
 
1590
                         }, metadata_2a)
 
1591
        metadata_2b = records[1][1]
 
1592
        self.assertEqual({'parents': ['a@cset-0-1'],
 
1593
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
 
1594
                          'storage_kind': 'mpdiff',
 
1595
                         }, metadata_2b)
 
1596
        metadata_3 = records[2][1]
 
1597
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
 
1598
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1599
                          'storage_kind': 'mpdiff',
 
1600
                         }, metadata_3)
 
1601
        bytes_2a = records[0][0]
 
1602
        self.assertEqualDiff(
 
1603
            'i 1\n'
 
1604
            '<inventory format="10" revision_id="a@cset-0-2a">\n'
 
1605
            '\n'
 
1606
            'c 0 1 1 1\n'
 
1607
            'i 1\n'
 
1608
            '<file file_id="file-id" name="file" parent_id="root-id"'
 
1609
                ' revision="a@cset-0-2a"'
 
1610
                ' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
 
1611
                ' text_size="12" />\n'
 
1612
            '\n'
 
1613
            'c 0 3 3 1\n', bytes_2a)
 
1614
        bytes_2b = records[1][0]
 
1615
        self.assertEqualDiff(
 
1616
            'i 1\n'
 
1617
            '<inventory format="10" revision_id="a@cset-0-2b">\n'
 
1618
            '\n'
 
1619
            'c 0 1 1 2\n'
 
1620
            'i 1\n'
 
1621
            '<file file_id="file2-id" name="other-file" parent_id="root-id"'
 
1622
                ' revision="a@cset-0-2b"'
 
1623
                ' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
 
1624
                ' text_size="14" />\n'
 
1625
            '\n'
 
1626
            'c 0 3 4 1\n', bytes_2b)
 
1627
        bytes_3 = records[2][0]
 
1628
        self.assertEqualDiff(
 
1629
            'i 1\n'
 
1630
            '<inventory format="10" revision_id="a@cset-0-3">\n'
 
1631
            '\n'
 
1632
            'c 0 1 1 2\n'
 
1633
            'c 1 3 3 2\n', bytes_3)
 
1634
 
 
1635
    def test_creating_bundle_preserves_chk_pages(self):
 
1636
        self.make_merged_branch()
 
1637
        target = self.b1.bzrdir.sprout('target',
 
1638
                                       revision_id='a@cset-0-2a').open_branch()
 
1639
        bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
 
1640
                                                      'a@cset-0-3')
 
1641
        self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
 
1642
        bundle = read_bundle(bundle_txt)
 
1643
        target.lock_write()
 
1644
        self.addCleanup(target.unlock)
 
1645
        install_bundle(target.repository, bundle)
 
1646
        inv1 = self.b1.repository.inventories.get_record_stream([
 
1647
            ('a@cset-0-3',)], 'unordered',
 
1648
            True).next().get_bytes_as('fulltext')
 
1649
        inv2 = target.repository.inventories.get_record_stream([
 
1650
            ('a@cset-0-3',)], 'unordered',
 
1651
            True).next().get_bytes_as('fulltext')
 
1652
        self.assertEqualDiff(inv1, inv2)
1357
1653
 
1358
1654
 
1359
1655
class MungedBundleTester(object):
1408
1704
        self.check_valid(bundle)
1409
1705
 
1410
1706
 
1411
 
class MungedBundleTesterV09(TestCaseWithTransport, MungedBundleTester):
 
1707
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
1412
1708
 
1413
1709
    format = '0.9'
1414
1710
 
1446
1742
        self.check_valid(bundle)
1447
1743
 
1448
1744
 
1449
 
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
 
1745
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
1450
1746
 
1451
1747
    format = '4'
1452
1748
 
1453
1749
 
1454
 
class TestBundleWriterReader(TestCase):
 
1750
class TestBundleWriterReader(tests.TestCase):
1455
1751
 
1456
1752
    def test_roundtrip_record(self):
1457
1753
        fileobj = StringIO()
1462
1758
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1463
1759
        writer.end()
1464
1760
        fileobj.seek(0)
1465
 
        record_iter = v4.BundleReader(fileobj).iter_records()
 
1761
        reader = v4.BundleReader(fileobj, stream_input=True)
 
1762
        record_iter = reader.iter_records()
 
1763
        record = record_iter.next()
 
1764
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
 
1765
            'info', None, None), record)
 
1766
        record = record_iter.next()
 
1767
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
 
1768
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
 
1769
                          record)
 
1770
 
 
1771
    def test_roundtrip_record_memory_hungry(self):
 
1772
        fileobj = StringIO()
 
1773
        writer = v4.BundleWriter(fileobj)
 
1774
        writer.begin()
 
1775
        writer.add_info_record(foo='bar')
 
1776
        writer._add_record("Record body", {'parents': ['1', '3'],
 
1777
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1778
        writer.end()
 
1779
        fileobj.seek(0)
 
1780
        reader = v4.BundleReader(fileobj, stream_input=False)
 
1781
        record_iter = reader.iter_records()
1466
1782
        record = record_iter.next()
1467
1783
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1468
1784
            'info', None, None), record)
1499
1815
        record = record_iter.next()
1500
1816
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1501
1817
            'info', None, None), record)
1502
 
        self.assertRaises(BadBundle, record_iter.next)
 
1818
        self.assertRaises(errors.BadBundle, record_iter.next)
 
1819
 
 
1820
 
 
1821
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
 
1822
 
 
1823
    def test_read_mergeable_skips_local(self):
 
1824
        """A local bundle named like the URL should not be read.
 
1825
        """
 
1826
        out, wt = test_read_bundle.create_bundle_file(self)
 
1827
        class FooService(object):
 
1828
            """A directory service that always returns source"""
 
1829
 
 
1830
            def look_up(self, name, url):
 
1831
                return 'source'
 
1832
        directories.register('foo:', FooService, 'Testing directory service')
 
1833
        self.addCleanup(directories.remove, 'foo:')
 
1834
        self.build_tree_contents([('./foo:bar', out.getvalue())])
 
1835
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
 
1836
                          'foo:bar')
 
1837
 
 
1838
    def test_infinite_redirects_are_not_a_bundle(self):
 
1839
        """If a URL causes TooManyRedirections then NotABundle is raised.
 
1840
        """
 
1841
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
 
1842
        server = RedirectingMemoryServer()
 
1843
        self.start_server(server)
 
1844
        url = server.get_url() + 'infinite-loop'
 
1845
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
 
1846
 
 
1847
    def test_smart_server_connection_reset(self):
 
1848
        """If a smart server connection fails during the attempt to read a
 
1849
        bundle, then the ConnectionReset error should be propagated.
 
1850
        """
 
1851
        # Instantiate a server that will provoke a ConnectionReset
 
1852
        sock_server = DisconnectingServer()
 
1853
        self.start_server(sock_server)
 
1854
        # We don't really care what the url is since the server will close the
 
1855
        # connection without interpreting it
 
1856
        url = sock_server.get_url()
 
1857
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
 
1858
 
 
1859
 
 
1860
class DisconnectingHandler(SocketServer.BaseRequestHandler):
 
1861
    """A request handler that immediately closes any connection made to it."""
 
1862
 
 
1863
    def handle(self):
 
1864
        self.request.close()
 
1865
 
 
1866
 
 
1867
class DisconnectingServer(test_server.TestingTCPServerInAThread):
 
1868
 
 
1869
    def __init__(self):
 
1870
        super(DisconnectingServer, self).__init__(
 
1871
            ('127.0.0.1', 0),
 
1872
            test_server.TestingTCPServer,
 
1873
            DisconnectingHandler)
 
1874
 
 
1875
    def get_url(self):
 
1876
        """Return the url of the server"""
 
1877
        return "bzr://%s:%d/" % self.server.server_address