/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/test_bundle.py

  • Committer: Jelmer Vernooij
  • Date: 2019-09-01 15:33:59 UTC
  • mto: This revision was merged to the branch mainline in revision 7404.
  • Revision ID: jelmer@jelmer.uk-20190901153359-9gl0ai0x5wuiv444
Rename init-repo to init-shared-repo.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2013, 2016 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from cStringIO import StringIO
 
17
import bz2
 
18
from io import BytesIO
18
19
import os
19
 
import socket
20
20
import sys
21
 
import threading
22
21
 
23
 
from bzrlib import (
24
 
    bzrdir,
 
22
from .. import (
25
23
    diff,
26
24
    errors,
27
 
    inventory,
28
25
    merge,
29
26
    osutils,
30
 
    repository,
31
27
    revision as _mod_revision,
32
28
    tests,
33
29
    treebuilder,
34
30
    )
35
 
from bzrlib.bundle import read_mergeable_from_url
36
 
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
37
 
from bzrlib.bundle.bundle_data import BundleTree
38
 
from bzrlib.bzrdir import BzrDir
39
 
from bzrlib.directory_service import directories
40
 
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
41
 
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
42
 
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
43
 
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
44
 
from bzrlib.branch import Branch
45
 
from bzrlib.repofmt import knitrepo
46
 
from bzrlib.tests import (
47
 
    test_read_bundle,
 
31
from ..bzr import (
 
32
    bzrdir,
 
33
    inventory,
 
34
    )
 
35
from ..bzr.bundle.apply_bundle import install_bundle, merge_bundle
 
36
from ..bzr.bundle.bundle_data import BundleTree
 
37
from ..bzr.bundle.serializer import write_bundle, read_bundle, v09, v4
 
38
from ..bzr.bundle.serializer.v08 import BundleSerializerV08
 
39
from ..bzr.bundle.serializer.v09 import BundleSerializerV09
 
40
from ..bzr.bundle.serializer.v4 import BundleSerializerV4
 
41
from ..bzr import knitrepo
 
42
from . import (
 
43
    features,
48
44
    test_commit,
49
45
    )
50
 
from bzrlib.transform import TreeTransform
 
46
from ..tree import find_previous_path
51
47
 
52
48
 
53
49
def get_text(vf, key):
54
50
    """Get the fulltext for a given revision id that is present in the vf"""
55
51
    stream = vf.get_record_stream([key], 'unordered', True)
56
 
    record = stream.next()
 
52
    record = next(stream)
57
53
    return record.get_bytes_as('fulltext')
58
54
 
59
55
 
60
56
def get_inventory_text(repo, revision_id):
61
57
    """Get the fulltext for the inventory at revision id"""
62
 
    repo.lock_read()
63
 
    try:
 
58
    with repo.lock_read():
64
59
        return get_text(repo.inventories, (revision_id,))
65
 
    finally:
66
 
        repo.unlock()
67
60
 
68
61
 
69
62
class MockTree(object):
 
63
 
70
64
    def __init__(self):
71
 
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
 
65
        from ..bzr.inventory import InventoryDirectory, ROOT_ID
72
66
        object.__init__(self)
73
67
        self.paths = {ROOT_ID: ""}
74
68
        self.ids = {"": ROOT_ID}
75
69
        self.contents = {}
76
70
        self.root = InventoryDirectory(ROOT_ID, '', None)
77
71
 
78
 
    inventory = property(lambda x:x)
79
 
 
80
 
    def __iter__(self):
81
 
        return self.paths.iterkeys()
 
72
    inventory = property(lambda x: x)
 
73
    root_inventory = property(lambda x: x)
 
74
 
 
75
    def get_root_id(self):
 
76
        return self.root.file_id
 
77
 
 
78
    def all_file_ids(self):
 
79
        return set(self.paths.keys())
 
80
 
 
81
    def all_versioned_paths(self):
 
82
        return set(self.paths.values())
 
83
 
 
84
    def is_executable(self, path):
 
85
        # Not all the files are executable.
 
86
        return False
82
87
 
83
88
    def __getitem__(self, file_id):
84
89
        if file_id == self.root.file_id:
86
91
        else:
87
92
            return self.make_entry(file_id, self.paths[file_id])
88
93
 
 
94
    def get_entry_by_path(self, path):
 
95
        return self[self.path2id(path)]
 
96
 
89
97
    def parent_id(self, file_id):
90
98
        parent_dir = os.path.dirname(self.paths[file_id])
91
99
        if parent_dir == "":
93
101
        return self.ids[parent_dir]
94
102
 
95
103
    def iter_entries(self):
96
 
        for path, file_id in self.ids.iteritems():
 
104
        for path, file_id in self.ids.items():
97
105
            yield path, self[file_id]
98
106
 
99
 
    def get_file_kind(self, file_id):
100
 
        if file_id in self.contents:
 
107
    def kind(self, path):
 
108
        if path in self.contents:
101
109
            kind = 'file'
102
110
        else:
103
111
            kind = 'directory'
104
112
        return kind
105
113
 
106
114
    def make_entry(self, file_id, path):
107
 
        from bzrlib.inventory import (InventoryEntry, InventoryFile
108
 
                                    , InventoryDirectory, InventoryLink)
 
115
        from ..bzr.inventory import (InventoryFile, InventoryDirectory,
 
116
                                     InventoryLink)
 
117
        if not isinstance(file_id, bytes):
 
118
            raise TypeError(file_id)
109
119
        name = os.path.basename(path)
110
 
        kind = self.get_file_kind(file_id)
 
120
        kind = self.kind(path)
111
121
        parent_id = self.parent_id(file_id)
112
 
        text_sha_1, text_size = self.contents_stats(file_id)
 
122
        text_sha_1, text_size = self.contents_stats(path)
113
123
        if kind == 'directory':
114
124
            ie = InventoryDirectory(file_id, name, parent_id)
115
125
        elif kind == 'file':
116
126
            ie = InventoryFile(file_id, name, parent_id)
 
127
            ie.text_sha1 = text_sha_1
 
128
            ie.text_size = text_size
117
129
        elif kind == 'symlink':
118
130
            ie = InventoryLink(file_id, name, parent_id)
119
131
        else:
120
132
            raise errors.BzrError('unknown kind %r' % kind)
121
 
        ie.text_sha1 = text_sha_1
122
 
        ie.text_size = text_size
123
133
        return ie
124
134
 
125
135
    def add_dir(self, file_id, path):
 
136
        if not isinstance(file_id, bytes):
 
137
            raise TypeError(file_id)
126
138
        self.paths[file_id] = path
127
139
        self.ids[path] = file_id
128
140
 
129
141
    def add_file(self, file_id, path, contents):
 
142
        if not isinstance(file_id, bytes):
 
143
            raise TypeError(file_id)
130
144
        self.add_dir(file_id, path)
131
 
        self.contents[file_id] = contents
 
145
        self.contents[path] = contents
132
146
 
133
147
    def path2id(self, path):
134
148
        return self.ids.get(path)
139
153
    def has_id(self, file_id):
140
154
        return self.id2path(file_id) is not None
141
155
 
142
 
    def get_file(self, file_id):
143
 
        result = StringIO()
144
 
        result.write(self.contents[file_id])
145
 
        result.seek(0,0)
 
156
    def get_file(self, path):
 
157
        result = BytesIO()
 
158
        try:
 
159
            result.write(self.contents[path])
 
160
        except KeyError:
 
161
            raise errors.NoSuchFile(path)
 
162
        result.seek(0, 0)
146
163
        return result
147
164
 
148
 
    def contents_stats(self, file_id):
149
 
        if file_id not in self.contents:
 
165
    def get_file_revision(self, path):
 
166
        return self.inventory.get_entry_by_path(path).revision
 
167
 
 
168
    def get_file_size(self, path):
 
169
        return self.inventory.get_entry_by_path(path).text_size
 
170
 
 
171
    def get_file_sha1(self, path, file_id=None):
 
172
        return self.inventory.get_entry_by_path(path).text_sha1
 
173
 
 
174
    def contents_stats(self, path):
 
175
        if path not in self.contents:
150
176
            return None, None
151
 
        text_sha1 = osutils.sha_file(self.get_file(file_id))
152
 
        return text_sha1, len(self.contents[file_id])
 
177
        text_sha1 = osutils.sha_file(self.get_file(path))
 
178
        return text_sha1, len(self.contents[path])
153
179
 
154
180
 
155
181
class BTreeTester(tests.TestCase):
157
183
 
158
184
    def make_tree_1(self):
159
185
        mtree = MockTree()
160
 
        mtree.add_dir("a", "grandparent")
161
 
        mtree.add_dir("b", "grandparent/parent")
162
 
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
163
 
        mtree.add_dir("d", "grandparent/alt_parent")
164
 
        return BundleTree(mtree, ''), mtree
 
186
        mtree.add_dir(b"a", "grandparent")
 
187
        mtree.add_dir(b"b", "grandparent/parent")
 
188
        mtree.add_file(b"c", "grandparent/parent/file", b"Hello\n")
 
189
        mtree.add_dir(b"d", "grandparent/alt_parent")
 
190
        return BundleTree(mtree, b''), mtree
165
191
 
166
192
    def test_renames(self):
167
193
        """Ensure that file renames have the proper effect on children"""
172
198
        self.assertEqual(btree.old_path("grandparent/parent/file"),
173
199
                         "grandparent/parent/file")
174
200
 
175
 
        self.assertEqual(btree.id2path("a"), "grandparent")
176
 
        self.assertEqual(btree.id2path("b"), "grandparent/parent")
177
 
        self.assertEqual(btree.id2path("c"), "grandparent/parent/file")
178
 
 
179
 
        self.assertEqual(btree.path2id("grandparent"), "a")
180
 
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
181
 
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
182
 
 
183
 
        self.assertTrue(btree.path2id("grandparent2") is None)
184
 
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
185
 
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
 
201
        self.assertEqual(btree.id2path(b"a"), "grandparent")
 
202
        self.assertEqual(btree.id2path(b"b"), "grandparent/parent")
 
203
        self.assertEqual(btree.id2path(b"c"), "grandparent/parent/file")
 
204
 
 
205
        self.assertEqual(btree.path2id("grandparent"), b"a")
 
206
        self.assertEqual(btree.path2id("grandparent/parent"), b"b")
 
207
        self.assertEqual(btree.path2id("grandparent/parent/file"), b"c")
 
208
 
 
209
        self.assertIs(btree.path2id("grandparent2"), None)
 
210
        self.assertIs(btree.path2id("grandparent2/parent"), None)
 
211
        self.assertIs(btree.path2id("grandparent2/parent/file"), None)
186
212
 
187
213
        btree.note_rename("grandparent", "grandparent2")
188
 
        self.assertTrue(btree.old_path("grandparent") is None)
189
 
        self.assertTrue(btree.old_path("grandparent/parent") is None)
190
 
        self.assertTrue(btree.old_path("grandparent/parent/file") is None)
191
 
 
192
 
        self.assertEqual(btree.id2path("a"), "grandparent2")
193
 
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
194
 
        self.assertEqual(btree.id2path("c"), "grandparent2/parent/file")
195
 
 
196
 
        self.assertEqual(btree.path2id("grandparent2"), "a")
197
 
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
198
 
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
 
214
        self.assertIs(btree.old_path("grandparent"), None)
 
215
        self.assertIs(btree.old_path("grandparent/parent"), None)
 
216
        self.assertIs(btree.old_path("grandparent/parent/file"), None)
 
217
 
 
218
        self.assertEqual(btree.id2path(b"a"), "grandparent2")
 
219
        self.assertEqual(btree.id2path(b"b"), "grandparent2/parent")
 
220
        self.assertEqual(btree.id2path(b"c"), "grandparent2/parent/file")
 
221
 
 
222
        self.assertEqual(btree.path2id("grandparent2"), b"a")
 
223
        self.assertEqual(btree.path2id("grandparent2/parent"), b"b")
 
224
        self.assertEqual(btree.path2id("grandparent2/parent/file"), b"c")
199
225
 
200
226
        self.assertTrue(btree.path2id("grandparent") is None)
201
227
        self.assertTrue(btree.path2id("grandparent/parent") is None)
202
228
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
203
229
 
204
230
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
205
 
        self.assertEqual(btree.id2path("a"), "grandparent2")
206
 
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
207
 
        self.assertEqual(btree.id2path("c"), "grandparent2/parent2/file")
 
231
        self.assertEqual(btree.id2path(b"a"), "grandparent2")
 
232
        self.assertEqual(btree.id2path(b"b"), "grandparent2/parent2")
 
233
        self.assertEqual(btree.id2path(b"c"), "grandparent2/parent2/file")
208
234
 
209
 
        self.assertEqual(btree.path2id("grandparent2"), "a")
210
 
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
211
 
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
 
235
        self.assertEqual(btree.path2id("grandparent2"), b"a")
 
236
        self.assertEqual(btree.path2id("grandparent2/parent2"), b"b")
 
237
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), b"c")
212
238
 
213
239
        self.assertTrue(btree.path2id("grandparent2/parent") is None)
214
240
        self.assertTrue(btree.path2id("grandparent2/parent/file") is None)
215
241
 
216
242
        btree.note_rename("grandparent/parent/file",
217
243
                          "grandparent2/parent2/file2")
218
 
        self.assertEqual(btree.id2path("a"), "grandparent2")
219
 
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
220
 
        self.assertEqual(btree.id2path("c"), "grandparent2/parent2/file2")
 
244
        self.assertEqual(btree.id2path(b"a"), "grandparent2")
 
245
        self.assertEqual(btree.id2path(b"b"), "grandparent2/parent2")
 
246
        self.assertEqual(btree.id2path(b"c"), "grandparent2/parent2/file2")
221
247
 
222
 
        self.assertEqual(btree.path2id("grandparent2"), "a")
223
 
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
224
 
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
 
248
        self.assertEqual(btree.path2id("grandparent2"), b"a")
 
249
        self.assertEqual(btree.path2id("grandparent2/parent2"), b"b")
 
250
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), b"c")
225
251
 
226
252
        self.assertTrue(btree.path2id("grandparent2/parent2/file") is None)
227
253
 
230
256
        btree = self.make_tree_1()[0]
231
257
        btree.note_rename("grandparent/parent/file",
232
258
                          "grandparent/alt_parent/file")
233
 
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
234
 
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
 
259
        self.assertEqual(btree.id2path(b"c"), "grandparent/alt_parent/file")
 
260
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), b"c")
235
261
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
236
262
 
237
263
    def unified_diff(self, old, new):
238
 
        out = StringIO()
 
