~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Patch Queue Manager
  • Date: 2016-04-21 04:10:52 UTC
  • mfrom: (6616.1.1 fix-en-user-guide)
  • Revision ID: pqm@pqm.ubuntu.com-20160421041052-clcye7ns1qcl2n7w
(richard-wilbur) Ensure build of English use guide always uses English text
 even when user's locale specifies a different language. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004-2006 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005-2013, 2016 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
from cStringIO import StringIO
18
18
import os
 
19
import SocketServer
19
20
import sys
20
 
import tempfile
21
21
 
22
 
from bzrlib.builtins import merge
23
 
from bzrlib.bzrdir import BzrDir
 
22
from bzrlib import (
 
23
    bzrdir,
 
24
    diff,
 
25
    errors,
 
26
    inventory,
 
27
    merge,
 
28
    osutils,
 
29
    revision as _mod_revision,
 
30
    tests,
 
31
    treebuilder,
 
32
    )
 
33
from bzrlib.bundle import read_mergeable_from_url
24
34
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
25
35
from bzrlib.bundle.bundle_data import BundleTree
26
 
from bzrlib.bundle.serializer import write_bundle, read_bundle
27
 
from bzrlib.branch import Branch
28
 
from bzrlib.diff import internal_diff
29
 
from bzrlib.delta import compare_trees
30
 
from bzrlib.errors import BzrError, TestamentMismatch, NotABundle, BadBundle
31
 
from bzrlib.merge import Merge3Merger
32
 
from bzrlib.osutils import has_symlinks, sha_file
33
 
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
34
 
                          TestCase, TestSkipped)
 
36
from bzrlib.directory_service import directories
 
37
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
 
38
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
 
39
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
 
40
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
 
41
from bzrlib.repofmt import knitrepo
 
42
from bzrlib.tests import (
 
43
    features,
 
44
    test_commit,
 
45
    test_read_bundle,
 
46
    test_server,
 
47
    )
35
48
from bzrlib.transform import TreeTransform
36
 
from bzrlib.workingtree import WorkingTree
 
49
 
 
50
 
 
51
def get_text(vf, key):
 
52
    """Get the fulltext for a given revision id that is present in the vf"""
 
53
    stream = vf.get_record_stream([key], 'unordered', True)
 
54
    record = stream.next()
 
55
    return record.get_bytes_as('fulltext')
 
56
 
 
57
 
 
58
def get_inventory_text(repo, revision_id):
 
59
    """Get the fulltext for the inventory at revision id"""
 
60
    repo.lock_read()
 
61
    try:
 
62
        return get_text(repo.inventories, (revision_id,))
 
63
    finally:
 
64
        repo.unlock()
37
65
 
38
66
 
39
67
class MockTree(object):
 
68
 
40
69
    def __init__(self):
41
 
        from bzrlib.inventory import RootEntry, ROOT_ID
 
70
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
42
71
        object.__init__(self)
43
72
        self.paths = {ROOT_ID: ""}
44
73
        self.ids = {"": ROOT_ID}
45
74
        self.contents = {}
46
 
        self.root = RootEntry(ROOT_ID)
 
75
        self.root = InventoryDirectory(ROOT_ID, '', None)
47
76
 
48
77
    inventory = property(lambda x:x)
49
 
 
50
 
    def __iter__(self):
51
 
        return self.paths.iterkeys()
 
78
    root_inventory = property(lambda x:x)
 
79
 
 
80
    def get_root_id(self):
 
81
        return self.root.file_id
 
82
 
 
83
    def all_file_ids(self):
 
84
        return set(self.paths.keys())
 
85
 
 
86
    def is_executable(self, file_id):
 
87
        # Not all the files are executable.
 
88
        return False
52
89
 
53
90
    def __getitem__(self, file_id):
54
91
        if file_id == self.root.file_id:
66
103
        for path, file_id in self.ids.iteritems():
67
104
            yield path, self[file_id]
68
105
 
69
 
    def get_file_kind(self, file_id):
 
106
    def kind(self, file_id):
70
107
        if file_id in self.contents:
71
108
            kind = 'file'
72
109
        else:
74
111
        return kind
75
112
 
76
113
    def make_entry(self, file_id, path):
77
 
        from bzrlib.inventory import (InventoryEntry, InventoryFile
78
 
                                    , InventoryDirectory, InventoryLink)
 
114
        from bzrlib.inventory import (InventoryFile , InventoryDirectory,
 
115
            InventoryLink)
79
116
        name = os.path.basename(path)
80
 
        kind = self.get_file_kind(file_id)
 
117
        kind = self.kind(file_id)
81
118
        parent_id = self.parent_id(file_id)
82
119
        text_sha_1, text_size = self.contents_stats(file_id)
83
120
        if kind == 'directory':
84
121
            ie = InventoryDirectory(file_id, name, parent_id)
85
122
        elif kind == 'file':
86
123
            ie = InventoryFile(file_id, name, parent_id)
 
124
            ie.text_sha1 = text_sha_1
 
125
            ie.text_size = text_size
87
126
        elif kind == 'symlink':
88
127
            ie = InventoryLink(file_id, name, parent_id)
89
128
        else:
90
 
            raise BzrError('unknown kind %r' % kind)
