~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-12-13 14:10:47 UTC
  • mfrom: (3106.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20071213141047-tklbta8rymzfpj6y
cherrypicking/rebasing section for User Guide

Show diffs side-by-side

added added

removed removed

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