264
        out = BytesIO()
239
265
        diff.internal_diff("old", old, "new", new, out)
240
 
        out.seek(0,0)
 
266
        out.seek(0, 0)
241
267
        return out.read()
242
268
 
243
269
    def make_tree_2(self):
244
270
        btree = self.make_tree_1()[0]
245
271
        btree.note_rename("grandparent/parent/file",
246
272
                          "grandparent/alt_parent/file")
247
 
        self.assertTrue(btree.id2path("e") is None)
248
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
249
 
        btree.note_id("e", "grandparent/parent/file")
 
273
        self.assertTrue(btree.id2path(b"e") is None)
 
274
        self.assertFalse(btree.is_versioned("grandparent/parent/file"))
 
275
        btree.note_id(b"e", "grandparent/parent/file")
250
276
        return btree
251
277
 
252
278
    def test_adds(self):
253
279
        """File/inventory adds"""
254
280
        btree = self.make_tree_2()
255
 
        add_patch = self.unified_diff([], ["Extra cheese\n"])
 
281
        add_patch = self.unified_diff([], [b"Extra cheese\n"])
256
282
        btree.note_patch("grandparent/parent/file", add_patch)
257
 
        btree.note_id('f', 'grandparent/parent/symlink', kind='symlink')
 
283
        btree.note_id(b'f', 'grandparent/parent/symlink', kind='symlink')
258
284
        btree.note_target('grandparent/parent/symlink', 'venus')
259
285
        self.adds_test(btree)
260
286
 
261
287
    def adds_test(self, btree):
262
 
        self.assertEqual(btree.id2path("e"), "grandparent/parent/file")
263
 
        self.assertEqual(btree.path2id("grandparent/parent/file"), "e")
264
 
        self.assertEqual(btree.get_file("e").read(), "Extra cheese\n")
265
 
        self.assertEqual(btree.get_symlink_target('f'), 'venus')
 
288
        self.assertEqual(btree.id2path(b"e"), "grandparent/parent/file")
 
289
        self.assertEqual(btree.path2id("grandparent/parent/file"), b"e")
 
290
        with btree.get_file("grandparent/parent/file") as f:
 
291
            self.assertEqual(f.read(), b"Extra cheese\n")
 
292
        self.assertEqual(
 
293
            btree.get_symlink_target('grandparent/parent/symlink'), 'venus')
266
294
 
267
295
    def test_adds2(self):
268
296
        """File/inventory adds, with patch-compatibile renames"""
269
297
        btree = self.make_tree_2()
270
298
        btree.contents_by_id = False
271
 
        add_patch = self.unified_diff(["Hello\n"], ["Extra cheese\n"])
 
299
        add_patch = self.unified_diff([b"Hello\n"], [b"Extra cheese\n"])
272
300
        btree.note_patch("grandparent/parent/file", add_patch)
273
 
        btree.note_id('f', 'grandparent/parent/symlink', kind='symlink')
 
301
        btree.note_id(b'f', 'grandparent/parent/symlink', kind='symlink')
274
302
        btree.note_target('grandparent/parent/symlink', 'venus')
275
303
        self.adds_test(btree)
276
304
 
277
305
    def make_tree_3(self):
278
306
        btree, mtree = self.make_tree_1()
279
 
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
 
307
        mtree.add_file(b"e", "grandparent/parent/topping", b"Anchovies\n")
280
308
        btree.note_rename("grandparent/parent/file",
281
309
                          "grandparent/alt_parent/file")
282
310
        btree.note_rename("grandparent/parent/topping",
284
312
        return btree
285
313
 
286
314
    def get_file_test(self, btree):
287
 
        self.assertEqual(btree.get_file("e").read(), "Lemon\n")
288
 
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
 
315
        with btree.get_file(btree.id2path(b"e")) as f:
 
316
            self.assertEqual(f.read(), b"Lemon\n")
 
317
        with btree.get_file(btree.id2path(b"c")) as f:
 
318
            self.assertEqual(f.read(), b"Hello\n")
289
319
 
290
320
    def test_get_file(self):
291
321
        """Get file contents"""
292
322
        btree = self.make_tree_3()
293
 
        mod_patch = self.unified_diff(["Anchovies\n"], ["Lemon\n"])
 
323
        mod_patch = self.unified_diff([b"Anchovies\n"], [b"Lemon\n"])
294
324
        btree.note_patch("grandparent/alt_parent/stopping", mod_patch)
295
325
        self.get_file_test(btree)
296
326
 
297
327
    def test_get_file2(self):
298
 
        """Get file contents, with patch-compatibile renames"""
 
328
        """Get file contents, with patch-compatible renames"""
299
329
        btree = self.make_tree_3()
300
330
        btree.contents_by_id = False
301
 
        mod_patch = self.unified_diff([], ["Lemon\n"])
 
331
        mod_patch = self.unified_diff([], [b"Lemon\n"])
302
332
        btree.note_patch("grandparent/alt_parent/stopping", mod_patch)
303
 
        mod_patch = self.unified_diff([], ["Hello\n"])
 
333
        mod_patch = self.unified_diff([], [b"Hello\n"])
304
334
        btree.note_patch("grandparent/alt_parent/file", mod_patch)
305
335
        self.get_file_test(btree)
306
336
 
307
337
    def test_delete(self):
308
338
        "Deletion by bundle"
309
339
        btree = self.make_tree_1()[0]
310
 
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
 
340
        with btree.get_file(btree.id2path(b"c")) as f:
 
341
            self.assertEqual(f.read(), b"Hello\n")
311
342
        btree.note_deletion("grandparent/parent/file")
312
 
        self.assertTrue(btree.id2path("c") is None)
313
 
        self.assertTrue(btree.path2id("grandparent/parent/file") is None)
 
343
        self.assertTrue(btree.id2path(b"c") is None)
 
344
        self.assertFalse(btree.is_versioned("grandparent/parent/file"))
314
345
 
315
346
    def sorted_ids(self, tree):
316
 
        ids = list(tree)
317
 
        ids.sort()
 
347
        ids = sorted(tree.all_file_ids())
318
348
        return ids
319
349
 
320
350
    def test_iteration(self):
321
351
        """Ensure that iteration through ids works properly"""
322
352
        btree = self.make_tree_1()[0]
323
353
        self.assertEqual(self.sorted_ids(btree),
324
 
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
 
354
                         [inventory.ROOT_ID, b'a', b'b', b'c', b'd'])
325
355
        btree.note_deletion("grandparent/parent/file")
326
 
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
 
356
        btree.note_id(b"e", "grandparent/alt_parent/fool", kind="directory")
327
357
        btree.note_last_changed("grandparent/alt_parent/fool",
328
358
                                "revisionidiguess")
329
359
        self.assertEqual(self.sorted_ids(btree),
330
 
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
 
360
                         [inventory.ROOT_ID, b'a', b'b', b'd', b'e'])
331
361
 
332
362
 
333
363
class BundleTester1(tests.TestCaseWithTransport):
338
368
        serializer = BundleSerializerV08('0.8')
339
369
        b = self.make_branch('.', format=format)
340
370
        self.assertRaises(errors.IncompatibleBundleFormat, serializer.write,
341
 
                          b.repository, [], {}, StringIO())
 
371
                          b.repository, [], {}, BytesIO())
342
372
 
343
373
    def test_matched_bundle(self):
344
374
        """Don't raise IncompatibleBundleFormat for knit2 and bundle0.9"""
346
376
        format.repository_format = knitrepo.RepositoryFormatKnit3()
347
377
        serializer = BundleSerializerV09('0.9')
348
378
        b = self.make_branch('.', format=format)
349
 
        serializer.write(b.repository, [], {}, StringIO())
 
379
        serializer.write(b.repository, [], {}, BytesIO())
350
380
 
351
381
    def test_mismatched_model(self):
352
382
        """Try copying a bundle from knit2 to knit1"""
353
383
        format = bzrdir.BzrDirMetaFormat1()
354
384
        format.repository_format = knitrepo.RepositoryFormatKnit3()
355
385
        source = self.make_branch_and_tree('source', format=format)
356
 
        source.commit('one', rev_id='one-id')
357
 
        source.commit('two', rev_id='two-id')
358
 
        text = StringIO()
359
 
        write_bundle(source.branch.repository, 'two-id', 'null:', text,
 
386
        source.commit('one', rev_id=b'one-id')
 
387
        source.commit('two', rev_id=b'two-id')
 
388
        text = BytesIO()
 
389
        write_bundle(source.branch.repository, b'two-id', b'null:', text,
360
390
                     format='0.9')
361
391
        text.seek(0)
362
392
 
386
416
        return tests.TestCaseWithTransport.make_branch(self, path, format)
387
417
 
388
418
    def create_bundle_text(self, base_rev_id, rev_id):
389
 
        bundle_txt = StringIO()
 
419
        bundle_txt = BytesIO()
390
420
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
391
421
                               bundle_txt, format=self.format)
392
422
        bundle_txt.seek(0)
393
423
        self.assertEqual(bundle_txt.readline(),
394
 
                         '# Bazaar revision bundle v%s\n' % self.format)
395
 
        self.assertEqual(bundle_txt.readline(), '#\n')
 
424
                         b'# Bazaar revision bundle v%s\n' % self.format.encode('ascii'))
 
425
        self.assertEqual(bundle_txt.readline(), b'#\n')
396
426
 
397
427
        rev = self.b1.repository.get_revision(rev_id)
398
428
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
427
457
                             len(bundle_rev.parent_ids))
428
458
        self.assertEqual(rev_ids,
429
459
                         [r.revision_id for r in bundle.real_revisions])
430
 
        self.valid_apply_bundle(base_rev_id, bundle,
431
 
                                   checkout_dir=checkout_dir)
 
460
        self.valid_apply_bundle(base_rev_id, bundle, checkout_dir=checkout_dir)
432
461
 
433
462
        return bundle
434
463
 
439
468
        :return: The in-memory bundle
