~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Ian Clatworthy
  • Date: 2007-03-22 01:02:07 UTC
  • mto: (2370.2.1 jam-integration)
  • mto: This revision was merged to the branch mainline in revision 2371.
  • Revision ID: ian.clatworthy@internode.on.net-20070322010207-r8xcwjshipoahmcx
Minor corrections to HACKING

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,
29
 
    revision as _mod_revision,
30
 
    tests,
 
26
    repository,
31
27
    treebuilder,
32
28
    )
33
 
from bzrlib.bundle import read_mergeable_from_url
 
29
from bzrlib.builtins import _merge_helper
 
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
 
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
 
33
from bzrlib.bundle.serializer import write_bundle, read_bundle
38
34
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
39
35
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
40
 
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
 
36
from bzrlib.branch import Branch
 
37
from bzrlib.diff import internal_diff
 
38
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
 
39
                           NoSuchFile,)
 
40
from bzrlib.merge import Merge3Merger
41
41
from bzrlib.repofmt import knitrepo
42
 
from bzrlib.tests import (
43
 
    features,
44
 
    test_commit,
45
 
    test_read_bundle,
46
 
    test_server,
47
 
    )
 
42
from bzrlib.osutils import has_symlinks, sha_file
 
43
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
 
44
                          TestCase, TestSkipped)
48
45
from bzrlib.transform import TreeTransform
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()
 
46
from bzrlib.workingtree import WorkingTree
65
47
 
66
48
 
67
49
class MockTree(object):
68
 
 
69
50
    def __init__(self):
70
51
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
71
52
        object.__init__(self)
75
56
        self.root = InventoryDirectory(ROOT_ID, '', None)
76
57
 
77
58
    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
 
59
 
 
60
    def __iter__(self):
 
61
        return self.paths.iterkeys()
89
62
 
90
63
    def __getitem__(self, file_id):
91
64
        if file_id == self.root.file_id:
103
76
        for path, file_id in self.ids.iteritems():
104
77
            yield path, self[file_id]
105
78
 
106
 
    def kind(self, file_id):
 
79
    def get_file_kind(self, file_id):
107
80
        if file_id in self.contents:
108
81
            kind = 'file'
109
82
        else:
111
84
        return kind
112
85
 
113
86
    def make_entry(self, file_id, path):
114
 
        from bzrlib.inventory import (InventoryFile , InventoryDirectory,
115
 
            InventoryLink)
 
87
        from bzrlib.inventory import (InventoryEntry, InventoryFile
 
88
                                    , InventoryDirectory, InventoryLink)
116
89
        name = os.path.basename(path)
117
 
        kind = self.kind(file_id)
 
90
        kind = self.get_file_kind(file_id)
118
91
        parent_id = self.parent_id(file_id)
119
92
        text_sha_1, text_size = self.contents_stats(file_id)
120
93
        if kind == 'directory':
121
94
            ie = InventoryDirectory(file_id, name, parent_id)
122
95
        elif kind == 'file':
123
96
            ie = InventoryFile(file_id, name, parent_id)
124
 
            ie.text_sha1 = text_sha_1
125
 
            ie.text_size = text_size
126
97
        elif kind == 'symlink':
127
98
            ie = InventoryLink(file_id, name, parent_id)
128
99
        else:
129
 
            raise errors.BzrError('unknown kind %r' % kind)
 
100
            raise BzrError('unknown kind %r' % kind)
 
101
        ie.text_sha1 = text_sha_1
 
102
        ie.text_size = text_size
130
103
        return ie
131
104
 
132
105
    def add_dir(self, file_id, path):
133
106
        self.paths[file_id] = path
134
107
        self.ids[path] = file_id
135
 
 
 
108
    
136
109
    def add_file(self, file_id, path, contents):
137
110
        self.add_dir(file_id, path)
138
111
        self.contents[file_id] = contents
152
125
        result.seek(0,0)
153
126
        return result
154
127
 
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
128
    def contents_stats(self, file_id):
165
129
        if file_id not in self.contents:
166
130
            return None, None
167
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
 
131
        text_sha1 = sha_file(self.get_file(file_id))
168
132
        return text_sha1, len(self.contents[file_id])
169
133
 
170
134
 
171
 
class BTreeTester(tests.TestCase):
 
135
class BTreeTester(TestCase):
172
136
    """A simple unittest tester for the BundleTree class."""
173
137
 
174
138
    def make_tree_1(self):
178
142
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
179
143
        mtree.add_dir("d", "grandparent/alt_parent")
180
144
        return BundleTree(mtree, ''), mtree
181
 
 
 
145
        
182
146
    def test_renames(self):
183
147
        """Ensure that file renames have the proper effect on children"""
184
148
        btree = self.make_tree_1()[0]
185
149
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
186
 
        self.assertEqual(btree.old_path("grandparent/parent"),
 
150
        self.assertEqual(btree.old_path("grandparent/parent"), 
187
151
                         "grandparent/parent")
188
152
        self.assertEqual(btree.old_path("grandparent/parent/file"),
189
153
                         "grandparent/parent/file")
196
160
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
197
161
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
198
162
 
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)
 
163
        assert btree.path2id("grandparent2") is None
 
164
        assert btree.path2id("grandparent2/parent") is None
 
165
        assert btree.path2id("grandparent2/parent/file") is None
202
166
 
203
167
        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)
 
168
        assert btree.old_path("grandparent") is None
 
169
        assert btree.old_path("grandparent/parent") is None
 
170
        assert btree.old_path("grandparent/parent/file") is None
207
171
 
208
172
        self.assertEqual(btree.id2path("a"), "grandparent2")
209
173
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
213
177
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
214
178
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
215
179
 
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)
 
180
        assert btree.path2id("grandparent") is None
 
181
        assert btree.path2id("grandparent/parent") is None
 
182
        assert btree.path2id("grandparent/parent/file") is None
219
183
 
220
184
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
221
185
        self.assertEqual(btree.id2path("a"), "grandparent2")
226
190
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
227
191
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
228
192
 
229
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
230
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
193
        assert btree.path2id("grandparent2/parent") is None
 
194
        assert btree.path2id("grandparent2/parent/file") is None
231
195
 
