~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

(jelmer) Migrate launchpad plugin configuration to config stacks API.
 (Jelmer Vernooij)

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