91
 
        ie.text_sha1 = text_sha_1
92
 
        ie.text_size = text_size
 
129
            raise errors.BzrError('unknown kind %r' % kind)
93
130
        return ie
94
131
 
95
132
    def add_dir(self, file_id, path):
96
133
        self.paths[file_id] = path
97
134
        self.ids[path] = file_id
98
 
    
 
135
 
99
136
    def add_file(self, file_id, path, contents):
100
137
        self.add_dir(file_id, path)
101
138
        self.contents[file_id] = contents
115
152
        result.seek(0,0)
116
153
        return result
117
154
 
 
155
    def get_file_revision(self, file_id):
 
156
        return self.inventory[file_id].revision
 
157
 
 
158
    def get_file_size(self, file_id):
 
159
        return self.inventory[file_id].text_size
 
160
 
 
161
    def get_file_sha1(self, file_id):
 
162
        return self.inventory[file_id].text_sha1
 
163
 
118
164
    def contents_stats(self, file_id):
119
165
        if file_id not in self.contents:
120
166
            return None, None
121
 
        text_sha1 = sha_file(self.get_file(file_id))
 
167
        text_sha1 = osutils.sha_file(self.get_file(file_id))
122
168
        return text_sha1, len(self.contents[file_id])
123
169
 
124
170
 
125
 
class BTreeTester(TestCase):
 
171
class BTreeTester(tests.TestCase):
126
172
    """A simple unittest tester for the BundleTree class."""
127
173
 
128
174
    def make_tree_1(self):
132
178
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
133
179
        mtree.add_dir("d", "grandparent/alt_parent")
134
180
        return BundleTree(mtree, ''), mtree
135
 
        
 
181
 
136
182
    def test_renames(self):
137
183
        """Ensure that file renames have the proper effect on children"""
138
184
        btree = self.make_tree_1()[0]