232
 
        btree.note_rename("grandparent/parent/file",
 
196
        btree.note_rename("grandparent/parent/file", 
233
197
                          "grandparent2/parent2/file2")
234
198
        self.assertEqual(btree.id2path("a"), "grandparent2")
235
199
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
239
203
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
240
204
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
241
205
 
242
 
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
 
206
        assert btree.path2id("grandparent2/parent2/file") is None
243
207
 
244
208
    def test_moves(self):
245
209
        """Ensure that file moves have the proper effect on children"""
246
210
        btree = self.make_tree_1()[0]
247
 
        btree.note_rename("grandparent/parent/file",
 
211
        btree.note_rename("grandparent/parent/file", 
248
212
                          "grandparent/alt_parent/file")
249
213
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
250
214
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
251
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
215
        assert btree.path2id("grandparent/parent/file") is None
252
216
 
253
217
    def unified_diff(self, old, new):
254
218
        out = StringIO()
255
 
        diff.internal_diff("old", old, "new", new, out)
 
219
        internal_diff("old", old, "new", new, out)
256
220
        out.seek(0,0)
257
221
        return out.read()
258
222
 
259
223
    def make_tree_2(self):
260
224
        btree = self.make_tree_1()[0]
261
 
        btree.note_rename("grandparent/parent/file",
 
225
        btree.note_rename("grandparent/parent/file", 
262
226
                          "grandparent/alt_parent/file")
263
 
        self.assertTrue(btree.id2path("e") is None)
264
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
227
        assert btree.id2path("e") is None
 
228
        assert btree.path2id("grandparent/parent/file") is None
265
229
        btree.note_id("e", "grandparent/parent/file")
266
230
        return btree
267
231
 
293
257
    def make_tree_3(self):
294
258
        btree, mtree = self.make_tree_1()
295
259
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
296
 
        btree.note_rename("grandparent/parent/file",
 
260
        btree.note_rename("grandparent/parent/file", 
297
261
                          "grandparent/alt_parent/file")
298
 
        btree.note_rename("grandparent/parent/topping",
 
262
        btree.note_rename("grandparent/parent/topping", 
299
263
                          "grandparent/alt_parent/stopping")
300
264
        return btree
301
265
 
325
289
        btree = self.make_tree_1()[0]
326
290
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
327
291
        btree.note_deletion("grandparent/parent/file")
328
 
        self.assertTrue(btree.id2path("c") is None)
329
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
292
        assert btree.id2path("c") is None
 
293
        assert btree.path2id("grandparent/parent/file") is None
330
294
 
331
295
    def sorted_ids(self, tree):
332
 
        ids = list(tree.all_file_ids())
 
296
        ids = list(tree)
333
297
        ids.sort()
334
298
        return ids
335
299
 
340
304
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
341
305
        btree.note_deletion("grandparent/parent/file")
342
306
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
343
 
        btree.note_last_changed("grandparent/alt_parent/fool",
 
307
        btree.note_last_changed("grandparent/alt_parent/fool", 
344
308
                                "revisionidiguess")
345
309
        self.assertEqual(self.sorted_ids(btree),
346
310
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
347
311
 
348
312
 
349
 
class BundleTester1(tests.TestCaseWithTransport):
 
313
class BundleTester1(TestCaseWithTransport):
350
314
 
351
315
    def test_mismatched_bundle(self):
352
316
        format = bzrdir.BzrDirMetaFormat1()
353
317
        format.repository_format = knitrepo.RepositoryFormatKnit3()
354
318
        serializer = BundleSerializerV08('0.8')
355
319
        b = self.make_branch('.', format=format)
356
 
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
320
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write, 
357
321
                          b.repository, [], {}, StringIO())
358
322
 
359
323
    def test_matched_bundle(self):
372
336
        source.commit('one', rev_id='one-id')
373
337
        source.commit('two', rev_id='two-id')
374
338
        text = StringIO()
375
 
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
 
339
        write_bundle(source.branch.repository, 'two-id', None, text, 
376
340
                     format='0.9')
377
341
        text.seek(0)
378
342
 
379
343
        format = bzrdir.BzrDirMetaFormat1()
380
344
        format.repository_format = knitrepo.RepositoryFormatKnit1()
381
345
        target = self.make_branch('target', format=format)
382
 
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
346
        self.assertRaises(errors.IncompatibleRevision, install_bundle, 
383
347
                          target.repository, read_bundle(text))
384
348
 
385
349
 
386
 
class BundleTester(object):
 
350
class V08BundleTester(TestCaseWithTransport):
 
351
 
 
352
    format = '0.8'
387
353
 
388
354
    def bzrdir_format(self):
389
355
        format = bzrdir.BzrDirMetaFormat1()
393
359
    def make_branch_and_tree(self, path, format=None):
394
360
        if format is None:
395
361
            format = self.bzrdir_format()
396
 
        return tests.TestCaseWithTransport.make_branch_and_tree(
397
 
            self, path, format)
 
362
        return TestCaseWithTransport.make_branch_and_tree(self, path, format)
398
363
 
399
364
    def make_branch(self, path, format=None):
400
365
        if format is None:
401
366
            format = self.bzrdir_format()
402
 
        return tests.TestCaseWithTransport.make_branch(self, path, format)
 
367
        return TestCaseWithTransport.make_branch(self, path, format)
403
368
 
404
369
    def create_bundle_text(self, base_rev_id, rev_id):
405
370
        bundle_txt = StringIO()
406
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
371
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
407
372
                               bundle_txt, format=self.format)
408
373
        bundle_txt.seek(0)
409
 
        self.assertEqual(bundle_txt.readline(),
 
374
        self.assertEqual(bundle_txt.readline(), 
410
375
                         '# Bazaar revision bundle v%s\n' % self.format)
411
376
        self.assertEqual(bundle_txt.readline(), '#\n')
412
377
 
413
378
        rev = self.b1.repository.get_revision(rev_id)
414
379
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
415
380
                         u'# message:\n')
 
381
 
 
382
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
416
383
        bundle_txt.seek(0)
417
384
        return bundle_txt, rev_ids
418
385
 
420
387
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
421
388
        Make sure that the text generated is valid, and that it
422
389
        can be applied against the base, and generate the same information.
423
 
 
424
 
        :return: The in-memory bundle
 
390
        
 
391
        :return: The in-memory bundle 
425
392
        """
426
393
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
427
394
 
428
 
        # This should also validate the generated bundle
 
395
        # This should also validate the generated bundle 
429
396
        bundle = read_bundle(bundle_txt)
430
397
        repository = self.b1.repository
431
398
        for bundle_rev in bundle.real_revisions:
435
402
            # it
436
403
            branch_rev = repository.get_revision(bundle_rev.revision_id)
437
404
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
438
 
                      'timestamp', 'timezone', 'message', 'committer',
 
405
                      'timestamp', 'timezone', 'message', 'committer', 
439
406
                      'parent_ids', 'properties'):
440
 
                self.assertEqual(getattr(branch_rev, a),
 
407
                self.assertEqual(getattr(branch_rev, a), 
441
408
                                 getattr(bundle_rev, a))
442
 
            self.assertEqual(len(branch_rev.parent_ids),
 
409
            self.assertEqual(len(branch_rev.parent_ids), 
443
410
                             len(bundle_rev.parent_ids))
444
 
        self.assertEqual(rev_ids,
 
411
        self.assertEqual(rev_ids, 
445
412
                         [r.revision_id for r in bundle.real_revisions])
446
413
        self.valid_apply_bundle(base_rev_id, bundle,
447
414
                                   checkout_dir=checkout_dir)
451
418
    def get_invalid_bundle(self, base_rev_id, rev_id):
452
419
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
453
420
        Munge the text so that it's invalid.
454
 
 
 
421
        
455
422
        :return: The in-memory bundle
456
423
        """
457
424
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
458
 
        new_text = bundle_txt.getvalue().replace('executable:no',
 
425
        new_text = bundle_txt.getvalue().replace('executable:no', 
459
426
                                               'executable:yes')
460
427
        bundle_txt = StringIO(new_text)
461
428
        bundle = read_bundle(bundle_txt)
462
429
        self.valid_apply_bundle(base_rev_id, bundle)
463
 
        return bundle
 
430
        return bundle 
464
431
 
465
432
    def test_non_bundle(self):
466
 
        self.assertRaises(errors.NotABundle,
467
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
433
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
468
434
 
469
435
    def test_malformed(self):
470
 
        self.assertRaises(errors.BadBundle, read_bundle,
 
436
        self.assertRaises(BadBundle, read_bundle, 
471
437
                          StringIO('# Bazaar revision bundle v'))
472
438
 
473
439
    def test_crlf_bundle(self):
474
440
        try:
475
441
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
476
 
        except errors.BadBundle:
 
442
        except BadBundle:
477
443
            # It is currently permitted for bundles with crlf line endings to
478
444
            # make read_bundle raise a BadBundle, but this should be fixed.
479
445
            # Anything else, especially NotABundle, is an error.
484
450
        """
485
451
 
486
452
        if checkout_dir is None:
487
 
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
 
453
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
488
454
        else:
489
455
            if not os.path.exists(checkout_dir):
490
456
                os.mkdir(checkout_dir)
491
457
        tree = self.make_branch_and_tree(checkout_dir)
492
458
        s = StringIO()
493
 
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
 
459
        ancestors = write_bundle(self.b1.repository, rev_id, None, s,
494
460
                                 format=self.format)
495
461
        s.seek(0)
496
 
        self.assertIsInstance(s.getvalue(), str)
 
462
        assert isinstance(s.getvalue(), str), (
 
463
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
497
464
        install_bundle(tree.branch.repository, read_bundle(s))
498
465
        for ancestor in ancestors:
499
466
            old = self.b1.repository.revision_tree(ancestor)
500
467
            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()
523
 
        if not _mod_revision.is_null(rev_id):
524
 
            tree.branch.generate_revision_history(rev_id)
 
468
 
 
469
            # Check that there aren't any inventory level changes
 
470
            delta = new.changes_from(old)
 
471
            self.assertFalse(delta.has_changed(),
 
472
                             'Revision %s not copied correctly.'
 
473
                             % (ancestor,))
 
474
 
 
475
            # Now check that the file contents are all correct
 
476
            for inventory_id in old:
 
477
                try:
 
478
                    old_file = old.get_file(inventory_id)
 
479
                except NoSuchFile:
 
480
                    continue
 
481
                if old_file is None:
 
482
                    continue
 
483
                self.assertEqual(old_file.read(),
 
484
                                 new.get_file(inventory_id).read())
 
485
        if rev_id is not None:
 
486
            rh = self.b1.revision_history()
 
487
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
525
488
            tree.update()
526
489
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
527
490
            self.assertFalse(delta.has_changed(),
533
496
        sure everything matches the builtin branch.
534
497
        """
535
498
        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
499
        original_parents = to_tree.get_parent_ids()
544
500
        repository = to_tree.branch.repository
545
501
        original_parents = to_tree.get_parent_ids()
546
502
        self.assertIs(repository.has_revision(base_rev_id), True)
547
503
        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)
 
504
            self.assert_(not repository.has_revision(rev.revision_id),
 
505
                'Revision {%s} present before applying bundle' 
 
506
                % rev.revision_id)
 
507
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
552
508
 
553
509
        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)
 
510
            self.assert_(repository.has_revision(rev.revision_id),
 
511
                'Missing revision {%s} after applying bundle' 
 
512
                % rev.revision_id)
557
513
 
558
 
        self.assertTrue(to_tree.branch.repository.has_revision(info.target))
 
514
        self.assert_(to_tree.branch.repository.has_revision(info.target))
559
515
        # Do we also want to verify that all the texts have been added?
560
516
 
561
517
        self.assertEqual(original_parents + [info.target],
562
 
                         to_tree.get_parent_ids())
 
518
            to_tree.get_parent_ids())
563
519
 
564
520
        rev = info.real_revisions[-1]
565
521
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
566
522
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
567
 
 
 
523
        
568
524
        # TODO: make sure the target tree is identical to base tree
569
525
        #       we might also check the working tree.
570
526
 
589
545
        self.tree1 = self.make_branch_and_tree('b1')
590
546
        self.b1 = self.tree1.branch
591
547
 
592
 
        self.build_tree_contents([('b1/one', 'one\n')])
593
 
        self.tree1.add('one', 'one-id')
594
 
        self.tree1.set_root_id('root-id')
 
548
        open('b1/one', 'wb').write('one\n')
 
549
        self.tree1.add('one')
595
550
        self.tree1.commit('add one', rev_id='a@cset-0-1')
596
551
 
597
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
 
552
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
 
553
        # FIXME: The current write_bundle api no longer supports
 
554
        #        setting a custom summary message
 
555
        #        We should re-introduce the ability, and update
 
556
        #        the tests to make sure it works.
 
557
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
 
558
        #         message='With a specialized message')
598
559
 
599
560
        # Make sure we can handle files with spaces, tabs, other
600
561
        # bogus characters
608
569
                , 'b1/sub/sub/'
609
570
                , 'b1/sub/sub/nonempty.txt'
610
571
                ])
611
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
612
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
 
572
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
 
573
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
613
574
        tt = TreeTransform(self.tree1)
614
575
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
615
576
        tt.apply()
616
 
        # have to fix length of file-id so that we can predictably rewrite
617
 
        # a (length-prefixed) record containing it later.
618
 
        self.tree1.add('with space.txt', 'withspace-id')
619
577
        self.tree1.add([
620
 
                  'dir'
 
578
                'with space.txt'
 
579
                , 'dir'
621
580
                , 'dir/filein subdir.c'
622
581
                , 'dir/WithCaps.txt'
623
582
                , 'dir/ pre space'
631
590
 
632
591
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
633
592
 
634
 
        # Check a rollup bundle
635
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
 
593
        # Check a rollup bundle 
 
594
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
636
595
 
637
596
        # Now delete entries
638
597
        self.tree1.remove(
645
604
        tt.set_executability(False, trans_id)
646
605
        tt.apply()
647
606
        self.tree1.commit('removed', rev_id='a@cset-0-3')
648
 
 
 
607
        
649
608
        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,
653
 
            'a@cset-0-2', 'a@cset-0-3')
654
 
        # Check a rollup bundle
655
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
 
609
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
 
610
                          'a@cset-0-2', 'a@cset-0-3')
 
611
        # Check a rollup bundle 
 
612
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
656
613
 
657
614
        # Now move the directory
658
615
        self.tree1.rename_one('dir', 'sub/dir')
659
616
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
660
617
 
661
618
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
662
 
        # Check a rollup bundle
663
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
 
619
        # Check a rollup bundle 
 
620
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
664
621
 
665
622
        # 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(
668
 
             '\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',
 
623
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
 
624
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
 
625
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
 
626
        self.tree1.rename_one('sub/dir/ pre space', 
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')
689
 
        self.tree1.merge_from_branch(other.branch)
 
644
        _merge_helper([other.basedir, -1], [None, None],
 
645
                      this_dir=self.tree1.basedir)
690
646
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
691
647
                          verbose=False)
692
648
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
693
649
 
694
 
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
695
 
        link_id = 'link-1'
696
 
 
697
 
        self.requireFeature(features.SymlinkFeature)
 
650
    def test_symlink_bundle(self):
 
651
        if not has_symlinks():
 
652
            raise TestSkipped("No symlink support")
698
653
        self.tree1 = self.make_branch_and_tree('b1')
699
654
        self.b1 = self.tree1.branch
700
 
 
701
655
        tt = TreeTransform(self.tree1)
702
 
        tt.new_symlink(link_name, tt.root, link_target, link_id)
 
656
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
703
657
        tt.apply()
704
658
        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
 
 
 
659
        self.get_valid_bundle(None, 'l@cset-0-1')
711
660
        tt = TreeTransform(self.tree1)
712
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
661
        trans_id = tt.trans_id_tree_file_id('link-1')
713
662
        tt.adjust_path('link2', tt.root, trans_id)
714
663
        tt.delete_contents(trans_id)
715
 
        tt.create_symlink(new_link_target, trans_id)
 
664
        tt.create_symlink('mars', trans_id)
716
665
        tt.apply()
717
666
        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
 
 
 
667
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
725
668
        tt = TreeTransform(self.tree1)
726
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
669
        trans_id = tt.trans_id_tree_file_id('link-1')
727
670
        tt.delete_contents(trans_id)
728
671
        tt.create_symlink('jupiter', trans_id)
729
672
        tt.apply()
730
673
        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
 
 
 
674
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
733
675
        tt = TreeTransform(self.tree1)
734
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
676
        trans_id = tt.trans_id_tree_file_id('link-1')
735
677
        tt.delete_contents(trans_id)
736
678
        tt.apply()
737
679
        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}')
 
680
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
748
681
 
749
682
    def test_binary_bundle(self):
750
683
        self.tree1 = self.make_branch_and_tree('b1')
751
684
        self.b1 = self.tree1.branch
752
685
        tt = TreeTransform(self.tree1)
753
 
 
 
686
        
754
687
        # Add
755
688
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
756
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
757
 
            'binary-2')
 
689
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
758
690
        tt.apply()
759
691
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
760
 
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
692
        self.get_valid_bundle(None, 'b@cset-0-1')
761
693
 
762
694
        # Delete
763
695
        tt = TreeTransform(self.tree1)
787
719
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
788
720
 
789
721
        # Rollup
790
 
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
722
        self.get_valid_bundle(None, 'b@cset-0-4')
791
723
 
792
724
    def test_last_modified(self):
793
725
        self.tree1 = self.make_branch_and_tree('b1')
811
743
        tt.create_file('file2', trans_id)
812
744
        tt.apply()
813
745
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
814
 
        self.tree1.merge_from_branch(other.branch)
 
746
        _merge_helper([other.basedir, -1], [None, None],
 
747
                      this_dir=self.tree1.basedir)
815
748
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
816
749
                          verbose=False)
817
750
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
821
754
        self.tree1 = self.make_branch_and_tree('b1')
822
755
        self.b1 = self.tree1.branch
823
756
 
824
 
        with open('b1/one', 'wb') as f: f.write('one\n')
 
757
        open('b1/one', 'wb').write('one\n')
825
758
        self.tree1.add('one')
826
759
        self.tree1.commit('add file', rev_id='a@cset-0-1')
827
 
        with open('b1/one', 'wb') as f: f.write('two\n')
 
760
        open('b1/one', 'wb').write('two\n')
828
761
        self.tree1.commit('modify', rev_id='a@cset-0-2')
829
 
        with open('b1/one', 'wb') as f: f.write('three\n')
 
762
        open('b1/one', 'wb').write('three\n')
830
763
        self.tree1.commit('modify', rev_id='a@cset-0-3')
831
764
        bundle_file = StringIO()
832
765
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
833
766
                               'a@cset-0-1', bundle_file, format=self.format)