440
469
        """
441
470
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
442
 
        new_text = bundle_txt.getvalue().replace('executable:no',
443
 
                                               'executable:yes')
444
 
        bundle_txt = StringIO(new_text)
 
471
        new_text = bundle_txt.getvalue().replace(b'executable:no',
 
472
                                                 b'executable:yes')
 
473
        bundle_txt = BytesIO(new_text)
445
474
        bundle = read_bundle(bundle_txt)
446
475
        self.valid_apply_bundle(base_rev_id, bundle)
447
476
        return bundle
448
477
 
449
478
    def test_non_bundle(self):
450
479
        self.assertRaises(errors.NotABundle,
451
 
                          read_bundle, StringIO('#!/bin/sh\n'))
 
480
                          read_bundle, BytesIO(b'#!/bin/sh\n'))
452
481
 
453
482
    def test_malformed(self):
454
483
        self.assertRaises(errors.BadBundle, read_bundle,
455
 
                          StringIO('# Bazaar revision bundle v'))
 
484
                          BytesIO(b'# Bazaar revision bundle v'))
456
485
 
457
486
    def test_crlf_bundle(self):
458
487
        try:
459
 
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
 
488
            read_bundle(BytesIO(b'# Bazaar revision bundle v0.8\r\n'))
460
489
        except errors.BadBundle:
461
490
            # It is currently permitted for bundles with crlf line endings to
462
491
            # make read_bundle raise a BadBundle, but this should be fixed.
473
502
            if not os.path.exists(checkout_dir):
474
503
                os.mkdir(checkout_dir)
475
504
        tree = self.make_branch_and_tree(checkout_dir)
476
 
        s = StringIO()
477
 
        ancestors = write_bundle(self.b1.repository, rev_id, 'null:', s,
 
505
        s = BytesIO()
 
506
        ancestors = write_bundle(self.b1.repository, rev_id, b'null:', s,
478
507
                                 format=self.format)
479
508
        s.seek(0)
480
 
        self.assertIsInstance(s.getvalue(), str)
 
509
        self.assertIsInstance(s.getvalue(), bytes)
481
510
        install_bundle(tree.branch.repository, read_bundle(s))
482
511
        for ancestor in ancestors:
483
512
            old = self.b1.repository.revision_tree(ancestor)
492
521
                                 % (ancestor,))
493
522
 
494
523
                # Now check that the file contents are all correct
495
 
                for inventory_id in old:
 
524
                for path in old.all_versioned_paths():
496
525
                    try:
497
 
                        old_file = old.get_file(inventory_id)
 
526
                        old_file = old.get_file(path)
498
527
                    except errors.NoSuchFile:
499
528
                        continue
500
 
                    if old_file is None:
501
 
                        continue
502
 
                    self.assertEqual(old_file.read(),
503
 
                                     new.get_file(inventory_id).read())
 
529
                    self.assertEqual(
 
530
                        old_file.read(), new.get_file(path).read())
504
531
            finally:
505
532
                new.unlock()
506
533
                old.unlock()
507
534
        if not _mod_revision.is_null(rev_id):
508
 
            rh = self.b1.revision_history()
509
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
 
535
            tree.branch.generate_revision_history(rev_id)
510
536
            tree.update()
511
537
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
512
538
            self.assertFalse(delta.has_changed(),
530
556
        original_parents = to_tree.get_parent_ids()
531
557
        self.assertIs(repository.has_revision(base_rev_id), True)
532
558
        for rev in info.real_revisions:
533
 
            self.assert_(not repository.has_revision(rev.revision_id),
534
 
                'Revision {%s} present before applying bundle'
535
 
                % rev.revision_id)
 
559
            self.assertTrue(not repository.has_revision(rev.revision_id),
 
560
                            'Revision {%s} present before applying bundle'
 
561
                            % rev.revision_id)
536
562
        merge_bundle(info, to_tree, True, merge.Merge3Merger, False, False)
537
563
 
538
564
        for rev in info.real_revisions:
539
 
            self.assert_(repository.has_revision(rev.revision_id),
540
 
                'Missing revision {%s} after applying bundle'
541
 
                % rev.revision_id)
 
565
            self.assertTrue(repository.has_revision(rev.revision_id),
 
566
                            'Missing revision {%s} after applying bundle'
 
567
                            % rev.revision_id)
542
568
 
543
 
        self.assert_(to_tree.branch.repository.has_revision(info.target))
 
569
        self.assertTrue(to_tree.branch.repository.has_revision(info.target))
544
570
        # Do we also want to verify that all the texts have been added?
545
571
 
546
572
        self.assertEqual(original_parents + [info.target],
547
 
            to_tree.get_parent_ids())
 
573
                         to_tree.get_parent_ids())
548
574
 
549
575
        rev = info.real_revisions[-1]
550
576
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
559
585
        for base_file, to_file in zip(base_files, to_files):
560
586
            self.assertEqual(base_file, to_file)
561
587
 
562
 
        for path, status, kind, fileid, entry in base_files:
 
588
        for path, status, kind, entry in base_files:
563
589
            # Check that the meta information is the same
564
 
            self.assertEqual(base_tree.get_file_size(fileid),
565
 
                    to_tree.get_file_size(fileid))
566
 
            self.assertEqual(base_tree.get_file_sha1(fileid),
567
 
                    to_tree.get_file_sha1(fileid))
 
590
            to_path = find_previous_path(base_tree, to_tree, path)
 
591
            self.assertEqual(
 
592
                base_tree.get_file_size(path),
 
593
                to_tree.get_file_size(to_path))
 
594
            self.assertEqual(
 
595
                base_tree.get_file_sha1(path),
 
596
                to_tree.get_file_sha1(to_path))
568
597
            # Check that the contents are the same
569
598
            # This is pretty expensive
570
599
            # self.assertEqual(base_tree.get_file(fileid).read(),
574
603
        self.tree1 = self.make_branch_and_tree('b1')
575
604
        self.b1 = self.tree1.branch
576
605
 
577
 
        self.build_tree_contents([('b1/one', 'one\n')])
578
 
        self.tree1.add('one', 'one-id')
579
 
        self.tree1.set_root_id('root-id')
580
 
        self.tree1.commit('add one', rev_id='a@cset-0-1')
 
606
        self.build_tree_contents([('b1/one', b'one\n')])
 
607
        self.tree1.add('one', b'one-id')
 
608
        self.tree1.set_root_id(b'root-id')
 
609
        self.tree1.commit('add one', rev_id=b'a@cset-0-1')
581
610
 
582
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
 
611
        bundle = self.get_valid_bundle(b'null:', b'a@cset-0-1')
583
612
 
584
613
        # Make sure we can handle files with spaces, tabs, other
585
614
        # bogus characters
586
615
        self.build_tree([
587
 
                'b1/with space.txt'
588
 
                , 'b1/dir/'
589
 
                , 'b1/dir/filein subdir.c'
590
 
                , 'b1/dir/WithCaps.txt'
591
 
                , 'b1/dir/ pre space'
592
 
                , 'b1/sub/'
593
 
                , 'b1/sub/sub/'
594
 
                , 'b1/sub/sub/nonempty.txt'
595
 
                ])
596
 
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', ''),
597
 
                                  ('b1/dir/nolastnewline.txt', 'bloop')])
598
 
        tt = TreeTransform(self.tree1)
599
 
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
 
616
            'b1/with space.txt', 'b1/dir/', 'b1/dir/filein subdir.c', 'b1/dir/WithCaps.txt', 'b1/dir/ pre space', 'b1/sub/', 'b1/sub/sub/', 'b1/sub/sub/nonempty.txt'
 
617
            ])
 
618
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', b''),
 
619
                                  ('b1/dir/nolastnewline.txt', b'bloop')])
 
620
        tt = self.tree1.get_transform()
 
621
        tt.new_file('executable', tt.root, [b'#!/bin/sh\n'], b'exe-1', True)
600
622
        tt.apply()
601
623
        # have to fix length of file-id so that we can predictably rewrite
602
624
        # a (length-prefixed) record containing it later.
603
 
        self.tree1.add('with space.txt', 'withspace-id')
 
625
        self.tree1.add('with space.txt', b'withspace-id')
604
626
        self.tree1.add([
605
 
                  'dir'
606
 
                , 'dir/filein subdir.c'
607
 
                , 'dir/WithCaps.txt'
608
 
                , 'dir/ pre space'
609
 
                , 'dir/nolastnewline.txt'
610
 
                , 'sub'
611
 
                , 'sub/sub'
612
 
                , 'sub/sub/nonempty.txt'
613
 
                , 'sub/sub/emptyfile.txt'
614
 
                ])
615
 
        self.tree1.commit('add whitespace', rev_id='a@cset-0-2')
 
627
            'dir', 'dir/filein subdir.c', 'dir/WithCaps.txt', 'dir/ pre space', 'dir/nolastnewline.txt', 'sub', 'sub/sub', 'sub/sub/nonempty.txt', 'sub/sub/emptyfile.txt'
 
628
            ])
 
629
        self.tree1.commit('add whitespace', rev_id=b'a@cset-0-2')
616
630
 
617
 
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
 
631
        bundle = self.get_valid_bundle(b'a@cset-0-1', b'a@cset-0-2')
618
632
 
619
633
        # Check a rollup bundle
620
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
 
634
        bundle = self.get_valid_bundle(b'null:', b'a@cset-0-2')
621
635
 
622
636
        # Now delete entries
623
637
        self.tree1.remove(
624
 
                ['sub/sub/nonempty.txt'
625
 
                , 'sub/sub/emptyfile.txt'
626
 
                , 'sub/sub'
627
 
                ])
628
 
        tt = TreeTransform(self.tree1)
629
 
        trans_id = tt.trans_id_tree_file_id('exe-1')
 
638
            ['sub/sub/nonempty.txt', 'sub/sub/emptyfile.txt', 'sub/sub'
 
639
             ])
 
640
        tt = self.tree1.get_transform()
 
641
        trans_id = tt.trans_id_tree_path('executable')
630
642
        tt.set_executability(False, trans_id)
631
643
        tt.apply()
632
 
        self.tree1.commit('removed', rev_id='a@cset-0-3')
 
644
        self.tree1.commit('removed', rev_id=b'a@cset-0-3')
633
645
 
634
 
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
 
646
        bundle = self.get_valid_bundle(b'a@cset-0-2', b'a@cset-0-3')
635
647
        self.assertRaises((errors.TestamentMismatch,
636
 
            errors.VersionedFileInvalidChecksum,
637
 
            errors.BadBundle), self.get_invalid_bundle,
638
 
            'a@cset-0-2', 'a@cset-0-3')
 
648
                           errors.VersionedFileInvalidChecksum,
 
649
                           errors.BadBundle), self.get_invalid_bundle,
 
650
                          b'a@cset-0-2', b'a@cset-0-3')
639
651
        # Check a rollup bundle
640
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
 
652
        bundle = self.get_valid_bundle(b'null:', b'a@cset-0-3')
641
653
 
642
654
        # Now move the directory
643
655
        self.tree1.rename_one('dir', 'sub/dir')
644
 
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
 
656
        self.tree1.commit('rename dir', rev_id=b'a@cset-0-4')
645
657
 
646
 
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
 
658
        bundle = self.get_valid_bundle(b'a@cset-0-3', b'a@cset-0-4')
647
659
        # Check a rollup bundle
648
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
 
660
        bundle = self.get_valid_bundle(b'null:', b'a@cset-0-4')
649
661
 
650
662
        # Modified files
651
 
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
652
 
        open('b1/sub/dir/ pre space', 'ab').write(
653
 
             '\r\nAdding some\r\nDOS format lines\r\n')
654
 
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
 
663
        with open('b1/sub/dir/WithCaps.txt', 'ab') as f:
 
664
            f.write(b'\nAdding some text\n')
 
665
        with open('b1/sub/dir/ pre space', 'ab') as f:
 
666
            f.write(
 
667
                b'\r\nAdding some\r\nDOS format lines\r\n')
 
668
        with open('b1/sub/dir/nolastnewline.txt', 'ab') as f:
 
669
            f.write(b'\n')
655
670
        self.tree1.rename_one('sub/dir/ pre space',
656
671
                              'sub/ start space')
657
 
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
658
 
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
 
672
        self.tree1.commit('Modified files', rev_id=b'a@cset-0-5')
 
673
        bundle = self.get_valid_bundle(b'a@cset-0-4', b'a@cset-0-5')
659
674
 
660
675
        self.tree1.rename_one('sub/dir/WithCaps.txt', 'temp')
661
676
        self.tree1.rename_one('with space.txt', 'WithCaps.txt')
662
677
        self.tree1.rename_one('temp', 'with space.txt')
663
 
        self.tree1.commit(u'swap filenames', rev_id='a@cset-0-6',
 
678
        self.tree1.commit(u'swap filenames', rev_id=b'a@cset-0-6',
664
679
                          verbose=False)
665
 
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
666
 
        other = self.get_checkout('a@cset-0-5')
 
680
        bundle = self.get_valid_bundle(b'a@cset-0-5', b'a@cset-0-6')
 
681
        other = self.get_checkout(b'a@cset-0-5')
667
682
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
668
 
                                       'a@cset-0-5')
 
683
                                       b'a@cset-0-5')
669
684
        tree2_inv = get_inventory_text(other.branch.repository,
670
 
                                       'a@cset-0-5')
 
685
                                       b'a@cset-0-5')
671
686
        self.assertEqualDiff(tree1_inv, tree2_inv)
672
687
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
673
 
        other.commit('rename file', rev_id='a@cset-0-6b')
 
688
        other.commit('rename file', rev_id=b'a@cset-0-6b')
674
689
        self.tree1.merge_from_branch(other.branch)
675
 
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
 
690
        self.tree1.commit(u'Merge', rev_id=b'a@cset-0-7',
676
691
                          verbose=False)
677
 
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
 
692
        bundle = self.get_valid_bundle(b'a@cset-0-6', b'a@cset-0-7')
678
693
 
679
694
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
680
 
        link_id = 'link-1'
 
695
        link_id = b'link-1'
681
696
 
682
 
        self.requireFeature(tests.SymlinkFeature)
 
697
        self.requireFeature(features.SymlinkFeature)
683
698
        self.tree1 = self.make_branch_and_tree('b1')
684
699
        self.b1 = self.tree1.branch
685
700
 
686
 
        tt = TreeTransform(self.tree1)
 
701
        tt = self.tree1.get_transform()
687
702
        tt.new_symlink(link_name, tt.root, link_target, link_id)
688
703
        tt.apply()
689
 
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
690
 
        bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
691
 
        if getattr(bundle ,'revision_tree', None) is not None:
 
704
        self.tree1.commit('add symlink', rev_id=b'l@cset-0-1')
 
705
        bundle = self.get_valid_bundle(b'null:', b'l@cset-0-1')
 
706
        if getattr(bundle, 'revision_tree', None) is not None:
692
707
            # Not all bundle formats supports revision_tree
693
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
694
 
            self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
 
708
            bund_tree = bundle.revision_tree(self.b1.repository, b'l@cset-0-1')
 
709
            self.assertEqual(
 
710
                link_target, bund_tree.get_symlink_target(link_name))
695
711
 
696
 
        tt = TreeTransform(self.tree1)
697
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
712
        tt = self.tree1.get_transform()
 
713
        trans_id = tt.trans_id_tree_path(link_name)
698
714
        tt.adjust_path('link2', tt.root, trans_id)
699
715
        tt.delete_contents(trans_id)
700
716
        tt.create_symlink(new_link_target, trans_id)
701
717
        tt.apply()
702
 
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
703
 
        bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
704
 
        if getattr(bundle ,'revision_tree', None) is not None:
 
718
        self.tree1.commit('rename and change symlink', rev_id=b'l@cset-0-2')
 
719
        bundle = self.get_valid_bundle(b'l@cset-0-1', b'l@cset-0-2')
 
720
        if getattr(bundle, 'revision_tree', None) is not None:
705
721
            # Not all bundle formats supports revision_tree
706
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
 
722
            bund_tree = bundle.revision_tree(self.b1.repository, b'l@cset-0-2')
707
723
            self.assertEqual(new_link_target,
708
 
                             bund_tree.get_symlink_target(link_id))
 
724
                             bund_tree.get_symlink_target('link2'))
709
725
 
710
 
        tt = TreeTransform(self.tree1)
711
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
726
        tt = self.tree1.get_transform()
 
727
        trans_id = tt.trans_id_tree_path('link2')
712
728
        tt.delete_contents(trans_id)
713
729
        tt.create_symlink('jupiter', trans_id)
714
730
        tt.apply()
715
 
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
716
 
        bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
 
731
        self.tree1.commit('just change symlink target', rev_id=b'l@cset-0-3')
 
732
        bundle = self.get_valid_bundle(b'l@cset-0-2', b'l@cset-0-3')
717
733
 
718
 
        tt = TreeTransform(self.tree1)
719
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
734
        tt = self.tree1.get_transform()
 
735
        trans_id = tt.trans_id_tree_path('link2')
720
736
        tt.delete_contents(trans_id)
721
737
        tt.apply()
722
 
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
723
 
        bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
 
738
        self.tree1.commit('Delete symlink', rev_id=b'l@cset-0-4')
 
739
        bundle = self.get_valid_bundle(b'l@cset-0-3', b'l@cset-0-4')
724
740
 
725
741
    def test_symlink_bundle(self):
726
742
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
727
743
 
728
744
    def test_unicode_symlink_bundle(self):
729
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
745
        self.requireFeature(features.UnicodeFilenameFeature)
730
746
        self._test_symlink_bundle(u'\N{Euro Sign}link',
731
747
                                  u'bar/\N{Euro Sign}foo',
732
748
                                  u'mars\N{Euro Sign}')
734
750
    def test_binary_bundle(self):
735
751
        self.tree1 = self.make_branch_and_tree('b1')
736
752
        self.b1 = self.tree1.branch
737
 
        tt = TreeTransform(self.tree1)
 
753
        tt = self.tree1.get_transform()
738
754
 
739
755
        # Add
740
 
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
741
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
742
 
            'binary-2')
 
756
        tt.new_file('file', tt.root, [
 
757
                    b'\x00\n\x00\r\x01\n\x02\r\xff'], b'binary-1')
 
758
        tt.new_file('file2', tt.root, [b'\x01\n\x02\r\x03\n\x04\r\xff'],
 
759
                    b'binary-2')
743
760
        tt.apply()
744
 
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
745
 
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
761
        self.tree1.commit('add binary', rev_id=b'b@cset-0-1')
 
762
        self.get_valid_bundle(b'null:', b'b@cset-0-1')
746
763
 
747
764
        # Delete
748
 
        tt = TreeTransform(self.tree1)
749
 
        trans_id = tt.trans_id_tree_file_id('binary-1')
 
765
        tt = self.tree1.get_transform()
 
766
        trans_id = tt.trans_id_tree_path('file')
750
767
        tt.delete_contents(trans_id)
751
768
        tt.apply()
752
 
        self.tree1.commit('delete binary', rev_id='b@cset-0-2')
753
 
        self.get_valid_bundle('b@cset-0-1', 'b@cset-0-2')
 
769
        self.tree1.commit('delete binary', rev_id=b'b@cset-0-2')
 
770
        self.get_valid_bundle(b'b@cset-0-1', b'b@cset-0-2')
754
771
 
755
772
        # Rename & modify
756
 
        tt = TreeTransform(self.tree1)
757
 
        trans_id = tt.trans_id_tree_file_id('binary-2')
 
773
        tt = self.tree1.get_transform()
 
774
        trans_id = tt.trans_id_tree_path('file2')
758
775
        tt.adjust_path('file3', tt.root, trans_id)
759
776
        tt.delete_contents(trans_id)
760
 
        tt.create_file('file\rcontents\x00\n\x00', trans_id)
 
777
        tt.create_file([b'file\rcontents\x00\n\x00'], trans_id)
761
778
        tt.apply()
762
 
        self.tree1.commit('rename and modify binary', rev_id='b@cset-0-3')
763
 
        self.get_valid_bundle('b@cset-0-2', 'b@cset-0-3')
 
779
        self.tree1.commit('rename and modify binary', rev_id=b'b@cset-0-3')
 
780
        self.get_valid_bundle(b'b@cset-0-2', b'b@cset-0-3')
764
781
 
765
782
        # Modify
766
 
        tt = TreeTransform(self.tree1)
767
 
        trans_id = tt.trans_id_tree_file_id('binary-2')
 
783
        tt = self.tree1.get_transform()
 
784
        trans_id = tt.trans_id_tree_path('file3')
768
785
        tt.delete_contents(trans_id)
769
 
        tt.create_file('\x00file\rcontents', trans_id)
 
786
        tt.create_file([b'\x00file\rcontents'], trans_id)
770
787
        tt.apply()
771
 
        self.tree1.commit('just modify binary', rev_id='b@cset-0-4')
772
 
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
 
788
        self.tree1.commit('just modify binary', rev_id=b'b@cset-0-4')
 
789
        self.get_valid_bundle(b'b@cset-0-3', b'b@cset-0-4')
773
790
 
774
791
        # Rollup
775
 
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
792
        self.get_valid_bundle(b'null:', b'b@cset-0-4')
776
793
 
777
794
    def test_last_modified(self):
778
795
        self.tree1 = self.make_branch_and_tree('b1')
779
796
        self.b1 = self.tree1.branch
780
 
        tt = TreeTransform(self.tree1)
781
 
        tt.new_file('file', tt.root, 'file', 'file')
782
 
        tt.apply()
783
 
        self.tree1.commit('create file', rev_id='a@lmod-0-1')
784
 
 
785
 
        tt = TreeTransform(self.tree1)
786
 
        trans_id = tt.trans_id_tree_file_id('file')
787
 
        tt.delete_contents(trans_id)
788
 
        tt.create_file('file2', trans_id)
789
 
        tt.apply()
790
 
        self.tree1.commit('modify text', rev_id='a@lmod-0-2a')
791
 
 
792
 
        other = self.get_checkout('a@lmod-0-1')
793
 
        tt = TreeTransform(other)
794
 
        trans_id = tt.trans_id_tree_file_id('file')
795
 
        tt.delete_contents(trans_id)
796
 
        tt.create_file('file2', trans_id)
797
 
        tt.apply()
798
 
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
 
797
        tt = self.tree1.get_transform()
 
798
        tt.new_file('file', tt.root, [b'file'], b'file')
 
799
        tt.apply()
 
800
        self.tree1.commit('create file', rev_id=b'a@lmod-0-1')
 
801
 
 
802
        tt = self.tree1.get_transform()
 
803
        trans_id = tt.trans_id_tree_path('file')
 
804
        tt.delete_contents(trans_id)
 
805
        tt.create_file([b'file2'], trans_id)
 
806
        tt.apply()
 
807
        self.tree1.commit('modify text', rev_id=b'a@lmod-0-2a')
 
808
 
 
809
        other = self.get_checkout(b'a@lmod-0-1')
 
810
        tt = other.get_transform()
 
811
        trans_id = tt.trans_id_tree_path('file2')
 
812
        tt.delete_contents(trans_id)
 
813
        tt.create_file([b'file2'], trans_id)
 
814
        tt.apply()
 
815
        other.commit('modify text in another tree', rev_id=b'a@lmod-0-2b')
799
816
        self.tree1.merge_from_branch(other.branch)
800
 
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
 
817
        self.tree1.commit(u'Merge', rev_id=b'a@lmod-0-3',
801
818
                          verbose=False)
802
 
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
803
 
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
 
819
        self.tree1.commit(u'Merge', rev_id=b'a@lmod-0-4')
 
820
        bundle = self.get_valid_bundle(b'a@lmod-0-2a', b'a@lmod-0-4')
804
821
 
805
822
    def test_hide_history(self):
806
823
        self.tree1 = self.make_branch_and_tree('b1')
807
824
        self.b1 = self.tree1.branch
808
825
 
809
 
        open('b1/one', 'wb').write('one\n')
 
826
        with open('b1/one', 'wb') as f:
 
827
            f.write(b'one\n')
810
828
        self.tree1.add('one')
811
 
        self.tree1.commit('add file', rev_id='a@cset-0-1')
812
 
        open('b1/one', 'wb').write('two\n')
813
 
        self.tree1.commit('modify', rev_id='a@cset-0-2')
814
 
        open('b1/one', 'wb').write('three\n')
815
 
        self.tree1.commit('modify', rev_id='a@cset-0-3')
816
 
        bundle_file = StringIO()
817
 
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
818
 
                               'a@cset-0-1', bundle_file, format=self.format)
819
 
        self.assertNotContainsRe(bundle_file.getvalue(), '\btwo\b')
820
 
        self.assertContainsRe(self.get_raw(bundle_file), 'one')
821
 
        self.assertContainsRe(self.get_raw(bundle_file), 'three')
 
829
        self.tree1.commit('add file', rev_id=b'a@cset-0-1')
 
830
        with open('b1/one', 'wb') as f:
 
831
            f.write(b'two\n')
 
832
        self.tree1.commit('modify', rev_id=b'a@cset-0-2')
 
833
        with open('b1/one', 'wb') as f:
 
834
            f.write(b'three\n')
 
835
        self.tree1.commit('modify', rev_id=b'a@cset-0-3')
 
836
        bundle_file = BytesIO()
 
837
        rev_ids = write_bundle(self.tree1.branch.repository, b'a@cset-0-3',
 
838
                               b'a@cset-0-1', bundle_file, format=self.format)
 
839
        self.assertNotContainsRe(bundle_file.getvalue(), b'\btwo\b')
 
840
        self.assertContainsRe(self.get_raw(bundle_file), b'one')
 
841
        self.assertContainsRe(self.get_raw(bundle_file), b'three')
822
842
 
823
843
    def test_bundle_same_basis(self):
824
844
        """Ensure using the basis as the target doesn't cause an error"""