139
185
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
140
 
        self.assertEqual(btree.old_path("grandparent/parent"), 
 
186
        self.assertEqual(btree.old_path("grandparent/parent"),
141
187
                         "grandparent/parent")
142
188
        self.assertEqual(btree.old_path("grandparent/parent/file"),
143
189
                         "grandparent/parent/file")
150
196
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
151
197
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
152
198
 
153
 
        assert btree.path2id("grandparent2") is None
154
 
        assert btree.path2id("grandparent2/parent") is None
155
 
        assert btree.path2id("grandparent2/parent/file") is None
 
199
        self.assertTrue(btree.path2id("grandparent2") is None)
 
200
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
201
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
156
202
 
157
203
        btree.note_rename("grandparent", "grandparent2")
158
 
        assert btree.old_path("grandparent") is None
159
 
        assert btree.old_path("grandparent/parent") is None
160
 
        assert btree.old_path("grandparent/parent/file") is None
 
204
        self.assertTrue(btree.old_path("grandparent") is None)
 
205
        self.assertTrue(btree.old_path("grandparent/parent") is None)
 
206
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
161
207
 
162
208
        self.assertEqual(btree.id2path("a"), "grandparent2")
163
209
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
167
213
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
168
214
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
169
215
 
170
 
        assert btree.path2id("grandparent") is None
171
 
        assert btree.path2id("grandparent/parent") is None
172
 
        assert btree.path2id("grandparent/parent/file") is None
 
216
        self.assertTrue(btree.path2id("grandparent") is None)
 
217
        self.assertTrue(btree.path2id("grandparent/parent") is None)
 
218
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
173
219
 
174
220
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
175
221
        self.assertEqual(btree.id2path("a"), "grandparent2")
180
226
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
181
227
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
182
228
 
183
 
        assert btree.path2id("grandparent2/parent") is None
184
 
        assert btree.path2id("grandparent2/parent/file") is None
 
229
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
 
230
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
185
231
 
186
 
        btree.note_rename("grandparent/parent/file", 
 
232
        btree.note_rename("grandparent/parent/file",
187
233
                          "grandparent2/parent2/file2")
188
234
        self.assertEqual(btree.id2path("a"), "grandparent2")
189
235
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
193
239
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
194
240
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
195
241
 
196
 
        assert btree.path2id("grandparent2/parent2/file") is None
 
242
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
197
243
 
198
244
    def test_moves(self):
199
245
        """Ensure that file moves have the proper effect on children"""
200
246
        btree = self.make_tree_1()[0]
201
 
        btree.note_rename("grandparent/parent/file", 
 
247
        btree.note_rename("grandparent/parent/file",
202
248
                          "grandparent/alt_parent/file")
203
249
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
204
250
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
205
 
        assert btree.path2id("grandparent/parent/file") is None
 
251
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
206
252
 
207
253
    def unified_diff(self, old, new):
208
254
        out = StringIO()
209
 
        internal_diff("old", old, "new", new, out)
 
255
        diff.internal_diff("old", old, "new", new, out)
210
256
        out.seek(0,0)
211
257
        return out.read()
212
258
 
213
259
    def make_tree_2(self):
214
260
        btree = self.make_tree_1()[0]
215
 
        btree.note_rename("grandparent/parent/file", 
 
261
        btree.note_rename("grandparent/parent/file",
216
262
                          "grandparent/alt_parent/file")
217
 
        assert btree.id2path("e") is None
218
 
        assert btree.path2id("grandparent/parent/file") is None
 
263
        self.assertTrue(btree.id2path("e") is None)
 
264
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
219
265
        btree.note_id("e", "grandparent/parent/file")
220
266
        return btree
221
267
 
247
293
    def make_tree_3(self):
248
294
        btree, mtree = self.make_tree_1()
249
295
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
250
 
        btree.note_rename("grandparent/parent/file", 
 
296
        btree.note_rename("grandparent/parent/file",
251
297
                          "grandparent/alt_parent/file")
252
 
        btree.note_rename("grandparent/parent/topping", 
 
298
        btree.note_rename("grandparent/parent/topping",
253
299
                          "grandparent/alt_parent/stopping")
254
300
        return btree
255
301
 
279
325
        btree = self.make_tree_1()[0]
280
326
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
281
327
        btree.note_deletion("grandparent/parent/file")
282
 
        assert btree.id2path("c") is None
283
 
        assert btree.path2id("grandparent/parent/file") is None
 
328
        self.assertTrue(btree.id2path("c") is None)
 
329
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
284
330
 
285
331
    def sorted_ids(self, tree):
286
 
        ids = list(tree)
 
332
        ids = list(tree.all_file_ids())
287
333
        ids.sort()
288
334
        return ids
289
335
 
290
336
    def test_iteration(self):
291
337
        """Ensure that iteration through ids works properly"""
292
338
        btree = self.make_tree_1()[0]
293
 
        self.assertEqual(self.sorted_ids(btree), ['a', 'b', 'c', 'd'])
 
339
        self.assertEqual(self.sorted_ids(btree),
 
340
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
294
341
        btree.note_deletion("grandparent/parent/file")
295
342
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
296
 
        btree.note_last_changed("grandparent/alt_parent/fool", 
 
343
        btree.note_last_changed("grandparent/alt_parent/fool",
297
344
                                "revisionidiguess")
298
 
        self.assertEqual(self.sorted_ids(btree), ['a', 'b', 'd', 'e'])
299
 
 
300
 
 
301
 
class BundleTester(TestCaseWithTransport):
 
345
        self.assertEqual(self.sorted_ids(btree),
 
346
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
 
347
 
 
348
 
 
349
class BundleTester1(tests.TestCaseWithTransport):
 
350
 
 
351
    def test_mismatched_bundle(self):
 
352
        format = bzrdir.BzrDirMetaFormat1()
 
353
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
354
        serializer = BundleSerializerV08('0.8')
 
355
        b = self.make_branch('.', format=format)
 
356
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
 
357
                          b.repository, [], {}, StringIO())
 
358
 
 
359
    def test_matched_bundle(self):
 
360
        """Don't raise IncompatibleBundleFormat for knit2 and bundle0.9"""
 
361
        format = bzrdir.BzrDirMetaFormat1()
 
362
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
363
        serializer = BundleSerializerV09('0.9')
 
364
        b = self.make_branch('.', format=format)
 
365
        serializer.write(b.repository, [], {}, StringIO())
 
366
 
 
367
    def test_mismatched_model(self):
 
368
        """Try copying a bundle from knit2 to knit1"""
 
369
        format = bzrdir.BzrDirMetaFormat1()
 
370
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
371
        source = self.make_branch_and_tree('source', format=format)
 
372
        source.commit('one', rev_id='one-id')
 
373
        source.commit('two', rev_id='two-id')
 
374
        text = StringIO()
 
375
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
 
376
                     format='0.9')
 
377
        text.seek(0)
 
378
 
 
379
        format = bzrdir.BzrDirMetaFormat1()
 
380
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
381
        target = self.make_branch('target', format=format)
 
382
        self.assertRaises(errors.IncompatibleRevision, install_bundle,
 
383
                          target.repository, read_bundle(text))
 
384
 
 
385
 
 
386
class BundleTester(object):
 
387
 
 
388
    def bzrdir_format(self):
 
389
        format = bzrdir.BzrDirMetaFormat1()
 
390
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
391
        return format
 
392
 
 
393
    def make_branch_and_tree(self, path, format=None):
 
394
        if format is None:
 
395
            format = self.bzrdir_format()
 
396
        return tests.TestCaseWithTransport.make_branch_and_tree(
 
397
            self, path, format)
 
398
 
 
399
    def make_branch(self, path, format=None):
 
400
        if format is None:
 
401
            format = self.bzrdir_format()
 
402
        return tests.TestCaseWithTransport.make_branch(self, path, format)
302
403
 
303
404
    def create_bundle_text(self, base_rev_id, rev_id):
304
405
        bundle_txt = StringIO()
305
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
306
 
                               bundle_txt)
 
406
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
 
407
                               bundle_txt, format=self.format)
307
408
        bundle_txt.seek(0)
308
 
        self.assertEqual(bundle_txt.readline(), 
309
 
                         '# Bazaar revision bundle v0.8\n')
 
409
        self.assertEqual(bundle_txt.readline(),
 
410
                         '# Bazaar revision bundle v%s\n' % self.format)
310
411
        self.assertEqual(bundle_txt.readline(), '#\n')
311
412
 
312
413
        rev = self.b1.repository.get_revision(rev_id)
313
414
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
314
415
                         u'# message:\n')
315
 
 
316
 
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
317
416
        bundle_txt.seek(0)
318
417
        return bundle_txt, rev_ids
319
418
 
321
420
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
322
421
        Make sure that the text generated is valid, and that it
323
422
        can be applied against the base, and generate the same information.
324
 
        
325
 
        :return: The in-memory bundle 
 
423
 
 
424
        :return: The in-memory bundle
326
425
        """
327
426
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
328
427
 
329
 
        # This should also validate the generated bundle 
 
428
        # This should also validate the generated bundle
330
429
        bundle = read_bundle(bundle_txt)
331
430
        repository = self.b1.repository
332
431
        for bundle_rev in bundle.real_revisions:
336
435
            # it
337
436
            branch_rev = repository.get_revision(bundle_rev.revision_id)
338
437
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
339
 
                      'timestamp', 'timezone', 'message', 'committer', 
 
438
                      'timestamp', 'timezone', 'message', 'committer',
340
439
                      'parent_ids', 'properties'):
341
 
                self.assertEqual(getattr(branch_rev, a), 
 
440
                self.assertEqual(getattr(branch_rev, a),
342
441
                                 getattr(bundle_rev, a))
343
 
            self.assertEqual(len(branch_rev.parent_ids), 
 
442
            self.assertEqual(len(branch_rev.parent_ids),
344
443
                             len(bundle_rev.parent_ids))
345
 
        self.assertEqual(rev_ids, 
 
444
        self.assertEqual(rev_ids,
346
445
                         [r.revision_id for r in bundle.real_revisions])
347
446
        self.valid_apply_bundle(base_rev_id, bundle,
348
447
                                   checkout_dir=checkout_dir)
352
451
    def get_invalid_bundle(self, base_rev_id, rev_id):
353
452
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
354
453
        Munge the text so that it's invalid.
355
 
        
 
454
 
356
455
        :return: The in-memory bundle
357
456
        """
358
457
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
359
 
        new_text = bundle_txt.getvalue().replace('executable:no', 
 
458
        new_text = bundle_txt.getvalue().replace('executable:no',
360
459
                                               'executable:yes')
361
460
        bundle_txt = StringIO(new_text)
362
461
        bundle = read_bundle(bundle_txt)
363
462
        self.valid_apply_bundle(base_rev_id, bundle)
364
 
        return bundle 
 
463
        return bundle
365
464
 
366
465
    def test_non_bundle(self):
367
 
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
 
466
        self.assertRaises(errors.NotABundle,
 
467
                          read_bundle, StringIO('#!/bin/sh\n'))
368
468
 
369
469
    def test_malformed(self):
370
 
        self.assertRaises(BadBundle, read_bundle, 
 
470
        self.assertRaises(errors.BadBundle, read_bundle,
371
471
                          StringIO('# Bazaar revision bundle v'))
372
472
 
373
473
    def test_crlf_bundle(self):
374
474
        try:
375
 
            read_bundle(StringIO('# Bazaar revision bundle v0.7\r\n'))
376
 
        except BadBundle:
 
475
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
 
476
        except errors.BadBundle:
377
477
            # It is currently permitted for bundles with crlf line endings to
378
478
            # make read_bundle raise a BadBundle, but this should be fixed.
379
479
            # Anything else, especially NotABundle, is an error.
384
484
        """
385
485
 
386
486
        if checkout_dir is None:
387
 
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
 
487
            checkout_dir = osutils.mkdtemp(prefix='test-branch-', dir='.')
388
488
        else:
389
489
            if not os.path.exists(checkout_dir):
390
490
                os.mkdir(checkout_dir)
391
 
        tree = BzrDir.create_standalone_workingtree(checkout_dir)
 
491
        tree = self.make_branch_and_tree(checkout_dir)
392
492
        s = StringIO()
393
 
        ancestors = write_bundle(self.b1.repository, rev_id, None, s)
 
493
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
 
494
                                 format=self.format)
394
495
        s.seek(0)
395
 
        assert isinstance(s.getvalue(), str), (
396
 
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
 
496
        self.assertIsInstance(s.getvalue(), str)
397
497
        install_bundle(tree.branch.repository, read_bundle(s))
398
498
        for ancestor in ancestors:
399
499
            old = self.b1.repository.revision_tree(ancestor)
400
500
            new = tree.branch.repository.revision_tree(ancestor)
401
 
 
402
 
            # Check that there aren't any inventory level changes
403
 
            delta = compare_trees(old, new)
404
 
            self.assertFalse(delta.has_changed(),
405
 
                             'Revision %s not copied correctly.'
406
 
                             % (ancestor,))
407
 
 
408
 
            # Now check that the file contents are all correct
409
 
            for inventory_id in old:
410
 
                try:
411
 
                    old_file = old.get_file(inventory_id)
412
 
                except:
413
 
                    continue
414
 
                if old_file is None:
415
 
                    continue
416
 
                self.assertEqual(old_file.read(),
417
 
                                 new.get_file(inventory_id).read())
418
 
        if rev_id is not None:
419
 
            rh = self.b1.revision_history()
420
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
 
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)
421
525
            tree.update()
422
 
            delta = compare_trees(self.b1.repository.revision_tree(rev_id),
423
 
                                  tree)
 
526
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
424
527
            self.assertFalse(delta.has_changed(),
425
 
                             'Working tree has modifications')
 
528
                             'Working tree has modifications: %s' % delta)
426
529
        return tree
427
530
 
428
531
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
430
533
        sure everything matches the builtin branch.
431
534
        """
432
535
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
 
536
        to_tree.lock_write()
 
537
        try:
 
538
            self._valid_apply_bundle(base_rev_id, info, to_tree)
 
539
        finally:
 
540
            to_tree.unlock()
 
541
 
 
542
    def _valid_apply_bundle(self, base_rev_id, info, to_tree):
 
543
        original_parents = to_tree.get_parent_ids()
433
544
        repository = to_tree.branch.repository
 
545
        original_parents = to_tree.get_parent_ids()
434
546
        self.assertIs(repository.has_revision(base_rev_id), True)
435
547
        for rev in info.real_revisions:
436
 
            self.assert_(not repository.has_revision(rev.revision_id),
437
 
                'Revision {%s} present before applying bundle' 
438
 
                % rev.revision_id)
439
 
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
 
548
            self.assertTrue(not repository.has_revision(rev.revision_id),
 
549
                            'Revision {%s} present before applying bundle'
 
550
                            % rev.revision_id)
 
551
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
440
552
 
441
553
        for rev in info.real_revisions:
442
 
            self.assert_(repository.has_revision(rev.revision_id),
443
 
                'Missing revision {%s} after applying bundle' 
444
 
                % rev.revision_id)
 
554
            self.assertTrue(repository.has_revision(rev.revision_id),
 
555
                            'Missing revision {%s} after applying bundle'
 
556
                            % rev.revision_id)
445
557
 
446
 
        self.assert_(to_tree.branch.repository.has_revision(info.target))
 
558
        self.assertTrue(to_tree.branch.repository.has_revision(info.target))
447
559
        # Do we also want to verify that all the texts have been added?
448
560
 
449
 
        self.assert_(info.target in to_tree.pending_merges())
450
 
 
 
561
        self.assertEqual(original_parents + [info.target],
 
562
                         to_tree.get_parent_ids())
451
563
 
452
564
        rev = info.real_revisions[-1]
453
565
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
454
566
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
455
 
        
 
567
 
456
568
        # TODO: make sure the target tree is identical to base tree
457
569
        #       we might also check the working tree.
458
570
 
477
589
        self.tree1 = self.make_branch_and_tree('b1')
478
590
        self.b1 = self.tree1.branch
479
591
 
480
 
        open('b1/one', 'wb').write('one\n')
481
 
        self.tree1.add('one')
 
592
        self.build_tree_contents([('b1/one', 'one\n')])
 
593
        self.tree1.add('one', 'one-id')
 
594
        self.tree1.set_root_id('root-id')
482
595
        self.tree1.commit('add one', rev_id='a@cset-0-1')
483
596
 
484
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
485
 
        # FIXME: The current write_bundle api no longer supports
486
 
        #        setting a custom summary message
487
 
        #        We should re-introduce the ability, and update
488
 
        #        the tests to make sure it works.
489
 
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
490
 
        #         message='With a specialized message')
 
597
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
491
598
 
492
599
        # Make sure we can handle files with spaces, tabs, other
493
600
        # bogus characters
501
608
                , 'b1/sub/sub/'
502
609
                , 'b1/sub/sub/nonempty.txt'
503
610
                ])
504
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
505
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
 
611
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
 
612
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
506
613
        tt = TreeTransform(self.tree1)
507
614
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
508
615
        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')
509
619
        self.tree1.add([
510
 
                'with space.txt'
511
 
                , 'dir'
 
620
                  'dir'
512
621
                , 'dir/filein subdir.c'
513
622
                , 'dir/WithCaps.txt'
514
623
                , 'dir/ pre space'
522
631
 
523
632
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
524
633
 
525
 
        # Check a rollup bundle 
526
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
 
634
        # Check a rollup bundle
 
635
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
527
636
 
528
637
        # Now delete entries
529
638
        self.tree1.remove(
536
645
        tt.set_executability(False, trans_id)
537
646
        tt.apply()
538
647
        self.tree1.commit('removed', rev_id='a@cset-0-3')
539
 
        
 
648
 
540
649
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
541
 
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
542
 
                          'a@cset-0-2', 'a@cset-0-3')
543
 
        # Check a rollup bundle 
544
 
        bundle = self.get_valid_bundle(None, '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')
545
656
 
546
657
        # Now move the directory
547
658
        self.tree1.rename_one('dir', 'sub/dir')
548
659
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
549
660
 
550
661
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
551
 
        # Check a rollup bundle 
552
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
 
662
        # Check a rollup bundle
 
663
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
553
664
 
554
665
        # Modified files
555
 
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
556
 
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
557
 
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
558
 
        self.tree1.rename_one('sub/dir/ pre space', 
 
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',
559
671
                              'sub/ start space')
560
672
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
561
673
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
567
679
                          verbose=False)
568
680
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
569
681
        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')
 
686
        self.assertEqualDiff(tree1_inv, tree2_inv)
570
687
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
571
688
        other.commit('rename file', rev_id='a@cset-0-6b')
572
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
 
689
        self.tree1.merge_from_branch(other.branch)
573
690
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
574
691
                          verbose=False)
575
692
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
576
693
 
577
 
    def test_symlink_bundle(self):
578
 
        if not has_symlinks():
579
 
            raise TestSkipped("No symlink support")
580
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
694
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
 
695
        link_id = 'link-1'
 
696
 
 
697
        self.requireFeature(features.SymlinkFeature)
 
698
        self.tree1 = self.make_branch_and_tree('b1')
581
699
        self.b1 = self.tree1.branch
 
700
 
582
701
        tt = TreeTransform(self.tree1)
583
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
 
702
        tt.new_symlink(link_name, tt.root, link_target, link_id)
584
703
        tt.apply()
585
704
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
586
 
        self.get_valid_bundle(None, '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
 
587
711
        tt = TreeTransform(self.tree1)
588
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
712
        trans_id = tt.trans_id_tree_file_id(link_id)
589
713
        tt.adjust_path('link2', tt.root, trans_id)
590
714
        tt.delete_contents(trans_id)
591
 
        tt.create_symlink('mars', trans_id)
 
715
        tt.create_symlink(new_link_target, trans_id)
592
716
        tt.apply()
593
717
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
594
 
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
718
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
 
719
        if getattr(bundle ,'revision_tree', None) is not None:
 
720
            # Not all bundle formats supports revision_tree
 
721
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
 
722
            self.assertEqual(new_link_target,
 
723
                             bund_tree.get_symlink_target(link_id))
 
724
 
595
725
        tt = TreeTransform(self.tree1)
596
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
726
        trans_id = tt.trans_id_tree_file_id(link_id)
597
727
        tt.delete_contents(trans_id)
598
728
        tt.create_symlink('jupiter', trans_id)
599
729
        tt.apply()
600
730
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
601
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
731
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
732
 
602
733
        tt = TreeTransform(self.tree1)
603
 
        trans_id = tt.trans_id_tree_file_id('link-1')
 
734
        trans_id = tt.trans_id_tree_file_id(link_id)
604
735
        tt.delete_contents(trans_id)
605
736
        tt.apply()
606
737
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
607
 
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
738
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
739
 
 
740
    def test_symlink_bundle(self):
 
741
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
 
742
 
 
743
    def test_unicode_symlink_bundle(self):
 
744
        self.requireFeature(features.UnicodeFilenameFeature)
 
745
        self._test_symlink_bundle(u'\N{Euro Sign}link',
 
746
                                  u'bar/\N{Euro Sign}foo',
 
747
                                  u'mars\N{Euro Sign}')
608
748
 
609
749
    def test_binary_bundle(self):
610
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
750
        self.tree1 = self.make_branch_and_tree('b1')
611
751
        self.b1 = self.tree1.branch
612
752
        tt = TreeTransform(self.tree1)
613
 
        tt.new_file('file', tt.root, '\x00\xff', 'binary-1')
614
 
        tt.new_file('file2', tt.root, '\x00\xff', 'binary-2')
 
753
 
 
754
        # Add
 
755
        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')
615
758
        tt.apply()
616
759
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
617
 
        self.get_valid_bundle(None, 'b@cset-0-1')
 
760
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
761
 
 
762
        # Delete
618
763
        tt = TreeTransform(self.tree1)
619
764
        trans_id = tt.trans_id_tree_file_id('binary-1')
620
765
        tt.delete_contents(trans_id)
621
766
        tt.apply()
622
767
        self.tree1.commit('delete binary', rev_id='b@cset-0-2')
623
768
        self.get_valid_bundle('b@cset-0-1', 'b@cset-0-2')
 
769
 
 
770
        # Rename & modify
624
771
        tt = TreeTransform(self.tree1)
625
772
        trans_id = tt.trans_id_tree_file_id('binary-2')
626
773
        tt.adjust_path('file3', tt.root, trans_id)
627
774
        tt.delete_contents(trans_id)
628
 
        tt.create_file('filecontents\x00', trans_id)
 
775
        tt.create_file('file\rcontents\x00\n\x00', trans_id)
629
776
        tt.apply()
630
777
        self.tree1.commit('rename and modify binary', rev_id='b@cset-0-3')
631
778
        self.get_valid_bundle('b@cset-0-2', 'b@cset-0-3')
 
779
 
 
780
        # Modify
632
781
        tt = TreeTransform(self.tree1)
633
782
        trans_id = tt.trans_id_tree_file_id('binary-2')
634
783
        tt.delete_contents(trans_id)
635
 
        tt.create_file('\x00filecontents', trans_id)
 
784
        tt.create_file('\x00file\rcontents', trans_id)
636
785
        tt.apply()
637
786
        self.tree1.commit('just modify binary', rev_id='b@cset-0-4')
638
787
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
639
788
 
 
789
        # Rollup
 
790
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
791
 
640
792
    def test_last_modified(self):
641
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
793
        self.tree1 = self.make_branch_and_tree('b1')
642
794
        self.b1 = self.tree1.branch
643
795
        tt = TreeTransform(self.tree1)
644
796
        tt.new_file('file', tt.root, 'file', 'file')
659
811
        tt.create_file('file2', trans_id)
660
812
        tt.apply()
661
813
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
662
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
 
814
        self.tree1.merge_from_branch(other.branch)
663
815
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
664
816
                          verbose=False)
665
817
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
666
818
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
667
819
 
668
820
    def test_hide_history(self):
669
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
 
821
        self.tree1 = self.make_branch_and_tree('b1')
670
822
        self.b1 = self.tree1.branch
671
823
 
672
 
        open('b1/one', 'wb').write('one\n')
 
824
        with open('b1/one', 'wb') as f: f.write('one\n')
673
825
        self.tree1.add('one')
674
826
        self.tree1.commit('add file', rev_id='a@cset-0-1')
675
 
        open('b1/one', 'wb').write('two\n')
 
827
        with open('b1/one', 'wb') as f: f.write('two\n')
676
828
        self.tree1.commit('modify', rev_id='a@cset-0-2')
677
 
        open('b1/one', 'wb').write('three\n')
 
829
        with open('b1/one', 'wb') as f: f.write('three\n')
678
830
        self.tree1.commit('modify', rev_id='a@cset-0-3')
679
831
        bundle_file = StringIO()
680
832
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
 
833
                               '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',
681
844
                               'a@cset-0-1', bundle_file)
682
 
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
683
 
        self.assertContainsRe(bundle_file.getvalue(), 'one')
684
 
        self.assertContainsRe(bundle_file.getvalue(), 'three')
 
845
 
 
846
    @staticmethod
 
847
    def get_raw(bundle_file):
 
848
        return bundle_file.getvalue()
685
849
 
686
850
    def test_unicode_bundle(self):
 
851
        self.requireFeature(features.UnicodeFilenameFeature)
687
852
        # Handle international characters
688
853
        os.mkdir('b1')
689
 
        try:
690
 
            f = open(u'b1/with Dod\xe9', 'wb')
691
 
        except UnicodeEncodeError:
692
 
            raise TestSkipped("Filesystem doesn't support unicode")
 
854
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
693
855
 
694
856
        self.tree1 = self.make_branch_and_tree('b1')
695
857
        self.b1 = self.tree1.branch
699
861
            u'William Dod\xe9\n').encode('utf-8'))
700
862
        f.close()
701
863
 
702
 
        self.tree1.add([u'with Dod\xe9'])
703
 
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
 
864
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
865
        self.tree1.commit(u'i18n commit from William Dod\xe9',
704
866
                          rev_id='i18n-1', committer=u'William Dod\xe9')
705
867
 
706
868
        # Add
707
 
        bundle = self.get_valid_bundle(None, 'i18n-1')
 
869
        bundle = self.get_valid_bundle('null:', 'i18n-1')
708
870
 
709
871
        # Modified
710
 
        f = open(u'b1/with Dod\xe9', 'wb')
 
872
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
711
873
        f.write(u'Modified \xb5\n'.encode('utf8'))
712
874
        f.close()
713
875
        self.tree1.commit(u'modified', rev_id='i18n-2')
714
876
 
715
877
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
716
 
        
 
878
 
717
879
        # Renamed
718
 
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
 
880
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
719
881
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
720
882
                          committer=u'Erik B\xe5gfors')