834
 
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
835
 
        self.assertContainsRe(self.get_raw(bundle_file), 'one')
836
 
        self.assertContainsRe(self.get_raw(bundle_file), 'three')
837
 
 
838
 
    def test_bundle_same_basis(self):
839
 
        """Ensure using the basis as the target doesn't cause an error"""
840
 
        self.tree1 = self.make_branch_and_tree('b1')
841
 
        self.tree1.commit('add file', rev_id='a@cset-0-1')
842
 
        bundle_file = StringIO()
843
 
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-1',
844
 
                               'a@cset-0-1', bundle_file)
845
 
 
846
 
    @staticmethod
847
 
    def get_raw(bundle_file):
848
 
        return bundle_file.getvalue()
 
767
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
 
768
        self.assertContainsRe(bundle_file.getvalue(), 'one')
 
769
        self.assertContainsRe(bundle_file.getvalue(), 'three')
849
770
 
850
771
    def test_unicode_bundle(self):
851
 
        self.requireFeature(features.UnicodeFilenameFeature)
852
772
        # Handle international characters
853
773
        os.mkdir('b1')
854
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
774
        try:
 
775
            f = open(u'b1/with Dod\xe9', 'wb')
 
776
        except UnicodeEncodeError:
 
777
            raise TestSkipped("Filesystem doesn't support unicode")