825
845
        self.tree1 = self.make_branch_and_tree('b1')
826
 
        self.tree1.commit('add file', rev_id='a@cset-0-1')
827
 
        bundle_file = StringIO()
828
 
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-1',
829
 
                               'a@cset-0-1', bundle_file)
 
846
        self.tree1.commit('add file', rev_id=b'a@cset-0-1')
 
847
        bundle_file = BytesIO()
 
848
        rev_ids = write_bundle(self.tree1.branch.repository, b'a@cset-0-1',
 
849
                               b'a@cset-0-1', bundle_file)
830
850
 
831
851
    @staticmethod
832
852
    def get_raw(bundle_file):
833
853
        return bundle_file.getvalue()
834
854
 
835
855
    def test_unicode_bundle(self):
836
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
856
        self.requireFeature(features.UnicodeFilenameFeature)
837
857
        # Handle international characters
838
858
        os.mkdir('b1')
839
859
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
842
862
        self.b1 = self.tree1.branch
843
863
 
844
864
        f.write((u'A file\n'
845
 
            u'With international man of mystery\n'
846
 
            u'William Dod\xe9\n').encode('utf-8'))
 
865
                 u'With international man of mystery\n'
 
866
                 u'William Dod\xe9\n').encode('utf-8'))
847
867
        f.close()
848
868
 
849
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
869
        self.tree1.add([u'with Dod\N{Euro Sign}'], [b'withdod-id'])
850
870
        self.tree1.commit(u'i18n commit from William Dod\xe9',
851
 
                          rev_id='i18n-1', committer=u'William Dod\xe9')
 
871
                          rev_id=b'i18n-1', committer=u'William Dod\xe9')
852
872
 
853
873
        # Add
854
 
        bundle = self.get_valid_bundle('null:', 'i18n-1')
 
874
        bundle = self.get_valid_bundle(b'null:', b'i18n-1')
855
875
 
856
876
        # Modified
857
877
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
858
878
        f.write(u'Modified \xb5\n'.encode('utf8'))
859
879
        f.close()
860
 
        self.tree1.commit(u'modified', rev_id='i18n-2')
 
880
        self.tree1.commit(u'modified', rev_id=b'i18n-2')
861
881
 
862
 
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
 
882
        bundle = self.get_valid_bundle(b'i18n-1', b'i18n-2')
863
883
 
864
884
        # Renamed
865
885
        self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
866
 
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
 
886
        self.tree1.commit(u'renamed, the new i18n man', rev_id=b'i18n-3',
867
887
                          committer=u'Erik B\xe5gfors')
868
888
 
869
 
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
 
889
        bundle = self.get_valid_bundle(b'i18n-2', b'i18n-3')
870
890
 
871
891
        # Removed
872
892
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
873
 
        self.tree1.commit(u'removed', rev_id='i18n-4')
 
893
        self.tree1.commit(u'removed', rev_id=b'i18n-4')
874
894
 
875
 
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
 
895
        bundle = self.get_valid_bundle(b'i18n-3', b'i18n-4')
876
896
 
877
897
        # Rollup
878
 
        bundle = self.get_valid_bundle('null:', 'i18n-4')
879
 
 
 
898
        bundle = self.get_valid_bundle(b'null:', b'i18n-4')
880
899
 
881
900
    def test_whitespace_bundle(self):
882
901
        if sys.platform in ('win32', 'cygwin'):
891
910
        #       once we actually support them
892
911
 
893
912
        # Added
894
 
        self.tree1.commit('funky whitespace', rev_id='white-1')
 
913
        self.tree1.commit('funky whitespace', rev_id=b'white-1')
895
914
 
896
 
        bundle = self.get_valid_bundle('null:', 'white-1')
 
915
        bundle = self.get_valid_bundle(b'null:', b'white-1')
897
916
 
898
917
        # Modified
899
 
        open('b1/trailing space ', 'ab').write('add some text\n')
900
 
        self.tree1.commit('add text', rev_id='white-2')
 
918
        with open('b1/trailing space ', 'ab') as f:
 
919
            f.write(b'add some text\n')
 
920
        self.tree1.commit('add text', rev_id=b'white-2')
901
921
 
902
 
        bundle = self.get_valid_bundle('white-1', 'white-2')
 
922
        bundle = self.get_valid_bundle(b'white-1', b'white-2')
903
923
 
904
924
        # Renamed
905
925
        self.tree1.rename_one('trailing space ', ' start and end space ')
906
 
        self.tree1.commit('rename', rev_id='white-3')
 
926
        self.tree1.commit('rename', rev_id=b'white-3')
907
927
 
908
 
        bundle = self.get_valid_bundle('white-2', 'white-3')
 
928
        bundle = self.get_valid_bundle(b'white-2', b'white-3')
909
929
 
910
930
        # Removed
911
931
        self.tree1.remove([' start and end space '])
912
 
        self.tree1.commit('removed', rev_id='white-4')
 
932
        self.tree1.commit('removed', rev_id=b'white-4')
913
933
 
914
 
        bundle = self.get_valid_bundle('white-3', 'white-4')
 
934
        bundle = self.get_valid_bundle(b'white-3', b'white-4')
915
935
 
916
936
        # Now test a complet roll-up
917
 
        bundle = self.get_valid_bundle('null:', 'white-4')
 
937
        bundle = self.get_valid_bundle(b'null:', b'white-4')
918
938
 
919
939
    def test_alt_timezone_bundle(self):
920
940
        self.tree1 = self.make_branch_and_memory_tree('b1')
927
947
        builder.finish_tree()
928
948
 
929
949
        # Asia/Colombo offset = 5 hours 30 minutes
930
 
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
 
950
        self.tree1.commit('non-hour offset timezone', rev_id=b'tz-1',
931
951
                          timezone=19800, timestamp=1152544886.0)
932
952
 
933
 
        bundle = self.get_valid_bundle('null:', 'tz-1')
 
953
        bundle = self.get_valid_bundle(b'null:', b'tz-1')
934
954
 
935
955
        rev = bundle.revisions[0]
936
956
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
941
961
    def test_bundle_root_id(self):
942
962
        self.tree1 = self.make_branch_and_tree('b1')
943
963
        self.b1 = self.tree1.branch
944
 
        self.tree1.commit('message', rev_id='revid1')
945
 
        bundle = self.get_valid_bundle('null:', 'revid1')
946
 
        tree = self.get_bundle_tree(bundle, 'revid1')
947
 
        self.assertEqual('revid1', tree.inventory.root.revision)
 
964
        self.tree1.commit('message', rev_id=b'revid1')
 
965
        bundle = self.get_valid_bundle(b'null:', b'revid1')
 
966
        tree = self.get_bundle_tree(bundle, b'revid1')
 
967
        root_revision = tree.get_file_revision(u'')
 
968
        self.assertEqual(b'revid1', root_revision)
948
969
 
949
970
    def test_install_revisions(self):
950
971
        self.tree1 = self.make_branch_and_tree('b1')
951
972
        self.b1 = self.tree1.branch
952
 
        self.tree1.commit('message', rev_id='rev2a')
953
 
        bundle = self.get_valid_bundle('null:', 'rev2a')
 
973
        self.tree1.commit('message', rev_id=b'rev2a')
 
974
        bundle = self.get_valid_bundle(b'null:', b'rev2a')
954
975
        branch2 = self.make_branch('b2')
955
 
        self.assertFalse(branch2.repository.has_revision('rev2a'))
 
976
        self.assertFalse(branch2.repository.has_revision(b'rev2a'))
956
977
        target_revision = bundle.install_revisions(branch2.repository)
957
 
        self.assertTrue(branch2.repository.has_revision('rev2a'))
958
 
        self.assertEqual('rev2a', target_revision)
 
978
        self.assertTrue(branch2.repository.has_revision(b'rev2a'))
 
979
        self.assertEqual(b'rev2a', target_revision)
959
980
 
960
981
    def test_bundle_empty_property(self):
961
982
        """Test serializing revision properties with an empty value."""
