~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

Merge bzr.dev and tree-file-ids-as-tuples.

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