855
778
 
856
779
        self.tree1 = self.make_branch_and_tree('b1')
857
780
        self.b1 = self.tree1.branch
861
784
            u'William Dod\xe9\n').encode('utf-8'))
862
785
        f.close()
863
786
 
864
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
865
 
        self.tree1.commit(u'i18n commit from William Dod\xe9',
 
787
        self.tree1.add([u'with Dod\xe9'])
 
788
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
866
789
                          rev_id='i18n-1', committer=u'William Dod\xe9')
867
790
 
868
791
        # Add
869
 
        bundle = self.get_valid_bundle('null:', 'i18n-1')
 
792
        bundle = self.get_valid_bundle(None, 'i18n-1')
870
793
 
871
794
        # Modified
872
 
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
 
795
        f = open(u'b1/with Dod\xe9', 'wb')
873
796
        f.write(u'Modified \xb5\n'.encode('utf8'))
874
797
        f.close()
875
798
        self.tree1.commit(u'modified', rev_id='i18n-2')
876
799
 
877
800
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
878
 
 
 
801
        
879
802
        # Renamed
880
 
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
 
803
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
881
804
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
882
805
                          committer=u'Erik B\xe5gfors')
883
806
 
884
807
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
885
808
 
886
809
        # Removed
887
 
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
 
810
        self.tree1.remove([u'B\xe5gfors'])
888
811
        self.tree1.commit(u'removed', rev_id='i18n-4')
889
812
 
890
813
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
891
814
 
892
815
        # Rollup
893
 
        bundle = self.get_valid_bundle('null:', 'i18n-4')
 
816
        bundle = self.get_valid_bundle(None, 'i18n-4')