962
983
        tree = self.make_branch_and_memory_tree('tree')
963
984
        tree.lock_write()
964
985
        self.addCleanup(tree.unlock)
965
 
        tree.add([''], ['TREE_ROOT'])
966
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
986
        tree.add([''], [b'TREE_ROOT'])
 
987
        tree.commit('One', revprops={u'one': 'two',
 
988
                                     u'empty': ''}, rev_id=b'rev1')
967
989
        self.b1 = tree.branch
968
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
990
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
969
991
        bundle = read_bundle(bundle_sio)
970
992
        revision_info = bundle.revisions[0]
971
 
        self.assertEqual('rev1', revision_info.revision_id)
 
993
        self.assertEqual(b'rev1', revision_info.revision_id)
972
994
        rev = revision_info.as_revision()
973
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
995
        self.assertEqual({'branch-nick': 'tree', 'empty': '', 'one': 'two'},
974
996
                         rev.properties)
975
997
 
976
998
    def test_bundle_sorted_properties(self):
979
1001
        tree.lock_write()
980
1002
        self.addCleanup(tree.unlock)
981
1003
 
982
 
        tree.add([''], ['TREE_ROOT'])
983
 
        tree.commit('One', rev_id='rev1',
984
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
1004
        tree.add([''], [b'TREE_ROOT'])
 
1005
        tree.commit('One', rev_id=b'rev1',
 
1006
                    revprops={u'a': '4', u'b': '3', u'c': '2', u'd': '1'})
985
1007
        self.b1 = tree.branch
986
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1008
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
987
1009
        bundle = read_bundle(bundle_sio)
988
1010
        revision_info = bundle.revisions[0]
989
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1011
        self.assertEqual(b'rev1', revision_info.revision_id)
990
1012
        rev = revision_info.as_revision()
991
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
992
 
                          'd':'1'}, rev.properties)
 
1013
        self.assertEqual({'branch-nick': 'tree', 'a': '4', 'b': '3', 'c': '2',
 
1014
                          'd': '1'}, rev.properties)
993
1015
 
994
1016
    def test_bundle_unicode_properties(self):
995
1017
        """We should be able to round trip a non-ascii property."""
997
1019
        tree.lock_write()
998
1020
        self.addCleanup(tree.unlock)
999
1021
 
1000
 
        tree.add([''], ['TREE_ROOT'])
 
1022
        tree.add([''], [b'TREE_ROOT'])
1001
1023
        # Revisions themselves do not require anything about revision property
1002
1024
        # keys, other than that they are a basestring, and do not contain
1003
1025
        # whitespace.
1004
1026
        # However, Testaments assert than they are str(), and thus should not
1005
1027
        # be Unicode.
1006
 
        tree.commit('One', rev_id='rev1',
1007
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
1028
        tree.commit('One', rev_id=b'rev1',
 
1029
                    revprops={u'omega': u'\u03a9', u'alpha': u'\u03b1'})
1008
1030
        self.b1 = tree.branch
1009
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1031
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1010
1032
        bundle = read_bundle(bundle_sio)
1011
1033
        revision_info = bundle.revisions[0]
1012
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1034
        self.assertEqual(b'rev1', revision_info.revision_id)
1013
1035
        rev = revision_info.as_revision()
1014
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1015
 
                          'alpha':u'\u03b1'}, rev.properties)
 
1036
        self.assertEqual({'branch-nick': 'tree', 'omega': u'\u03a9',
 
1037
                          'alpha': u'\u03b1'}, rev.properties)
1016
1038
 
1017
1039
    def test_bundle_with_ghosts(self):
1018
1040
        tree = self.make_branch_and_tree('tree')
1019
1041
        self.b1 = tree.branch
1020
 
        self.build_tree_contents([('tree/file', 'content1')])
 
1042
        self.build_tree_contents([('tree/file', b'content1')])
1021
1043
        tree.add(['file'])
1022
1044
        tree.commit('rev1')
1023
 
        self.build_tree_contents([('tree/file', 'content2')])
1024
 
        tree.add_parent_tree_id('ghost')
1025
 
        tree.commit('rev2', rev_id='rev2')
1026
 
        bundle = self.get_valid_bundle('null:', 'rev2')
 
1045
        self.build_tree_contents([('tree/file', b'content2')])
 
1046
        tree.add_parent_tree_id(b'ghost')
 
1047
        tree.commit('rev2', rev_id=b'rev2')
 
1048
        bundle = self.get_valid_bundle(b'null:', b'rev2')
1027
1049
 
1028
1050
    def make_simple_tree(self, format=None):
1029
1051
        tree = self.make_branch_and_tree('b1', format=format)
1034
1056
 
1035
1057
    def test_across_serializers(self):
1036
1058
        tree = self.make_simple_tree('knit')
1037
 
        tree.commit('hello', rev_id='rev1')
1038
 
        tree.commit('hello', rev_id='rev2')
1039
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1059
        tree.commit('hello', rev_id=b'rev1')
 
1060
        tree.commit('hello', rev_id=b'rev2')
 
1061
        bundle = read_bundle(self.create_bundle_text(b'null:', b'rev2')[0])
1040
1062
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1041
1063
        bundle.install_revisions(repo)
1042
 
        inv_text = repo._get_inventory_xml('rev2')
1043
 
        self.assertNotContainsRe(inv_text, 'format="5"')
1044
 
        self.assertContainsRe(inv_text, 'format="7"')
 
1064
        inv_text = repo._get_inventory_xml(b'rev2')
 
1065
        self.assertNotContainsRe(inv_text, b'format="5"')
 
1066
        self.assertContainsRe(inv_text, b'format="7"')
1045
1067
 
1046
1068
    def make_repo_with_installed_revisions(self):
1047
1069
        tree = self.make_simple_tree('knit')
1048
 
        tree.commit('hello', rev_id='rev1')
1049
 
        tree.commit('hello', rev_id='rev2')
1050
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1070
        tree.commit('hello', rev_id=b'rev1')
 
1071
        tree.commit('hello', rev_id=b'rev2')
 
1072
        bundle = read_bundle(self.create_bundle_text(b'null:', b'rev2')[0])
1051
1073
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1052
1074
        bundle.install_revisions(repo)
1053
1075
        return repo
1054
1076
 
1055
1077
    def test_across_models(self):
1056
1078
        repo = self.make_repo_with_installed_revisions()
1057
 
        inv = repo.get_inventory('rev2')
1058
 
        self.assertEqual('rev2', inv.root.revision)
 
1079
        inv = repo.get_inventory(b'rev2')
 
1080
        self.assertEqual(b'rev2', inv.root.revision)
1059
1081
        root_id = inv.root.file_id
1060
1082
        repo.lock_read()
1061
1083
        self.addCleanup(repo.unlock)