721
883
 
722
884
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
723
885
 
724
886
        # Removed
725
 
        self.tree1.remove([u'B\xe5gfors'])
 
887
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
726
888
        self.tree1.commit(u'removed', rev_id='i18n-4')
727
889
 
728
890
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
729
891
 
730
892
        # Rollup
731
 
        bundle = self.get_valid_bundle(None, 'i18n-4')
 
893
        bundle = self.get_valid_bundle('null:', 'i18n-4')
732
894
 
733
895
 
734
896
    def test_whitespace_bundle(self):
735
897
        if sys.platform in ('win32', 'cygwin'):
736
 
            raise TestSkipped('Windows doesn\'t support filenames'
737
 
                              ' with tabs or trailing spaces')
 
898
            raise tests.TestSkipped('Windows doesn\'t support filenames'
 
899
                                    ' with tabs or trailing spaces')
738
900
        self.tree1 = self.make_branch_and_tree('b1')
739
901
        self.b1 = self.tree1.branch
740
902
 
746
908
        # Added
747
909
        self.tree1.commit('funky whitespace', rev_id='white-1')
748
910
 
749
 
        bundle = self.get_valid_bundle(None, 'white-1')
 
911
        bundle = self.get_valid_bundle('null:', 'white-1')
750
912
 
751
913
        # Modified