894
817
 
895
818
 
896
819
    def test_whitespace_bundle(self):
897
820
        if sys.platform in ('win32', 'cygwin'):
898
 
            raise tests.TestSkipped('Windows doesn\'t support filenames'
899
 
                                    ' with tabs or trailing spaces')
 
821
            raise TestSkipped('Windows doesn\'t support filenames'
 
822
                              ' with tabs or trailing spaces')
900
823
        self.tree1 = self.make_branch_and_tree('b1')
901
824
        self.b1 = self.tree1.branch
902
825
 
908
831
        # Added
909
832
        self.tree1.commit('funky whitespace', rev_id='white-1')
910
833
 
911
 
        bundle = self.get_valid_bundle('null:', 'white-1')
 
834
        bundle = self.get_valid_bundle(None, 'white-1')
912
835
 
913
836
        # Modified
914
 
        with open('b1/trailing space ', 'ab') as f: f.write('add some text\n')
 
837
        open('b1/trailing space ', 'ab').write('add some text\n')
915
838
        self.tree1.commit('add text', rev_id='white-2')
916
839
 
917
840
        bundle = self.get_valid_bundle('white-1', 'white-2')
927
850
        self.tree1.commit('removed', rev_id='white-4')
928
851
 
929
852
        bundle = self.get_valid_bundle('white-3', 'white-4')
930
 
 
 
853
        
931
854
        # Now test a complet roll-up
932
 
        bundle = self.get_valid_bundle('null:', 'white-4')
 
855
        bundle = self.get_valid_bundle(None, 'white-4')
933
856
 
934
857
    def test_alt_timezone_bundle(self):
935
858
        self.tree1 = self.make_branch_and_memory_tree('b1')
945
868
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
946
869
                          timezone=19800, timestamp=1152544886.0)
947
870
 
948
 
        bundle = self.get_valid_bundle('null:', 'tz-1')
949
 
 
 
871
        bundle = self.get_valid_bundle(None, 'tz-1')
 
872
        
950
873
        rev = bundle.revisions[0]
951
874
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
952
875
        self.assertEqual(19800, rev.timezone)
957
880
        self.tree1 = self.make_branch_and_tree('b1')
958
881
        self.b1 = self.tree1.branch
959
882
        self.tree1.commit('message', rev_id='revid1')
960
 
        bundle = self.get_valid_bundle('null:', 'revid1')
961
 
        tree = self.get_bundle_tree(bundle, 'revid1')
962
 
        root_revision = tree.get_file_revision(tree.get_root_id())
963
 
        self.assertEqual('revid1', root_revision)
964
 
 
965
 
    def test_install_revisions(self):
966
 
        self.tree1 = self.make_branch_and_tree('b1')
967
 
        self.b1 = self.tree1.branch
968
 
        self.tree1.commit('message', rev_id='rev2a')
969
 
        bundle = self.get_valid_bundle('null:', 'rev2a')
970
 
        branch2 = self.make_branch('b2')
971
 
        self.assertFalse(branch2.repository.has_revision('rev2a'))
972
 
        target_revision = bundle.install_revisions(branch2.repository)
973
 
        self.assertTrue(branch2.repository.has_revision('rev2a'))
974
 
        self.assertEqual('rev2a', target_revision)
975
 
 
976
 
    def test_bundle_empty_property(self):
977
 
        """Test serializing revision properties with an empty value."""
978
 
        tree = self.make_branch_and_memory_tree('tree')
979
 
        tree.lock_write()
980
 
        self.addCleanup(tree.unlock)
981
 
        tree.add([''], ['TREE_ROOT'])
982
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
983
 
        self.b1 = tree.branch
984
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
985
 
        bundle = read_bundle(bundle_sio)
986
 
        revision_info = bundle.revisions[0]
987
 
        self.assertEqual('rev1', revision_info.revision_id)
988
 
        rev = revision_info.as_revision()
989
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
990
 
                         rev.properties)
991
 
 
992
 
    def test_bundle_sorted_properties(self):
993
 
        """For stability the writer should write properties in sorted order."""
994
 
        tree = self.make_branch_and_memory_tree('tree')
995
 
        tree.lock_write()
996
 
        self.addCleanup(tree.unlock)
997
 
 
998
 
        tree.add([''], ['TREE_ROOT'])
999
 
        tree.commit('One', rev_id='rev1',
1000
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
1001
 
        self.b1 = tree.branch
1002
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1003
 
        bundle = read_bundle(bundle_sio)
1004
 
        revision_info = bundle.revisions[0]
1005
 
        self.assertEqual('rev1', revision_info.revision_id)
1006
 
        rev = revision_info.as_revision()
1007
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
1008
 
                          'd':'1'}, rev.properties)
1009
 
 
1010
 
    def test_bundle_unicode_properties(self):
1011
 
        """We should be able to round trip a non-ascii property."""
1012
 
        tree = self.make_branch_and_memory_tree('tree')
1013
 
        tree.lock_write()
1014
 
        self.addCleanup(tree.unlock)
1015
 
 
1016
 
        tree.add([''], ['TREE_ROOT'])
1017
 
        # Revisions themselves do not require anything about revision property
1018
 
        # keys, other than that they are a basestring, and do not contain
1019
 
        # whitespace.
1020
 
        # However, Testaments assert than they are str(), and thus should not
1021
 
        # be Unicode.
1022
 
        tree.commit('One', rev_id='rev1',
1023
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
1024
 
        self.b1 = tree.branch
1025
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1026
 
        bundle = read_bundle(bundle_sio)
1027
 
        revision_info = bundle.revisions[0]
1028
 
        self.assertEqual('rev1', revision_info.revision_id)
1029
 
        rev = revision_info.as_revision()
1030
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1031
 
                          'alpha':u'\u03b1'}, rev.properties)
1032
 
 
1033
 
    def test_bundle_with_ghosts(self):
1034
 
        tree = self.make_branch_and_tree('tree')
1035
 
        self.b1 = tree.branch
1036
 
        self.build_tree_contents([('tree/file', 'content1')])
1037
 
        tree.add(['file'])
1038
 
        tree.commit('rev1')
1039
 
        self.build_tree_contents([('tree/file', 'content2')])
1040
 
        tree.add_parent_tree_id('ghost')
1041
 
        tree.commit('rev2', rev_id='rev2')
1042
 
        bundle = self.get_valid_bundle('null:', 'rev2')
1043
 
 
1044
 
    def make_simple_tree(self, format=None):
1045
 
        tree = self.make_branch_and_tree('b1', format=format)
1046
 
        self.b1 = tree.branch
1047
 
        self.build_tree(['b1/file'])
1048
 
        tree.add('file')
1049
 
        return tree
1050
 
 
1051
 
    def test_across_serializers(self):
1052
 
        tree = self.make_simple_tree('knit')
1053
 
        tree.commit('hello', rev_id='rev1')
1054
 
        tree.commit('hello', rev_id='rev2')
1055
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1056
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1057
 
        bundle.install_revisions(repo)
1058
 
        inv_text = repo._get_inventory_xml('rev2')