1062
 
        self.assertEqual({(root_id, 'rev1'):(),
1063
 
            (root_id, 'rev2'):((root_id, 'rev1'),)},
1064
 
            repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
 
1084
        self.assertEqual({(root_id, b'rev1'): (),
 
1085
                          (root_id, b'rev2'): ((root_id, b'rev1'),)},
 
1086
                         repo.texts.get_parent_map([(root_id, b'rev1'), (root_id, b'rev2')]))
1065
1087
 
1066
1088
    def test_inv_hash_across_serializers(self):
1067
1089
        repo = self.make_repo_with_installed_revisions()
1068
 
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
1069
 
        xml = repo._get_inventory_xml('rev2')
 
1090
        recorded_inv_sha1 = repo.get_revision(b'rev2').inventory_sha1
 
1091
        xml = repo._get_inventory_xml(b'rev2')
1070
1092
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1071
1093
 
1072
1094
    def test_across_models_incompatible(self):
1073
1095
        tree = self.make_simple_tree('dirstate-with-subtree')
1074
 
        tree.commit('hello', rev_id='rev1')
1075
 
        tree.commit('hello', rev_id='rev2')
 
1096
        tree.commit('hello', rev_id=b'rev1')
 
1097
        tree.commit('hello', rev_id=b'rev2')
1076
1098
        try:
1077
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1099
            bundle = read_bundle(self.create_bundle_text(b'null:', b'rev1')[0])
1078
1100
        except errors.IncompatibleBundleFormat:
1079
1101
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1080
1102
        repo = self.make_repository('repo', format='knit')
1081
1103
        bundle.install_revisions(repo)
1082
1104
 
1083
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1105
        bundle = read_bundle(self.create_bundle_text(b'null:', b'rev2')[0])
1084
1106
        self.assertRaises(errors.IncompatibleRevision,
1085
1107
                          bundle.install_revisions, repo)
1086
1108
 
1087
1109
    def test_get_merge_request(self):
1088
1110
        tree = self.make_simple_tree()
1089
 
        tree.commit('hello', rev_id='rev1')
1090
 
        tree.commit('hello', rev_id='rev2')
1091
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1111
        tree.commit('hello', rev_id=b'rev1')
 
1112
        tree.commit('hello', rev_id=b'rev2')
 
1113
        bundle = read_bundle(self.create_bundle_text(b'null:', b'rev1')[0])
1092
1114
        result = bundle.get_merge_request(tree.branch.repository)
1093
 
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
 
1115
        self.assertEqual((None, b'rev1', 'inapplicable'), result)
1094
1116
 
1095
1117
    def test_with_subtree(self):
1096
1118
        tree = self.make_branch_and_tree('tree',
1099
1121
        subtree = self.make_branch_and_tree('tree/subtree',
1100
1122
                                            format='dirstate-with-subtree')
1101
1123
        tree.add('subtree')
1102
 
        tree.commit('hello', rev_id='rev1')
 
1124
        tree.commit('hello', rev_id=b'rev1')
1103
1125
        try:
1104
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1126
            bundle = read_bundle(self.create_bundle_text(b'null:', b'rev1')[0])
1105
1127
        except errors.IncompatibleBundleFormat:
1106
1128
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1107
1129
        if isinstance(bundle, v09.BundleInfo09):
1116
1138
        self.tree1 = self.make_branch_and_tree('tree')
1117
1139
        self.b1 = self.tree1.branch
1118
1140
        try:
1119
 
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
 
1141
            self.tree1.commit('Revision/id/with/slashes', rev_id=b'rev/id')
1120
1142
        except ValueError:
1121
1143
            raise tests.TestSkipped(
1122
1144
                "Repository doesn't support revision ids with slashes")
1123
 
        bundle = self.get_valid_bundle('null:', 'rev/id')
 
1145
        bundle = self.get_valid_bundle(b'null:', b'rev/id')
1124
1146
 
1125
1147
    def test_skip_file(self):
1126
1148
        """Make sure we don't accidentally write to the wrong versionedfile"""
1127
1149
        self.tree1 = self.make_branch_and_tree('tree')
1128
1150
        self.b1 = self.tree1.branch
1129
1151
        # rev1 is not present in bundle, done by fetch
1130
 
        self.build_tree_contents([('tree/file2', 'contents1')])
1131
 
        self.tree1.add('file2', 'file2-id')
1132
 
        self.tree1.commit('rev1', rev_id='reva')
1133
 
        self.build_tree_contents([('tree/file3', 'contents2')])
 
1152
        self.build_tree_contents([('tree/file2', b'contents1')])
 
1153
        self.tree1.add('file2', b'file2-id')
 
1154
        self.tree1.commit('rev1', rev_id=b'reva')
 
1155
        self.build_tree_contents([('tree/file3', b'contents2')])
1134
1156
        # rev2 is present in bundle, and done by fetch
1135
1157
        # having file1 in the bunle causes file1's versionedfile to be opened.
1136
 
        self.tree1.add('file3', 'file3-id')
1137
 
        self.tree1.commit('rev2')
 
1158
        self.tree1.add('file3', b'file3-id')
 
1159
        rev2 = self.tree1.commit('rev2')
1138
1160
        # Updating file2 should not cause an attempt to add to file1's vf
1139
 
        target = self.tree1.bzrdir.sprout('target').open_workingtree()
1140
 
        self.build_tree_contents([('tree/file2', 'contents3')])
1141
 
        self.tree1.commit('rev3', rev_id='rev3')
1142
 
        bundle = self.get_valid_bundle('reva', 'rev3')
 
1161
        target = self.tree1.controldir.sprout('target').open_workingtree()
 
1162
        self.build_tree_contents([('tree/file2', b'contents3')])
 
1163
        self.tree1.commit('rev3', rev_id=b'rev3')
 
1164
        bundle = self.get_valid_bundle(b'reva', b'rev3')
1143
1165
        if getattr(bundle, 'get_bundle_reader', None) is None:
1144
1166
            raise tests.TestSkipped('Bundle format cannot provide reader')
1145
 
        # be sure that file1 comes before file2
1146
 
        for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1147
 
            if f == 'file3-id':
1148
 
                break
1149
 
            self.assertNotEqual(f, 'file2-id')
 
1167
        file_ids = set(
 
1168
            (f, r) for b, m, k, r, f in bundle.get_bundle_reader().iter_records()
 
1169
            if f is not None)
 
1170
        self.assertEqual(
 
1171
            {(b'file2-id', b'rev3'), (b'file3-id', rev2)}, file_ids)
1150
1172
        bundle.install_revisions(target.branch.repository)
1151
1173
 
1152
1174
 
1159
1181
        tree = self.make_branch_and_memory_tree('tree')
1160
1182
        tree.lock_write()
1161
1183
        self.addCleanup(tree.unlock)
1162
 
        tree.add([''], ['TREE_ROOT'])
1163
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1184
        tree.add([''], [b'TREE_ROOT'])
 
1185
        tree.commit('One', revprops={u'one': 'two',
 
1186
                                     u'empty': ''}, rev_id=b'rev1')
1164
1187
        self.b1 = tree.branch
1165
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1188
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1166
1189
        self.assertContainsRe(bundle_sio.getvalue(),
1167
 
                              '# properties:\n'
1168
 
                              '#   branch-nick: tree\n'
1169
 
                              '#   empty: \n'
1170
 
                              '#   one: two\n'
1171
 
                             )
 
1190
                              b'# properties:\n'
 
1191
                              b'#   branch-nick: tree\n'
 
1192
                              b'#   empty: \n'
 
1193
                              b'#   one: two\n'
 
1194
                              )
1172
1195
        bundle = read_bundle(bundle_sio)
1173
1196
        revision_info = bundle.revisions[0]
1174
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1197
        self.assertEqual(b'rev1', revision_info.revision_id)
1175
1198
        rev = revision_info.as_revision()
1176
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1199
        self.assertEqual({'branch-nick': 'tree', 'empty': '', 'one': 'two'},
1177
1200
                         rev.properties)
1178
1201
 
1179
1202
    def get_bundle_tree(self, bundle, revision_id):
1180
1203
        repository = self.make_repository('repo')
1181
 
        return bundle.revision_tree(repository, 'revid1')
 
1204
        return bundle.revision_tree(repository, b'revid1')
1182
1205
 
1183
1206
    def test_bundle_empty_property_alt(self):
1184
1207
        """Test serializing revision properties with an empty value.
1191
1214
        tree = self.make_branch_and_memory_tree('tree')
1192
1215
        tree.lock_write()
1193
1216
        self.addCleanup(tree.unlock)
1194
 
        tree.add([''], ['TREE_ROOT'])
1195
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1217
        tree.add([''], [b'TREE_ROOT'])
 
1218
        tree.commit('One', revprops={u'one': 'two',
 
1219
                                     u'empty': ''}, rev_id=b'rev1')
1196
1220
        self.b1 = tree.branch
1197
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1221
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1198
1222
        txt = bundle_sio.getvalue()
1199
 
        loc = txt.find('#   empty: ') + len('#   empty:')
 
1223
        loc = txt.find(b'#   empty: ') + len(b'#   empty:')
1200
1224
        # Create a new bundle, which strips the trailing space after empty
1201
 
        bundle_sio = StringIO(txt[:loc] + txt[loc+1:])
 
1225
        bundle_sio = BytesIO(txt[:loc] + txt[loc + 1:])
1202
1226
 
1203
1227
        self.assertContainsRe(bundle_sio.getvalue(),
1204
 
                              '# properties:\n'
1205
 
                              '#   branch-nick: tree\n'
1206
 
                              '#   empty:\n'
1207
 
                              '#   one: two\n'
1208
 
                             )
 
1228
                              b'# properties:\n'
 
1229
                              b'#   branch-nick: tree\n'
 
1230
                              b'#   empty:\n'
 
1231
                              b'#   one: two\n'
 
1232
                              )
1209
1233
        bundle = read_bundle(bundle_sio)
1210
1234
        revision_info = bundle.revisions[0]
1211
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1235
        self.assertEqual(b'rev1', revision_info.revision_id)
1212
1236
        rev = revision_info.as_revision()
1213
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1237
        self.assertEqual({'branch-nick': 'tree', 'empty': '', 'one': 'two'},
1214
1238
                         rev.properties)
1215
1239
 
1216
1240
    def test_bundle_sorted_properties(self):
1219
1243
        tree.lock_write()
1220
1244
        self.addCleanup(tree.unlock)
1221
1245
 
1222
 
        tree.add([''], ['TREE_ROOT'])
1223
 
        tree.commit('One', rev_id='rev1',
1224
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
1246
        tree.add([''], [b'TREE_ROOT'])
 
1247
        tree.commit('One', rev_id=b'rev1',
 
1248
                    revprops={u'a': '4', u'b': '3', u'c': '2', u'd': '1'})
1225
1249
        self.b1 = tree.branch
1226
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1250
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1227
1251
        self.assertContainsRe(bundle_sio.getvalue(),
1228
 
                              '# properties:\n'
1229
 
                              '#   a: 4\n'
1230
 
                              '#   b: 3\n'
1231
 
                              '#   branch-nick: tree\n'
1232
 
                              '#   c: 2\n'
1233
 
                              '#   d: 1\n'
1234
 
                             )
 
1252
                              b'# properties:\n'
 
1253
                              b'#   a: 4\n'
 
1254
                              b'#   b: 3\n'
 
1255
                              b'#   branch-nick: tree\n'
 
1256
                              b'#   c: 2\n'
 
1257
                              b'#   d: 1\n'
 
1258
                              )
1235
1259
        bundle = read_bundle(bundle_sio)
1236
1260
        revision_info = bundle.revisions[0]
1237
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1261
        self.assertEqual(b'rev1', revision_info.revision_id)
1238
1262
        rev = revision_info.as_revision()
1239
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
1240
 
                          'd':'1'}, rev.properties)
 
1263
        self.assertEqual({'branch-nick': 'tree', 'a': '4', 'b': '3', 'c': '2',
 
1264
                          'd': '1'}, rev.properties)
1241
1265
 
1242
1266
    def test_bundle_unicode_properties(self):
1243
1267
        """We should be able to round trip a non-ascii property."""
1245
1269
        tree.lock_write()
1246
1270
        self.addCleanup(tree.unlock)
1247
1271
 
1248
 
        tree.add([''], ['TREE_ROOT'])
 
1272
        tree.add([''], [b'TREE_ROOT'])
1249
1273
        # Revisions themselves do not require anything about revision property
1250
1274
        # keys, other than that they are a basestring, and do not contain
1251
1275
        # whitespace.
1252
1276
        # However, Testaments assert than they are str(), and thus should not
1253
1277
        # be Unicode.
1254
 
        tree.commit('One', rev_id='rev1',
1255
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
1278
        tree.commit('One', rev_id=b'rev1',
 
1279
                    revprops={u'omega': u'\u03a9', u'alpha': u'\u03b1'})
1256
1280
        self.b1 = tree.branch
1257
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1281
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1258
1282
        self.assertContainsRe(bundle_sio.getvalue(),
1259
 
                              '# properties:\n'
1260
 
                              '#   alpha: \xce\xb1\n'
1261
 
                              '#   branch-nick: tree\n'
1262
 
                              '#   omega: \xce\xa9\n'
1263
 
                             )
 
1283
                              b'# properties:\n'
 
1284
                              b'#   alpha: \xce\xb1\n'
 
1285
                              b'#   branch-nick: tree\n'
 
1286
                              b'#   omega: \xce\xa9\n'
 
1287
                              )
1264
1288
        bundle = read_bundle(bundle_sio)
1265
1289
        revision_info = bundle.revisions[0]
1266
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1290
        self.assertEqual(b'rev1', revision_info.revision_id)
1267
1291
        rev = revision_info.as_revision()
1268
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1269
 
                          'alpha':u'\u03b1'}, rev.properties)
 
1292
        self.assertEqual({'branch-nick': 'tree', 'omega': u'\u03a9',
 
1293
                          'alpha': u'\u03b1'}, rev.properties)
1270
1294
 
1271
1295
 
1272
1296
class V09BundleKnit2Tester(V08BundleTester):
1319
1343
            self.assertEqual(len(branch_rev.parent_ids),
1320
1344
                             len(bundle_rev.parent_ids))
1321
1345
        self.assertEqual(set(rev_ids),
1322
 
                         set([r.revision_id for r in bundle.real_revisions]))
 
1346
                         {r.revision_id for r in bundle.real_revisions})
1323
1347
        self.valid_apply_bundle(base_rev_id, bundle,
1324
 
                                   checkout_dir=checkout_dir)
 
1348
                                checkout_dir=checkout_dir)
1325
1349
 
1326
1350
        return bundle
1327
1351
 
1331
1355
 
1332
1356
        :return: The in-memory bundle
1333
1357
        """
1334
 
        from bzrlib.bundle import serializer
 
1358
        from ..bzr.bundle import serializer
1335
1359
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1336
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1337
 
        new_text = new_text.replace('<file file_id="exe-1"',
1338
 
                                    '<file executable="y" file_id="exe-1"')
1339
 
        new_text = new_text.replace('B260', 'B275')
1340
 
        bundle_txt = StringIO()
 
1360
        new_text = self.get_raw(BytesIO(b''.join(bundle_txt)))
 
1361
        new_text = new_text.replace(b'<file file_id="exe-1"',
 
1362
                                    b'<file executable="y" file_id="exe-1"')
 
1363
        new_text = new_text.replace(b'B260', b'B275')
 
1364
        bundle_txt = BytesIO()
1341
1365
        bundle_txt.write(serializer._get_bundle_header('4'))
1342
 
        bundle_txt.write('\n')
1343
 
        bundle_txt.write(new_text.encode('bz2'))
 
1366
        bundle_txt.write(b'\n')
 
1367
        bundle_txt.write(bz2.compress(new_text))
1344
1368
        bundle_txt.seek(0)
1345
1369
        bundle = read_bundle(bundle_txt)
1346
1370
        self.valid_apply_bundle(base_rev_id, bundle)
1347
1371
        return bundle
1348
1372
 
1349
1373
    def create_bundle_text(self, base_rev_id, rev_id):
1350
 
        bundle_txt = StringIO()
 
1374
        bundle_txt = BytesIO()
1351
1375
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1352
1376
                               bundle_txt, format=self.format)
1353
1377
        bundle_txt.seek(0)
1354
1378
        self.assertEqual(bundle_txt.readline(),
1355
 
                         '# Bazaar revision bundle v%s\n' % self.format)
1356
 
        self.assertEqual(bundle_txt.readline(), '#\n')
 
1379
                         b'# Bazaar revision bundle v%s\n' % self.format.encode('ascii'))
 
1380
        self.assertEqual(bundle_txt.readline(), b'#\n')
1357
1381
        rev = self.b1.repository.get_revision(rev_id)
1358
1382
        bundle_txt.seek(0)
1359
1383
        return bundle_txt, rev_ids
1365
1389
 
1366
1390
    def test_creation(self):
1367
1391
        tree = self.make_branch_and_tree('tree')
1368
 
        self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
1369
 
        tree.add('file', 'fileid-2')
1370
 
        tree.commit('added file', rev_id='rev1')
1371
 
        self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
1372
 
        tree.commit('changed file', rev_id='rev2')
1373
 
        s = StringIO()
 
1392
        self.build_tree_contents([('tree/file', b'contents1\nstatic\n')])
 
1393
        tree.add('file', b'fileid-2')
 
1394
        tree.commit('added file', rev_id=b'rev1')
 
1395
        self.build_tree_contents([('tree/file', b'contents2\nstatic\n')])
 
1396
        tree.commit('changed file', rev_id=b'rev2')
 
1397
        s = BytesIO()
1374
1398
        serializer = BundleSerializerV4('1.0')
1375
 
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
 
1399
        with tree.lock_read():
 
1400
            serializer.write_bundle(
 
1401
                tree.branch.repository, b'rev2', b'null:', s)
1376
1402
        s.seek(0)
1377
1403
        tree2 = self.make_branch_and_tree('target')
1378
1404
        target_repo = tree2.branch.repository
1380
1406
        target_repo.lock_read()
1381
1407
        self.addCleanup(target_repo.unlock)
1382
1408
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1383
 
        repo_texts = dict((i, ''.join(content)) for i, content
 
1409
        repo_texts = dict((i, b''.join(content)) for i, content
1384
1410
                          in target_repo.iter_files_bytes(
1385
 
                                [('fileid-2', 'rev1', '1'),
1386
 
                                 ('fileid-2', 'rev2', '2')]))
1387
 
        self.assertEqual({'1':'contents1\nstatic\n',
1388
 
                          '2':'contents2\nstatic\n'},
 
1411
            [(b'fileid-2', b'rev1', '1'),
 
1412
             (b'fileid-2', b'rev2', '2')]))
 
1413
        self.assertEqual({'1': b'contents1\nstatic\n',
 
1414
                          '2': b'contents2\nstatic\n'},
1389
1415
                         repo_texts)
1390
 
        rtree = target_repo.revision_tree('rev2')
 
1416
        rtree = target_repo.revision_tree(b'rev2')
1391
1417
        inventory_vf = target_repo.inventories
1392
1418
        # If the inventory store has a graph, it must match the revision graph.
1393
1419
        self.assertSubset(
1394
 
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
1395
 
            [None, (('rev1',),)])
 
1420
            [inventory_vf.get_parent_map([(b'rev2',)])[(b'rev2',)]],
 
1421
            [None, ((b'rev1',),)])
1396
1422
        self.assertEqual('changed file',
1397
 
                         target_repo.get_revision('rev2').message)
 
1423
                         target_repo.get_revision(b'rev2').message)
1398
1424
 
1399
1425
    @staticmethod
1400
1426
    def get_raw(bundle_file):
1402
1428
        line = bundle_file.readline()
1403
1429
        line = bundle_file.readline()
1404
1430
        lines = bundle_file.readlines()
1405
 
        return ''.join(lines).decode('bz2')
 
1431
        return bz2.decompress(b''.join(lines))
1406
1432
 
1407
1433
    def test_copy_signatures(self):
1408
1434
        tree_a = self.make_branch_and_tree('tree_a')
1409
 
        import bzrlib.gpg
1410
 
        import bzrlib.commit as commit
1411
 
        oldstrategy = bzrlib.gpg.GPGStrategy
 
1435
        import breezy.gpg
 
1436
        import breezy.commit as commit
 
1437
        oldstrategy = breezy.gpg.GPGStrategy
1412
1438
        branch = tree_a.branch
1413
1439
        repo_a = branch.repository
1414
 
        tree_a.commit("base", allow_pointless=True, rev_id='A')
1415
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
1440
        tree_a.commit("base", allow_pointless=True, rev_id=b'A')
 
1441
        self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
1416
1442
        try:
1417
 
            from bzrlib.testament import Testament
 
1443
            from ..bzr.testament import Testament
1418
1444
            # monkey patch gpg signing mechanism
1419
 
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1420
 
            new_config = test_commit.MustSignConfig(branch)
1421
 
            commit.Commit(config=new_config).commit(message="base",
1422
 
                                                    allow_pointless=True,
1423
 
                                                    rev_id='B',
1424
 
                                                    working_tree=tree_a)
 
1445
            breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
 
1446
            new_config = test_commit.MustSignConfig()
 
1447
            commit.Commit(config_stack=new_config).commit(message="base",
 
1448
                                                          allow_pointless=True,
 
1449
                                                          rev_id=b'B',
 
1450
                                                          working_tree=tree_a)
 
1451
 
1425
1452
            def sign(text):
1426
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
1427
 
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
 
1453
                return breezy.gpg.LoopbackGPGStrategy(None).sign(text)
 
1454
            self.assertTrue(repo_a.has_signature_for_revision_id(b'B'))
1428
1455
        finally:
1429
 
            bzrlib.gpg.GPGStrategy = oldstrategy
 
1456
            breezy.gpg.GPGStrategy = oldstrategy
1430
1457
        tree_b = self.make_branch_and_tree('tree_b')
1431
1458
        repo_b = tree_b.branch.repository
1432
 
        s = StringIO()
 
1459
        s = BytesIO()
1433
1460
        serializer = BundleSerializerV4('4')
1434
 
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
 
1461
        with tree_a.lock_read():
 
1462
            serializer.write_bundle(
 
1463
                tree_a.branch.repository, b'B', b'null:', s)
1435
1464
        s.seek(0)
1436
1465
        install_bundle(repo_b, serializer.read(s))