752
 
        open('b1/trailing space ', 'ab').write('add some text\n')
 
914
        with open('b1/trailing space ', 'ab') as f: f.write('add some text\n')
753
915
        self.tree1.commit('add text', rev_id='white-2')
754
916
 
755
917
        bundle = self.get_valid_bundle('white-1', 'white-2')
765
927
        self.tree1.commit('removed', rev_id='white-4')
766
928
 
767
929
        bundle = self.get_valid_bundle('white-3', 'white-4')
768
 
        
 
930
 
769
931
        # Now test a complet roll-up
770
 
        bundle = self.get_valid_bundle(None, 'white-4')
771
 
 
772
 
 
773
 
class MungedBundleTester(TestCaseWithTransport):
 
932
        bundle = self.get_valid_bundle('null:', 'white-4')
 
933
 
 
934
    def test_alt_timezone_bundle(self):
 
935
        self.tree1 = self.make_branch_and_memory_tree('b1')
 
936
        self.b1 = self.tree1.branch
 
937
        builder = treebuilder.TreeBuilder()
 
938
 
 
939
        self.tree1.lock_write()
 
940
        builder.start_tree(self.tree1)
 
941
        builder.build(['newfile'])
 
942
        builder.finish_tree()
 
943
 
 
944
        # Asia/Colombo offset = 5 hours 30 minutes
 