1059
 
        self.assertNotContainsRe(inv_text, 'format="5"')
1060
 
        self.assertContainsRe(inv_text, 'format="7"')
1061
 
 
1062
 
    def make_repo_with_installed_revisions(self):
1063
 
        tree = self.make_simple_tree('knit')
1064
 
        tree.commit('hello', rev_id='rev1')
1065
 
        tree.commit('hello', rev_id='rev2')
1066
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1067
 
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1068
 
        bundle.install_revisions(repo)
1069
 
        return repo
1070
 
 
1071
 
    def test_across_models(self):
1072
 
        repo = self.make_repo_with_installed_revisions()
1073
 
        inv = repo.get_inventory('rev2')
1074
 
        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)
1087
 
 
1088
 
    def test_across_models_incompatible(self):
1089
 
        tree = self.make_simple_tree('dirstate-with-subtree')
1090
 
        tree.commit('hello', rev_id='rev1')
1091
 
        tree.commit('hello', rev_id='rev2')
1092
 
        try:
1093
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1094
 
        except errors.IncompatibleBundleFormat:
1095
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1096
 
        repo = self.make_repository('repo', format='knit')
1097
 
        bundle.install_revisions(repo)
1098
 
 
1099
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1100
 
        self.assertRaises(errors.IncompatibleRevision,
1101
 
                          bundle.install_revisions, repo)
1102
 
 
1103
 
    def test_get_merge_request(self):
1104
 
        tree = self.make_simple_tree()
1105
 
        tree.commit('hello', rev_id='rev1')
1106
 
        tree.commit('hello', rev_id='rev2')
1107
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1108
 
        result = bundle.get_merge_request(tree.branch.repository)
1109
 
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
1110
 
 
1111
 
    def test_with_subtree(self):
1112
 
        tree = self.make_branch_and_tree('tree',
1113
 
                                         format='dirstate-with-subtree')
1114
 
        self.b1 = tree.branch
1115
 
        subtree = self.make_branch_and_tree('tree/subtree',
1116
 
                                            format='dirstate-with-subtree')
1117
 
        tree.add('subtree')
1118
 
        tree.commit('hello', rev_id='rev1')
1119
 
        try:
1120
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1121
 
        except errors.IncompatibleBundleFormat:
1122
 
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1123
 
        if isinstance(bundle, v09.BundleInfo09):
1124
 
            raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
1125
 
        repo = self.make_repository('repo', format='knit')
1126
 
        self.assertRaises(errors.IncompatibleRevision,
1127
 
                          bundle.install_revisions, repo)
1128
 
        repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
1129
 
        bundle.install_revisions(repo2)
1130
 
 
1131
 
    def test_revision_id_with_slash(self):
1132
 
        self.tree1 = self.make_branch_and_tree('tree')
1133
 
        self.b1 = self.tree1.branch
1134
 
        try:
1135
 
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1136
 
        except ValueError:
1137
 
            raise tests.TestSkipped(
1138
 
                "Repository doesn't support revision ids with slashes")
1139
 
        bundle = self.get_valid_bundle('null:', 'rev/id')
1140
 
 
1141
 
    def test_skip_file(self):
1142
 
        """Make sure we don't accidentally write to the wrong versionedfile"""
1143
 
        self.tree1 = self.make_branch_and_tree('tree')
1144
 
        self.b1 = self.tree1.branch
1145
 
        # rev1 is not present in bundle, done by fetch
1146
 
        self.build_tree_contents([('tree/file2', 'contents1')])
1147
 
        self.tree1.add('file2', 'file2-id')
1148
 
        self.tree1.commit('rev1', rev_id='reva')
1149
 
        self.build_tree_contents([('tree/file3', 'contents2')])
1150
 
        # rev2 is present in bundle, and done by fetch
1151
 
        # having file1 in the bunle causes file1's versionedfile to be opened.
1152
 
        self.tree1.add('file3', 'file3-id')
1153
 
        self.tree1.commit('rev2')
1154
 
        # Updating file2 should not cause an attempt to add to file1's vf
1155
 
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
1156
 
        self.build_tree_contents([('tree/file2', 'contents3')])
1157
 
        self.tree1.commit('rev3', rev_id='rev3')
1158
 
        bundle = self.get_valid_bundle('reva', 'rev3')
1159
 
        if getattr(bundle, 'get_bundle_reader', None) is None:
1160
 
            raise tests.TestSkipped('Bundle format cannot provide reader')
1161
 
        # be sure that file1 comes before file2
1162
 
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1163
 
            if f == 'file3-id':
1164
 
                break
1165
 
            self.assertNotEqual(f, 'file2-id')
1166
 
        bundle.install_revisions(target.branch.repository)
1167
 
 
1168
 
 
1169
 
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
1170
 
 
1171
 
    format = '0.8'
1172
 
 
1173
 
    def test_bundle_empty_property(self):
1174
 
        """Test serializing revision properties with an empty value."""
1175
 
        tree = self.make_branch_and_memory_tree('tree')
1176
 
        tree.lock_write()
1177
 
        self.addCleanup(tree.unlock)
1178
 
        tree.add([''], ['TREE_ROOT'])
1179
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1180
 
        self.b1 = tree.branch
1181
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1182
 
        self.assertContainsRe(bundle_sio.getvalue(),
1183
 
                              '# properties:\n'
1184
 
                              '#   branch-nick: tree\n'
1185
 
                              '#   empty: \n'
1186
 
                              '#   one: two\n'
1187
 
                             )
1188
 
        bundle = read_bundle(bundle_sio)
1189
 
        revision_info = bundle.revisions[0]
1190
 
        self.assertEqual('rev1', revision_info.revision_id)
1191
 
        rev = revision_info.as_revision()
1192
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
1193
 
                         rev.properties)
1194
 
 
1195
 
    def get_bundle_tree(self, bundle, revision_id):
1196
 
        repository = self.make_repository('repo')
1197
 
        return bundle.revision_tree(repository, 'revid1')
1198
 
 
1199
 
    def test_bundle_empty_property_alt(self):
1200
 
        """Test serializing revision properties with an empty value.
1201
 
 
1202
 
        Older readers had a bug when reading an empty property.
1203
 
        They assumed that all keys ended in ': \n'. However they would write an
1204
 
        empty value as ':\n'. This tests make sure that all newer bzr versions
1205
 
        can handle th second form.
1206
 
        """
1207
 
        tree = self.make_branch_and_memory_tree('tree')
1208
 
        tree.lock_write()
1209
 
        self.addCleanup(tree.unlock)
1210
 
        tree.add([''], ['TREE_ROOT'])
1211
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1212
 
        self.b1 = tree.branch
1213
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1214
 
        txt = bundle_sio.getvalue()
1215
 
        loc = txt.find('#   empty: ') + len('#   empty:')
1216
 
        # Create a new bundle, which strips the trailing space after empty
1217
 
        bundle_sio = StringIO(txt[:loc] + txt[loc+1:])