1437
 
        self.assertTrue(repo_b.has_signature_for_revision_id('B'))
1438
 
        self.assertEqual(repo_b.get_signature_text('B'),
1439
 
                         repo_a.get_signature_text('B'))
 
1466
        self.assertTrue(repo_b.has_signature_for_revision_id(b'B'))
 
1467
        self.assertEqual(repo_b.get_signature_text(b'B'),
 
1468
                         repo_a.get_signature_text(b'B'))
1440
1469
        s.seek(0)
1441
1470
        # ensure repeat installs are harmless
1442
1471
        install_bundle(repo_b, serializer.read(s))
1443
1472
 
1444
1473
 
1445
 
class V4WeaveBundleTester(V4BundleTester):
1446
 
 
1447
 
    def bzrdir_format(self):
1448
 
        return 'metaweave'
1449
 
 
1450
 
 
1451
1474
class V4_2aBundleTester(V4BundleTester):
1452
1475
 
1453
1476
    def bzrdir_format(self):
1459
1482
 
1460
1483
        :return: The in-memory bundle
1461
1484
        """
1462
 
        from bzrlib.bundle import serializer
 
1485
        from ..bzr.bundle import serializer
1463
1486
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1464
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1487
        new_text = self.get_raw(BytesIO(b''.join(bundle_txt)))
1465
1488
        # We are going to be replacing some text to set the executable bit on a
1466
1489
        # file. Make sure the text replacement actually works correctly.
1467
 
        self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
1468
 
        new_text = new_text.replace('<file file_id="exe-1"',
1469
 
                                    '<file executable="y" file_id="exe-1"')
1470
 
        new_text = new_text.replace('B244', 'B259')
1471
 
        bundle_txt = StringIO()
 
1490
        self.assertContainsRe(new_text, b'(?m)B244\n\ni 1\n<inventory')
 
1491
        new_text = new_text.replace(b'<file file_id="exe-1"',
 
1492
                                    b'<file executable="y" file_id="exe-1"')
 
1493
        new_text = new_text.replace(b'B244', b'B259')
 
1494
        bundle_txt = BytesIO()
1472
1495
        bundle_txt.write(serializer._get_bundle_header('4'))
1473
 
        bundle_txt.write('\n')
1474
 
        bundle_txt.write(new_text.encode('bz2'))
 
1496
        bundle_txt.write(b'\n')
 
1497
        bundle_txt.write(bz2.compress(new_text))
1475
1498
        bundle_txt.seek(0)
1476
1499
        bundle = read_bundle(bundle_txt)
1477
1500
        self.valid_apply_bundle(base_rev_id, bundle)
1480
1503
    def make_merged_branch(self):
1481
1504
        builder = self.make_branch_builder('source')
1482
1505
        builder.start_series()
1483
 
        builder.build_snapshot('a@cset-0-1', None, [
1484
 
            ('add', ('', 'root-id', 'directory', None)),
1485
 
            ('add', ('file', 'file-id', 'file', 'original content\n')),
1486
 
            ])
1487
 
        builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
1488
 
            ('modify', ('file-id', 'new-content\n')),
1489
 
            ])
1490
 
        builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
1491
 
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1492
 
            ])
1493
 
        builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
1494
 
            ('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1495
 
            ])
 
1506
        builder.build_snapshot(None, [
 
1507
            ('add', ('', b'root-id', 'directory', None)),
 
1508
            ('add', ('file', b'file-id', 'file', b'original content\n')),
 
1509
            ], revision_id=b'a@cset-0-1')
 
1510
        builder.build_snapshot([b'a@cset-0-1'], [
 
1511
            ('modify', ('file', b'new-content\n')),
 
1512
            ], revision_id=b'a@cset-0-2a')
 
1513
        builder.build_snapshot([b'a@cset-0-1'], [
 
1514
            ('add', ('other-file', b'file2-id', 'file', b'file2-content\n')),
 
1515
            ], revision_id=b'a@cset-0-2b')
 
1516
        builder.build_snapshot([b'a@cset-0-2a', b'a@cset-0-2b'], [
 
1517
            ('add', ('other-file', b'file2-id', 'file', b'file2-content\n')),
 
1518
            ], revision_id=b'a@cset-0-3')
1496
1519
        builder.finish_series()
1497
1520
        self.b1 = builder.get_branch()
1498
1521
        self.b1.lock_read()
1501
1524
    def make_bundle_just_inventories(self, base_revision_id,
1502
1525
                                     target_revision_id,
1503
1526
                                     revision_ids):
1504
 
        sio = StringIO()
 
1527
        sio = BytesIO()
1505
1528
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
1506
1529
                                         self.b1.repository, sio)
1507
1530
        writer.bundle.begin()
1512
1535
 
1513
1536
    def test_single_inventory_multiple_parents_as_xml(self):
1514
1537
        self.make_merged_branch()
1515
 
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1516
 
                                                ['a@cset-0-3'])
 
1538
        sio = self.make_bundle_just_inventories(b'a@cset-0-1', b'a@cset-0-3',
 
1539
                                                [b'a@cset-0-3'])
1517
1540
        reader = v4.BundleReader(sio, stream_input=False)
1518
1541
        records = list(reader.iter_records())
1519
1542
        self.assertEqual(1, len(records))
1520
1543
        (bytes, metadata, repo_kind, revision_id,
1521
1544
         file_id) = records[0]
1522
1545
        self.assertIs(None, file_id)
1523
 
        self.assertEqual('a@cset-0-3', revision_id)
 
1546
        self.assertEqual(b'a@cset-0-3', revision_id)
1524
1547
        self.assertEqual('inventory', repo_kind)
1525
 
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1526
 
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1527
 
                          'storage_kind': 'mpdiff',
1528
 
                         }, metadata)
 
1548
        self.assertEqual({b'parents': [b'a@cset-0-2a', b'a@cset-0-2b'],
 
1549
                          b'sha1': b'09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1550
                          b'storage_kind': b'mpdiff',
 
1551
                          }, metadata)
1529
1552
        # We should have an mpdiff that takes some lines from both parents.
1530
1553
        self.assertEqualDiff(
1531
 
            'i 1\n'
1532
 
            '<inventory format="10" revision_id="a@cset-0-3">\n'
1533
 
            '\n'
1534
 
            'c 0 1 1 2\n'
1535
 
            'c 1 3 3 2\n', bytes)
 
1554
            b'i 1\n'
 
1555
            b'<inventory format="10" revision_id="a@cset-0-3">\n'
 
1556
            b'\n'
 
1557
            b'c 0 1 1 2\n'
 
1558
            b'c 1 3 3 2\n', bytes)
1536
1559
 
1537
1560
    def test_single_inv_no_parents_as_xml(self):
1538
1561
        self.make_merged_branch()
1539
 
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
1540
 
                                                ['a@cset-0-1'])
 
1562
        sio = self.make_bundle_just_inventories(b'null:', b'a@cset-0-1',
 
1563
                                                [b'a@cset-0-1'])
1541
1564
        reader = v4.BundleReader(sio, stream_input=False)
1542
1565
        records = list(reader.iter_records())
1543
1566
        self.assertEqual(1, len(records))
1544
1567
        (bytes, metadata, repo_kind, revision_id,
1545
1568
         file_id) = records[0]
1546
1569
        self.assertIs(None, file_id)
1547
 
        self.assertEqual('a@cset-0-1', revision_id)
 
1570
        self.assertEqual(b'a@cset-0-1', revision_id)
1548
1571
        self.assertEqual('inventory', repo_kind)
1549
 
        self.assertEqual({'parents': [],
1550
 
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
1551
 
                          'storage_kind': 'mpdiff',
1552
 
                         }, metadata)
 
1572
        self.assertEqual({b'parents': [],
 
1573
                          b'sha1': b'a13f42b142d544aac9b085c42595d304150e31a2',
 
1574
                          b'storage_kind': b'mpdiff',
 
1575
                          }, metadata)
1553
1576
        # We should have an mpdiff that takes some lines from both parents.
1554
1577
        self.assertEqualDiff(
1555
 
            'i 4\n'
1556
 
            '<inventory format="10" revision_id="a@cset-0-1">\n'
1557
 
            '<directory file_id="root-id" name=""'
1558
 
                ' revision="a@cset-0-1" />\n'
1559
 
            '<file file_id="file-id" name="file" parent_id="root-id"'
1560
 
                ' revision="a@cset-0-1"'
1561
 
                ' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
1562
 
                ' text_size="17" />\n'
1563
 
            '</inventory>\n'
1564
 
            '\n', bytes)
 
1578
            b'i 4\n'
 
1579
            b'<inventory format="10" revision_id="a@cset-0-1">\n'
 
1580
            b'<directory file_id="root-id" name=""'
 
1581
            b' revision="a@cset-0-1" />\n'
 
1582
            b'<file file_id="file-id" name="file" parent_id="root-id"'
 
1583
            b' revision="a@cset-0-1"'
 
1584
            b' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
 
1585
            b' text_size="17" />\n'
 
1586
            b'</inventory>\n'
 
1587
            b'\n', bytes)
1565
1588
 
1566
1589
    def test_multiple_inventories_as_xml(self):
1567
1590
        self.make_merged_branch()
1568
 
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1569
 
            ['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
 
1591
        sio = self.make_bundle_just_inventories(b'a@cset-0-1', b'a@cset-0-3',
 
1592
                                                [b'a@cset-0-2a', b'a@cset-0-2b', b'a@cset-0-3'])
1570
1593
        reader = v4.BundleReader(sio, stream_input=False)
1571
1594
        records = list(reader.iter_records())
1572
1595
        self.assertEqual(3, len(records))
1573
1596
        revision_ids = [rev_id for b, m, k, rev_id, f in records]
1574
 
        self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
 
1597
        self.assertEqual([b'a@cset-0-2a', b'a@cset-0-2b', b'a@cset-0-3'],
1575
1598
                         revision_ids)
1576
1599
        metadata_2a = records[0][1]
1577
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1578
 
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
1579
 
                          'storage_kind': 'mpdiff',
1580
 
                         }, metadata_2a)
 
1600
        self.assertEqual({b'parents': [b'a@cset-0-1'],
 
1601
                          b'sha1': b'1e105886d62d510763e22885eec733b66f5f09bf',
 
1602
                          b'storage_kind': b'mpdiff',
 
1603
                          }, metadata_2a)
1581
1604
        metadata_2b = records[1][1]
1582
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1583
 
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
1584
 
                          'storage_kind': 'mpdiff',
1585
 
                         }, metadata_2b)
 
1605
        self.assertEqual({b'parents': [b'a@cset-0-1'],
 
1606
                          b'sha1': b'f03f12574bdb5ed2204c28636c98a8547544ccd8',
 
1607
                          b'storage_kind': b'mpdiff',
 
1608
                          }, metadata_2b)
1586
1609
        metadata_3 = records[2][1]
1587
 
        self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1588
 
                          'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1589
 
                          'storage_kind': 'mpdiff',
1590
 
                         }, metadata_3)
 
1610
        self.assertEqual({b'parents': [b'a@cset-0-2a', b'a@cset-0-2b'],
 
1611
                          b'sha1': b'09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1612
                          b'storage_kind': b'mpdiff',
 
1613
                          }, metadata_3)
1591
1614
        bytes_2a = records[0][0]
1592
1615
        self.assertEqualDiff(
1593
 
            'i 1\n'
1594
 
            '<inventory format="10" revision_id="a@cset-0-2a">\n'
1595
 
            '\n'
1596
 
            'c 0 1 1 1\n'
1597
 
            'i 1\n'
1598
 
            '<file file_id="file-id" name="file" parent_id="root-id"'
1599
 
                ' revision="a@cset-0-2a"'
1600
 
                ' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
1601
 
                ' text_size="12" />\n'
1602
 
            '\n'
1603
 
            'c 0 3 3 1\n', bytes_2a)
 
1616
            b'i 1\n'
 
1617
            b'<inventory format="10" revision_id="a@cset-0-2a">\n'
 
1618
            b'\n'
 
1619
            b'c 0 1 1 1\n'
 
1620
            b'i 1\n'
 
1621
            b'<file file_id="file-id" name="file" parent_id="root-id"'
 
1622
            b' revision="a@cset-0-2a"'
 
1623
            b' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
 
1624
            b' text_size="12" />\n'
 
1625
            b'\n'
 
1626
            b'c 0 3 3 1\n', bytes_2a)
1604
1627
        bytes_2b = records[1][0]
1605
1628
        self.assertEqualDiff(
1606
 
            'i 1\n'
1607
 
            '<inventory format="10" revision_id="a@cset-0-2b">\n'
1608
 
            '\n'
1609
 
            'c 0 1 1 2\n'
1610
 
            'i 1\n'
1611
 
            '<file file_id="file2-id" name="other-file" parent_id="root-id"'
1612
 
                ' revision="a@cset-0-2b"'
1613
 
                ' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
1614
 
                ' text_size="14" />\n'
1615
 
            '\n'
1616
 
            'c 0 3 4 1\n', bytes_2b)
 
1629
            b'i 1\n'
 
1630
            b'<inventory format="10" revision_id="a@cset-0-2b">\n'
 
1631
            b'\n'
 
1632
            b'c 0 1 1 2\n'
 
1633
            b'i 1\n'
 
1634
            b'<file file_id="file2-id" name="other-file" parent_id="root-id"'
 
1635
            b' revision="a@cset-0-2b"'
 
1636
            b' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
 
1637
            b' text_size="14" />\n'
 
1638
            b'\n'
 
1639
            b'c 0 3 4 1\n', bytes_2b)
1617
1640
        bytes_3 = records[2][0]
1618
1641
        self.assertEqualDiff(
1619
 
            'i 1\n'
1620
 
            '<inventory format="10" revision_id="a@cset-0-3">\n'
1621
 
            '\n'
1622
 
            'c 0 1 1 2\n'
1623
 
            'c 1 3 3 2\n', bytes_3)
 
1642
            b'i 1\n'
 
1643
            b'<inventory format="10" revision_id="a@cset-0-3">\n'
 
1644
            b'\n'
 
1645
            b'c 0 1 1 2\n'
 
1646
            b'c 1 3 3 2\n', bytes_3)
1624
1647
 
1625
1648
    def test_creating_bundle_preserves_chk_pages(self):
1626
1649
        self.make_merged_branch()
1627
 
        target = self.b1.bzrdir.sprout('target',
1628
 
                                       revision_id='a@cset-0-2a').open_branch()
1629
 
        bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
1630
 
                                                      'a@cset-0-3')
1631
 
        self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
 
1650
        target = self.b1.controldir.sprout('target',
 
1651
                                           revision_id=b'a@cset-0-2a').open_branch()
 
1652
        bundle_txt, rev_ids = self.create_bundle_text(b'a@cset-0-2a',
 
1653
                                                      b'a@cset-0-3')
 
1654
        self.assertEqual(set([b'a@cset-0-2b', b'a@cset-0-3']), set(rev_ids))
1632
1655
        bundle = read_bundle(bundle_txt)
1633
1656
        target.lock_write()
1634
1657
        self.addCleanup(target.unlock)
1635
1658
        install_bundle(target.repository, bundle)
1636
 
        inv1 = self.b1.repository.inventories.get_record_stream([
1637
 
            ('a@cset-0-3',)], 'unordered',
1638
 
            True).next().get_bytes_as('fulltext')
1639
 
        inv2 = target.repository.inventories.get_record_stream([
1640
 
            ('a@cset-0-3',)], 'unordered',
1641
 
            True).next().get_bytes_as('fulltext')
 
1659
        inv1 = next(self.b1.repository.inventories.get_record_stream([
 
1660
            (b'a@cset-0-3',)], 'unordered',
 
1661
            True)).get_bytes_as('fulltext')
 
1662
        inv2 = next(target.repository.inventories.get_record_stream([
 
1663
            (b'a@cset-0-3',)], 'unordered',
 
1664
            True)).get_bytes_as('fulltext')
1642
1665
        self.assertEqualDiff(inv1, inv2)
1643
1666
 
1644
1667
 
1649
1672
 
1650
1673
        self.build_tree(['b1/one'])
1651
1674
        wt.add('one')
1652
 
        wt.commit('add one', rev_id='a@cset-0-1')
 
1675
        wt.commit('add one', rev_id=b'a@cset-0-1')
1653
1676
        self.build_tree(['b1/two'])
1654
1677
        wt.add('two')
1655
 
        wt.commit('add two', rev_id='a@cset-0-2',
1656
 
                  revprops={'branch-nick':'test'})
 
1678
        wt.commit('add two', rev_id=b'a@cset-0-2',
 
1679
                  revprops={u'branch-nick': 'test'})
1657
1680
 
1658
 
        bundle_txt = StringIO()
1659
 
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
1660
 
                               'a@cset-0-1', bundle_txt, self.format)
1661
 
        self.assertEqual(set(['a@cset-0-2']), set(rev_ids))
 
1681
        bundle_txt = BytesIO()
 
1682
        rev_ids = write_bundle(wt.branch.repository, b'a@cset-0-2',
 
1683
                               b'a@cset-0-1', bundle_txt, self.format)
 
1684
        self.assertEqual({b'a@cset-0-2'}, set(rev_ids))
1662
1685
        bundle_txt.seek(0, 0)
1663
1686
        return bundle_txt
1664
1687
 
1665
1688
    def check_valid(self, bundle):
1666
1689
        """Check that after whatever munging, the final object is valid."""
1667
 
        self.assertEqual(['a@cset-0-2'],
1668
 
            [r.revision_id for r in bundle.real_revisions])
 
1690
        self.assertEqual([b'a@cset-0-2'],
 
1691
                         [r.revision_id for r in bundle.real_revisions])
1669
1692
 
1670
1693
    def test_extra_whitespace(self):
1671
1694
        bundle_txt = self.build_test_bundle()
1674
1697
        # Adding one extra newline used to give us
1675
1698
        # TypeError: float() argument must be a string or a number
1676
1699
        bundle_txt.seek(0, 2)
1677
 
        bundle_txt.write('\n')
 
1700
        bundle_txt.write(b'\n')
1678
1701
        bundle_txt.seek(0)
1679
1702
 
1680
1703
        bundle = read_bundle(bundle_txt)
1687
1710
        # Adding two extra newlines used to give us
1688
1711
        # MalformedPatches: The first line of all patches should be ...
1689
1712
        bundle_txt.seek(0, 2)
1690
 
        bundle_txt.write('\n\n')
 
1713
        bundle_txt.write(b'\n\n')
1691
1714
        bundle_txt.seek(0)
1692
1715
 
1693
1716
        bundle = read_bundle(bundle_txt)
1707
1730
        # test is concerned with the exact case where the serializer
1708
1731
        # creates a blank line at the end, and fails if that
1709
1732
        # line is stripped
1710
 
        self.assertEqual('\n\n', raw[-2:])
1711
 
        bundle_txt = StringIO(raw[:-1])
 
1733
        self.assertEqual(b'\n\n', raw[-2:])
 
1734
        bundle_txt = BytesIO(raw[:-1])
1712
1735
 
1713
1736
        bundle = read_bundle(bundle_txt)
1714
1737
        self.check_valid(bundle)
1716
1739
    def test_opening_text(self):
1717
1740
        bundle_txt = self.build_test_bundle()
1718
1741
 
1719
 
        bundle_txt = StringIO("Some random\nemail comments\n"
1720
 
                              + bundle_txt.getvalue())
 
1742
        bundle_txt = BytesIO(
 
1743
            b"Some random\nemail comments\n" + bundle_txt.getvalue())
1721
1744
 
1722
1745
        bundle = read_bundle(bundle_txt)
1723
1746
        self.check_valid(bundle)
1725
1748
    def test_trailing_text(self):
1726
1749
        bundle_txt = self.build_test_bundle()
1727
1750
 
1728
 
        bundle_txt = StringIO(bundle_txt.getvalue() +
1729
 
                              "Some trailing\nrandom\ntext\n")
 
1751
        bundle_txt = BytesIO(
 
1752
            bundle_txt.getvalue() + b"Some trailing\nrandom\ntext\n")
1730
1753
 
1731
1754
        bundle = read_bundle(bundle_txt)
1732
1755
        self.check_valid(bundle)
1740
1763
class TestBundleWriterReader(tests.TestCase):
1741
1764
 
1742
1765
    def test_roundtrip_record(self):
1743
 
        fileobj = StringIO()
 
1766
        fileobj = BytesIO()
1744
1767
        writer = v4.BundleWriter(fileobj)
1745
1768
        writer.begin()
1746
 
        writer.add_info_record(foo='bar')
1747
 
        writer._add_record("Record body", {'parents': ['1', '3'],
1748
 
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1769
        writer.add_info_record({b'foo': b'bar'})
 
1770
        writer._add_record(b"Record body", {b'parents': [b'1', b'3'],
 
1771
                                            b'storage_kind': b'fulltext'}, 'file', b'revid', b'fileid')
1749
1772
        writer.end()
1750
1773
        fileobj.seek(0)
1751
1774
        reader = v4.BundleReader(fileobj, stream_input=True)
1752
1775
        record_iter = reader.iter_records()
1753
 
        record = record_iter.next()
1754
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1755
 
            'info', None, None), record)
1756
 
        record = record_iter.next()
1757
 
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1758
 
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1759
 
                          record)
 
1776
        record = next(record_iter)
 
1777
        self.assertEqual((None, {b'foo': b'bar', b'storage_kind': b'header'},
 
1778
                          'info', None, None), record)
 
1779
        record = next(record_iter)
 
1780
        self.assertEqual((b"Record body", {b'storage_kind': b'fulltext',
 
1781
                                           b'parents': [b'1', b'3']}, 'file', b'revid', b'fileid'),
 
1782
                         record)
1760
1783
 
1761
1784
    def test_roundtrip_record_memory_hungry(self):
1762
 
        fileobj = StringIO()
 
1785
        fileobj = BytesIO()
1763
1786
        writer = v4.BundleWriter(fileobj)
1764
1787
        writer.begin()
1765
 
        writer.add_info_record(foo='bar')
1766
 
        writer._add_record("Record body", {'parents': ['1', '3'],
1767
 
            'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
 
1788
        writer.add_info_record({b'foo': b'bar'})
 
1789
        writer._add_record(b"Record body", {b'parents': [b'1', b'3'],
 
1790
                                            b'storage_kind': b'fulltext'}, 'file', b'revid', b'fileid')
1768
1791
        writer.end()
1769
1792
        fileobj.seek(0)
1770
1793
        reader = v4.BundleReader(fileobj, stream_input=False)
1771
1794
        record_iter = reader.iter_records()
1772
 
        record = record_iter.next()
1773
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1774
 
            'info', None, None), record)
1775
 
        record = record_iter.next()
1776
 
        self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1777
 
                          'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1778
 
                          record)
 
1795
        record = next(record_iter)
 
1796
        self.assertEqual((None, {b'foo': b'bar', b'storage_kind': b'header'},
 
1797
                          'info', None, None), record)
 
1798
        record = next(record_iter)
 
1799
        self.assertEqual((b"Record body", {b'storage_kind': b'fulltext',
 
1800
                                           b'parents': [b'1', b'3']}, 'file', b'revid', b'fileid'),
 
1801
                         record)
1779
1802
 
1780
1803
    def test_encode_name(self):
1781
 
        self.assertEqual('revision/rev1',
1782
 
            v4.BundleWriter.encode_name('revision', 'rev1'))
1783
 
        self.assertEqual('file/rev//1/file-id-1',
1784
 
            v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
1785
 
        self.assertEqual('info',
1786
 
            v4.BundleWriter.encode_name('info', None, None))
 
1804
        self.assertEqual(b'revision/rev1',
 
1805
                         v4.BundleWriter.encode_name('revision', b'rev1'))
 
1806
        self.assertEqual(b'file/rev//1/file-id-1',
 
1807
                         v4.BundleWriter.encode_name('file', b'rev/1', b'file-id-1'))
 
1808
        self.assertEqual(b'info',
 
1809
                         v4.BundleWriter.encode_name('info', None, None))
1787
1810
 
1788
1811
    def test_decode_name(self):
1789
 
        self.assertEqual(('revision', 'rev1', None),
1790
 
            v4.BundleReader.decode_name('revision/rev1'))
1791
 
        self.assertEqual(('file', 'rev/1', 'file-id-1'),
1792
 
            v4.BundleReader.decode_name('file/rev//1/file-id-1'))
 
1812
        self.assertEqual(('revision', b'rev1', None),
 
1813
                         v4.BundleReader.decode_name(b'revision/rev1'))
 
1814
        self.assertEqual(('file', b'rev/1', b'file-id-1'),
 
1815
                         v4.BundleReader.decode_name(b'file/rev//1/file-id-1'))
1793
1816
        self.assertEqual(('info', None, None),
1794
 
                         v4.BundleReader.decode_name('info'))
 
1817
                         v4.BundleReader.decode_name(b'info'))
1795
1818
 
1796
1819
    def test_too_many_names(self):
1797
 
        fileobj = StringIO()
 
1820
        fileobj = BytesIO()
1798
1821
        writer = v4.BundleWriter(fileobj)
1799
1822
        writer.begin()
1800
 
        writer.add_info_record(foo='bar')
1801
 
        writer._container.add_bytes_record('blah', ['two', 'names'])
 
1823
        writer.add_info_record({b'foo': b'bar'})
 
1824
        writer._container.add_bytes_record(b'blah', [(b'two', ), (b'names', )])
1802
1825
        writer.end()
1803
1826
        fileobj.seek(0)
1804
1827
        record_iter = v4.BundleReader(fileobj).iter_records()
1805
 
        record = record_iter.next()
1806
 
        self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1807
 
            'info', None, None), record)
1808
 
        self.assertRaises(errors.BadBundle, record_iter.next)
1809
 
 
1810
 
 
1811
 
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
1812
 
 
1813
 
    def test_read_mergeable_skips_local(self):
1814
 
        """A local bundle named like the URL should not be read.