945
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
 
946
                          timezone=19800, timestamp=1152544886.0)
 
947
 
 
948
        bundle = self.get_valid_bundle('null:', 'tz-1')
 
949
 
 
950
        rev = bundle.revisions[0]
 
951
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
 
952
        self.assertEqual(19800, rev.timezone)
 
953
        self.assertEqual(1152544886.0, rev.timestamp)
 
954
        self.tree1.unlock()
 
955
 
 
956
    def test_bundle_root_id(self):
 
957
        self.tree1 = self.make_branch_and_tree('b1')
 
958
        self.b1 = self.tree1.branch
 
959
        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)
 
1286
 
 
1287
 
 
1288
class V09BundleKnit2Tester(V08BundleTester):
 
1289
 
 
1290
    format = '0.9'
 
1291
 
 
1292
    def bzrdir_format(self):
 
1293
        format = bzrdir.BzrDirMetaFormat1()
 
1294
        format.repository_format = knitrepo.RepositoryFormatKnit3()
 
1295
        return format
 
1296
 
 
1297
 
 
1298
class V09BundleKnit1Tester(V08BundleTester):
 
1299
 
 
1300
    format = '0.9'
 
1301
 
 
1302
    def bzrdir_format(self):
 
1303
        format = bzrdir.BzrDirMetaFormat1()
 