1218
 
 
1219
 
        self.assertContainsRe(bundle_sio.getvalue(),
1220
 
                              '# properties:\n'
1221
 
                              '#   branch-nick: tree\n'
1222
 
                              '#   empty:\n'
1223
 
                              '#   one: two\n'
1224
 
                             )
1225
 
        bundle = read_bundle(bundle_sio)
1226
 
        revision_info = bundle.revisions[0]
1227
 
        self.assertEqual('rev1', revision_info.revision_id)
1228
 
        rev = revision_info.as_revision()
1229
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
1230
 
                         rev.properties)
1231
 
 
1232
 
    def test_bundle_sorted_properties(self):
1233
 
        """For stability the writer should write properties in sorted order."""
1234
 
        tree = self.make_branch_and_memory_tree('tree')
1235
 
        tree.lock_write()
1236
 
        self.addCleanup(tree.unlock)
1237
 
 
1238
 
        tree.add([''], ['TREE_ROOT'])
1239
 
        tree.commit('One', rev_id='rev1',
1240
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
1241
 
        self.b1 = tree.branch
1242
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1243
 
        self.assertContainsRe(bundle_sio.getvalue(),
1244
 
                              '# properties:\n'
1245
 
                              '#   a: 4\n'
1246
 
                              '#   b: 3\n'
1247
 
                              '#   branch-nick: tree\n'
1248
 
                              '#   c: 2\n'
1249
 
                              '#   d: 1\n'
1250
 
                             )
1251
 
        bundle = read_bundle(bundle_sio)
1252
 
        revision_info = bundle.revisions[0]
1253
 
        self.assertEqual('rev1', revision_info.revision_id)
1254
 
        rev = revision_info.as_revision()
1255
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
1256
 
                          'd':'1'}, rev.properties)
1257
 
 
1258
 
    def test_bundle_unicode_properties(self):
1259
 
        """We should be able to round trip a non-ascii property."""
1260
 
        tree = self.make_branch_and_memory_tree('tree')
1261
 
        tree.lock_write()
1262
 
        self.addCleanup(tree.unlock)
1263
 
 
1264
 
        tree.add([''], ['TREE_ROOT'])
1265
 
        # Revisions themselves do not require anything about revision property
1266
 
        # keys, other than that they are a basestring, and do not contain
1267
 
        # whitespace.
1268
 
        # However, Testaments assert than they are str(), and thus should not
1269
 
        # be Unicode.
1270
 
        tree.commit('One', rev_id='rev1',
1271
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
1272
 
        self.b1 = tree.branch
1273
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1274
 
        self.assertContainsRe(bundle_sio.getvalue(),
1275
 
                              '# properties:\n'
1276
 
                              '#   alpha: \xce\xb1\n'
1277
 
                              '#   branch-nick: tree\n'
1278
 
                              '#   omega: \xce\xa9\n'
1279
 
                             )
1280
 
        bundle = read_bundle(bundle_sio)
1281
 
        revision_info = bundle.revisions[0]
1282
 
        self.assertEqual('rev1', revision_info.revision_id)
1283
 
        rev = revision_info.as_revision()
1284
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1285
 
                          'alpha':u'\u03b1'}, rev.properties)
 
883
        bundle = self.get_valid_bundle(None, 'revid1')
 
884
        tree = bundle.revision_tree(self.b1.repository, 'revid1')
 
885
        self.assertEqual('revid1', tree.inventory.root.revision)
1286
886
 
1287
887
 
1288
888
class V09BundleKnit2Tester(V08BundleTester):
1305
905
        return format
1306
906
 
1307
907
 
1308
 
class V4BundleTester(BundleTester, tests.TestCaseWithTransport):
1309
 
 
1310
 
    format = '4'
1311
 
 
1312
 
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
1313
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1314
 
        Make sure that the text generated is valid, and that it
1315
 
        can be applied against the base, and generate the same information.
1316
 
 
1317
 
        :return: The in-memory bundle
1318
 
        """
1319
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1320
 
 
1321
 
        # This should also validate the generated bundle
1322
 
        bundle = read_bundle(bundle_txt)
1323
 
        repository = self.b1.repository
1324
 
        for bundle_rev in bundle.real_revisions:
1325
 
            # These really should have already been checked when we read the
1326
 
            # bundle, since it computes the sha1 hash for the revision, which
1327
 
            # only will match if everything is okay, but lets be explicit about
1328
 
            # it
1329
 
            branch_rev = repository.get_revision(bundle_rev.revision_id)
1330
 
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1331
 
                      'timestamp', 'timezone', 'message', 'committer',
1332
 
                      'parent_ids', 'properties'):
1333
 
                self.assertEqual(getattr(branch_rev, a),
1334
 
                                 getattr(bundle_rev, a))
1335
 
            self.assertEqual(len(branch_rev.parent_ids),
1336
 
                             len(bundle_rev.parent_ids))
1337
 
        self.assertEqual(set(rev_ids),
1338
 
                         set([r.revision_id for r in bundle.real_revisions]))
1339
 
        self.valid_apply_bundle(base_rev_id, bundle,
1340
 
                                   checkout_dir=checkout_dir)
1341
 
 
1342
 
        return bundle
1343
 
 
1344
 
    def get_invalid_bundle(self, base_rev_id, rev_id):
1345
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
1346
 
        Munge the text so that it's invalid.
1347
 
 
1348
 
        :return: The in-memory bundle
1349
 
        """
1350
 
        from bzrlib.bundle import serializer
1351
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1352
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1353
 
        new_text = new_text.replace('<file file_id="exe-1"',
1354
 
                                    '<file executable="y" file_id="exe-1"')
1355
 
        new_text = new_text.replace('B260', 'B275')
1356
 
        bundle_txt = StringIO()
1357
 
        bundle_txt.write(serializer._get_bundle_header('4'))
1358
 
        bundle_txt.write('\n')
1359
 
        bundle_txt.write(new_text.encode('bz2'))
1360
 
        bundle_txt.seek(0)
1361
 
        bundle = read_bundle(bundle_txt)
1362
 
        self.valid_apply_bundle(base_rev_id, bundle)
1363
 
        return bundle
1364
 
 
1365
 
    def create_bundle_text(self, base_rev_id, rev_id):
1366
 
        bundle_txt = StringIO()
1367
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1368
 
                               bundle_txt, format=self.format)
1369
 
        bundle_txt.seek(0)
1370
 
        self.assertEqual(bundle_txt.readline(),
1371
 
                         '# Bazaar revision bundle v%s\n' % self.format)
1372
 
        self.assertEqual(bundle_txt.readline(), '#\n')
1373
 
        rev = self.b1.repository.get_revision(rev_id)
1374
 
        bundle_txt.seek(0)
1375
 
        return bundle_txt, rev_ids
1376
 
 
1377
 
    def get_bundle_tree(self, bundle, revision_id):
1378
 
        repository = self.make_repository('repo')
1379
 
        bundle.install_revisions(repository)
1380
 
        return repository.revision_tree(revision_id)
1381
 
 
1382
 
    def test_creation(self):
1383
 
        tree = self.make_branch_and_tree('tree')
1384
 
        self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
1385
 
        tree.add('file', 'fileid-2')