1815
 
        """
1816
 
        out, wt = test_read_bundle.create_bundle_file(self)
1817
 
        class FooService(object):
1818
 
            """A directory service that always returns source"""
1819
 
 
1820
 
            def look_up(self, name, url):
1821
 
                return 'source'
1822
 
        directories.register('foo:', FooService, 'Testing directory service')
1823
 
        self.addCleanup(directories.remove, 'foo:')
1824
 
        self.build_tree_contents([('./foo:bar', out.getvalue())])
1825
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1826
 
                          'foo:bar')
1827
 
 
1828
 
    def test_infinite_redirects_are_not_a_bundle(self):
1829
 
        """If a URL causes TooManyRedirections then NotABundle is raised.
1830
 
        """
1831
 
        from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
1832
 
        server = RedirectingMemoryServer()
1833
 
        self.start_server(server)
1834
 
        url = server.get_url() + 'infinite-loop'
1835
 
        self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
1836
 
 
1837
 
    def test_smart_server_connection_reset(self):
1838
 
        """If a smart server connection fails during the attempt to read a
1839
 
        bundle, then the ConnectionReset error should be propagated.
1840
 
        """
1841
 
        # Instantiate a server that will provoke a ConnectionReset
1842
 
        sock_server = _DisconnectingTCPServer()
1843
 
        self.start_server(sock_server)
1844
 
        # We don't really care what the url is since the server will close the
1845
 
        # connection without interpreting it
1846
 
        url = sock_server.get_url()
1847
 
        self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1848
 
 
1849
 
 
1850
 
class _DisconnectingTCPServer(object):
1851
 
    """A TCP server that immediately closes any connection made to it."""
1852
 
 
1853
 
    def start_server(self):
1854
 
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1855
 
        self.sock.bind(('127.0.0.1', 0))
1856
 
        self.sock.listen(1)
1857
 
        self.port = self.sock.getsockname()[1]
1858
 
        self.thread = threading.Thread(
1859
 
            name='%s (port %d)' % (self.__class__.__name__, self.port),
1860
 
            target=self.accept_and_close)
1861
 
        self.thread.start()
1862
 
 
1863
 
    def accept_and_close(self):
1864
 
        conn, addr = self.sock.accept()
1865
 
        conn.shutdown(socket.SHUT_RDWR)
1866
 
        conn.close()
1867
 
 
1868
 
    def get_url(self):
1869
 
        return 'bzr://127.0.0.1:%d/' % (self.port,)
1870
 
 
1871
 
    def stop_server(self):
1872
 
        try:
1873
 
            # make sure the thread dies by connecting to the listening socket,
1874
 
            # just in case the test failed to do so.
1875
 
            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1876
 
            conn.connect(self.sock.getsockname())
1877
 
            conn.close()
1878
 
        except socket.error:
1879
 
            pass
1880
 
        self.sock.close()
1881
 
        self.thread.join()
 
1828
        record = next(record_iter)
 
1829
        self.assertEqual((None, {b'foo': b'bar', b'storage_kind': b'header'},
 
1830
                          'info', None, None), record)
 
1831
        self.assertRaises(errors.BadBundle, next, record_iter)