1304
        format.repository_format = knitrepo.RepositoryFormatKnit1()
 
1305
        return format
 
1306
 
 
1307
 
 
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):
774
1656
 
775
1657
    def build_test_bundle(self):
776
1658
        wt = self.make_branch_and_tree('b1')
780
1662
        wt.commit('add one', rev_id='a@cset-0-1')
781
1663
        self.build_tree(['b1/two'])
782
1664
        wt.add('two')
783
 
        wt.commit('add two', rev_id='a@cset-0-2')
 
1665
        wt.commit('add two', rev_id='a@cset-0-2',
 
1666
                  revprops={'branch-nick':'test'})
784
1667
 
785
1668
        bundle_txt = StringIO()
786
1669
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
787
 
                               'a@cset-0-1', bundle_txt)
788
 
        self.assertEqual(['a@cset-0-2'], rev_ids)
 
1670
                               'a@cset-0-1', bundle_txt, self.format)
 
1671
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
789
1672
        bundle_txt.seek(0, 0)
790
1673
        return bundle_txt
791
1674
 
792
1675
    def check_valid(self, bundle):
793
1676
        """Check that after whatever munging, the final object is valid."""
794
 
        self.assertEqual(['a@cset-0-2'], 
 
1677
        self.assertEqual(['a@cset-0-2'],
795
1678
            [r.revision_id for r in bundle.real_revisions])
796
1679
 
797
1680
    def test_extra_whitespace(self):
820
1703
        bundle = read_bundle(bundle_txt)
821
1704
        self.check_valid(bundle)
822
1705
 
 
1706
 
 
1707
class MungedBundleTesterV09(tests.TestCaseWithTransport, MungedBundleTester):
 
1708
 
 
1709
    format = '0.9'
 
1710
 
823
1711
    def test_missing_trailing_whitespace(self):
824
1712
        bundle_txt = self.build_test_bundle()
825
1713
 
830
1718
        # creates a blank line at the end, and fails if that
831
1719
        # line is stripped
832
1720
        self.assertEqual('\n\n', raw[-2:])
833
 
        bundle_text = StringIO(raw[:-1])
834
 
 
835
 
        bundle = read_bundle(bundle_txt)
836
 
        self.check_valid(bundle)
 
1721
        bundle_txt = StringIO(raw[:-1])
 
1722
 
 
1723
        bundle = read_bundle(bundle_txt)
 
1724
        self.check_valid(bundle)
 
1725
 
 
1726
    def test_opening_text(self):
 
1727
        bundle_txt = self.build_test_bundle()
 
1728
 
 
1729
        bundle_txt = StringIO("Some random\nemail comments\n"
 
1730
                              + bundle_txt.getvalue())
 
1731
 
 
1732
        bundle = read_bundle(bundle_txt)
 
1733
        self.check_valid(bundle)
 
1734
 
 
1735
    def test_trailing_text(self):
 
1736
        bundle_txt = self.build_test_bundle()
 
1737
 
 
1738
        bundle_txt = StringIO(bundle_txt.getvalue() +
 
1739
                              "Some trailing\nrandom\ntext\n")
 
1740
 
 
1741
        bundle = read_bundle(bundle_txt)
 
1742
        self.check_valid(bundle)
 
1743
 
 
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