1386
 
        tree.commit('added file', rev_id='rev1')
1387
 
        self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
1388
 
        tree.commit('changed file', rev_id='rev2')
1389
 
        s = StringIO()
1390
 
        serializer = BundleSerializerV4('1.0')
1391
 
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
1392
 
        s.seek(0)
1393
 
        tree2 = self.make_branch_and_tree('target')
1394
 
        target_repo = tree2.branch.repository
1395
 
        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)
1406
 
        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',),)])
1412
 
        self.assertEqual('changed file',
1413
 
                         target_repo.get_revision('rev2').message)
1414
 
 
1415
 
    @staticmethod
1416
 
    def get_raw(bundle_file):
1417
 
        bundle_file.seek(0)
1418
 
        line = bundle_file.readline()
1419
 
        line = bundle_file.readline()
1420
 
        lines = bundle_file.readlines()
1421
 
        return ''.join(lines).decode('bz2')
1422
 
 
1423
 
    def test_copy_signatures(self):
1424
 
        tree_a = self.make_branch_and_tree('tree_a')
1425
 
        import bzrlib.gpg
1426
 
        import bzrlib.commit as commit
1427
 
        oldstrategy = bzrlib.gpg.GPGStrategy
1428
 
        branch = tree_a.branch
1429
 
        repo_a = branch.repository
1430
 
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1431
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
1432
 
        try:
1433
 
            from bzrlib.testament import Testament
1434
 
            # monkey patch gpg signing mechanism
1435
 
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1436
 
            new_config = test_commit.MustSignConfig()
1437
 
            commit.Commit(config_stack=new_config).commit(message="base",
1438
 
                                                    allow_pointless=True,
1439
 
                                                    rev_id='B',
1440
 
                                                    working_tree=tree_a)
1441
 
            def sign(text):
1442
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
1443
 
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
1444
 
        finally:
1445
 
            bzrlib.gpg.GPGStrategy = oldstrategy
1446
 
        tree_b = self.make_branch_and_tree('tree_b')
1447
 
        repo_b = tree_b.branch.repository
1448
 
        s = StringIO()
1449
 
        serializer = BundleSerializerV4('4')
1450
 
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
1451
 
        s.seek(0)
1452
 
        install_bundle(repo_b, serializer.read(s))
1453
 
        self.assertTrue(repo_b.has_signature_for_revision_id('B'))
1454
 
        self.assertEqual(repo_b.get_signature_text('B'),
1455
 
                         repo_a.get_signature_text('B'))
1456
 
        s.seek(0)
1457
 
        # ensure repeat installs are harmless
1458
 
        install_bundle(repo_b, serializer.read(s))
1459
 
 
1460
 
 
1461
 
class V4_2aBundleTester(V4BundleTester):
1462
 
 
1463
 
    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)
1653
 
 
1654
 
 
1655
 
class MungedBundleTester(object):
 
908
class MungedBundleTester(TestCaseWithTransport):
1656
909
 
1657
910
    def build_test_bundle(self):
1658
911
        wt = self.make_branch_and_tree('b1')
1667
920
 
1668
921
        bundle_txt = StringIO()
1669
922
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
1670
 
                               'a@cset-0-1', bundle_txt, self.format)
1671
 
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
 
923
                               'a@cset-0-1', bundle_txt)
 
924
        self.assertEqual(['a@cset-0-2'], rev_ids)
1672
925
        bundle_txt.seek(0, 0)
1673
926
        return bundle_txt
1674
927
 
1703
956
        bundle = read_bundle(bundle_txt)
1704
957
        self.check_valid(bundle)
1705
958
 
1706
 
 
1707
 
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
1708
 
 
1709
 
    format = '0.9'
1710
 
 
1711
959
    def test_missing_trailing_whitespace(self):
1712
960
        bundle_txt = self.build_test_bundle()
1713
961
 
1741
989
        bundle = read_bundle(bundle_txt)
1742
990
        self.check_valid(bundle)
1743
991
 
1744
 
 
1745
 
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
1746
 
 
1747
 
    format = '4'
1748
 
 
1749
 
 
1750
 
class TestBundleWriterReader(tests.TestCase):
1751
 
 
1752
 
    def test_roundtrip_record(self):
1753
 
        fileobj = StringIO()
1754
 
        writer = v4.BundleWriter(fileobj)
1755
 
        writer.begin()
1756
 
        writer.add_info_record(foo='bar')
1757
 
        writer._add_record("Record body", {'parents': ['1', '3'],
1758
 
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1759
 
        writer.end()
1760
 
        fileobj.seek(0)
1761
 
        reader = v4.BundleReader(fileobj, stream_input=True)
1762
 
        record_iter = reader.iter_records()
1763
 
        record = record_iter.next()
1764
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1765
 
            'info', None, None), record)
1766
 
        record = record_iter.next()
1767
 
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1768
 
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1769
 
                          record)
1770
 
 
1771
 
    def test_roundtrip_record_memory_hungry(self):
1772
 
        fileobj = StringIO()
1773
 
        writer = v4.BundleWriter(fileobj)
1774
 
        writer.begin()
1775
 
        writer.add_info_record(foo='bar')
1776
 
        writer._add_record("Record body", {'parents': ['1', '3'],
1777
 
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1778
 
        writer.end()
1779
 
        fileobj.seek(0)
1780
 
        reader = v4.BundleReader(fileobj, stream_input=False)
1781
 
        record_iter = reader.iter_records()
1782
 
        record = record_iter.next()
1783
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1784
 
            'info', None, None), record)
1785
 
        record = record_iter.next()
1786
 
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1787
 
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1788
 
                          record)
1789
 
 
1790
 
    def test_encode_name(self):
1791
 
        self.assertEqual('revision/rev1',
1792
 
            v4.BundleWriter.encode_name('revision', 'rev1'))
1793
 
        self.assertEqual('file/rev//1/file-id-1',
1794
 
            v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
1795
 
        self.assertEqual('info',
1796
 
            v4.BundleWriter.encode_name('info', None, None))
1797
 
 
1798
 
    def test_decode_name(self):
1799
 
        self.assertEqual(('revision', 'rev1', None),
1800
 
            v4.BundleReader.decode_name('revision/rev1'))
1801
 
        self.assertEqual(('file', 'rev/1', 'file-id-1'),
1802
 
            v4.BundleReader.decode_name('file/rev//1/file-id-1'))
1803
 
        self.assertEqual(('info', None, None),
1804
 
                         v4.BundleReader.decode_name('info'))
1805
 
 
1806
 
    def test_too_many_names(self):
1807
 
        fileobj = StringIO()
1808
 
        writer = v4.BundleWriter(fileobj)
1809
 
        writer.begin()
1810
 
        writer.add_info_record(foo='bar')
1811
 
        writer._container.add_bytes_record('blah', ['two', 'names'])
1812
 
        writer.end()
1813
 
        fileobj.seek(0)
1814
 
        record_iter = v4.BundleReader(fileobj).iter_records()
1815
 
        record = record_iter.next()
1816
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1817
 
            '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