/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-06-15 13:39:46 UTC
  • mto: This revision was merged to the branch mainline in revision 7342.
  • Revision ID: jelmer@jelmer.uk-20190615133946-uywh9ix0lfpqw0hy
Install quilt.

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 ..bundle.apply_bundle import install_bundle, merge_bundle
 
36
from ..bundle.bundle_data import BundleTree
 
37
from ..bundle.serializer import write_bundle, read_bundle, v09, v4
 
38
from ..bundle.serializer.v08 import BundleSerializerV08
 
39
from ..bundle.serializer.v09 import BundleSerializerV09
 
40
from ..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 ..transform import TreeTransform
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
            self.assertEqual(
 
591
                base_tree.get_file_size(path),
 
592
                to_tree.get_file_size(to_tree.id2path(entry.file_id)))
 
593
            self.assertEqual(
 
594
                base_tree.get_file_sha1(path, entry.file_id),
 
595
                to_tree.get_file_sha1(to_tree.id2path(entry.file_id)))
568
596
            # Check that the contents are the same
569
597
            # This is pretty expensive
570
598
            # self.assertEqual(base_tree.get_file(fileid).read(),
574
602
        self.tree1 = self.make_branch_and_tree('b1')
575
603
        self.b1 = self.tree1.branch
576
604
 
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')
 
605
        self.build_tree_contents([('b1/one', b'one\n')])
 
606
        self.tree1.add('one', b'one-id')
 
607
        self.tree1.set_root_id(b'root-id')
 
608
        self.tree1.commit('add one', rev_id=b'a@cset-0-1')
581
609
 
582
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-1')
 
610
        bundle = self.get_valid_bundle(b'null:', b'a@cset-0-1')
583
611
 
584
612
        # Make sure we can handle files with spaces, tabs, other
585
613
        # bogus characters
586
614
        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')])
 
615
            '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'
 
616
            ])
 
617
        self.build_tree_contents([('b1/sub/sub/emptyfile.txt', b''),
 
618
                                  ('b1/dir/nolastnewline.txt', b'bloop')])
598
619
        tt = TreeTransform(self.tree1)
599
 
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
 
620
        tt.new_file('executable', tt.root, [b'#!/bin/sh\n'], b'exe-1', True)
600
621
        tt.apply()
601
622
        # have to fix length of file-id so that we can predictably rewrite
602
623
        # a (length-prefixed) record containing it later.
603
 
        self.tree1.add('with space.txt', 'withspace-id')
 
624
        self.tree1.add('with space.txt', b'withspace-id')
604
625
        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')
 
626
            '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'
 
627
            ])
 
628
        self.tree1.commit('add whitespace', rev_id=b'a@cset-0-2')
616
629
 
617
 
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
 
630
        bundle = self.get_valid_bundle(b'a@cset-0-1', b'a@cset-0-2')
618
631
 
619
632
        # Check a rollup bundle
620
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-2')
 
633
        bundle = self.get_valid_bundle(b'null:', b'a@cset-0-2')
621
634
 
622
635
        # Now delete entries
623
636
        self.tree1.remove(
624
 
                ['sub/sub/nonempty.txt'
625
 
                , 'sub/sub/emptyfile.txt'
626
 
                , 'sub/sub'
627
 
                ])
 
637
            ['sub/sub/nonempty.txt', 'sub/sub/emptyfile.txt', 'sub/sub'
 
638
             ])
628
639
        tt = TreeTransform(self.tree1)
629
 
        trans_id = tt.trans_id_tree_file_id('exe-1')
 
640
        trans_id = tt.trans_id_tree_path('executable')
630
641
        tt.set_executability(False, trans_id)
631
642
        tt.apply()
632
 
        self.tree1.commit('removed', rev_id='a@cset-0-3')
 
643
        self.tree1.commit('removed', rev_id=b'a@cset-0-3')
633
644
 
634
 
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
 
645
        bundle = self.get_valid_bundle(b'a@cset-0-2', b'a@cset-0-3')
635
646
        self.assertRaises((errors.TestamentMismatch,
636
 
            errors.VersionedFileInvalidChecksum,
637
 
            errors.BadBundle), self.get_invalid_bundle,
638
 
            'a@cset-0-2', 'a@cset-0-3')
 
647
                           errors.VersionedFileInvalidChecksum,
 
648
                           errors.BadBundle), self.get_invalid_bundle,
 
649
                          b'a@cset-0-2', b'a@cset-0-3')
639
650
        # Check a rollup bundle
640
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-3')
 
651
        bundle = self.get_valid_bundle(b'null:', b'a@cset-0-3')
641
652
 
642
653
        # Now move the directory
643
654
        self.tree1.rename_one('dir', 'sub/dir')
644
 
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
 
655
        self.tree1.commit('rename dir', rev_id=b'a@cset-0-4')
645
656
 
646
 
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
 
657
        bundle = self.get_valid_bundle(b'a@cset-0-3', b'a@cset-0-4')
647
658
        # Check a rollup bundle
648
 
        bundle = self.get_valid_bundle('null:', 'a@cset-0-4')
 
659
        bundle = self.get_valid_bundle(b'null:', b'a@cset-0-4')
649
660
 
650
661
        # 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')
 
662
        with open('b1/sub/dir/WithCaps.txt', 'ab') as f:
 
663
            f.write(b'\nAdding some text\n')
 
664
        with open('b1/sub/dir/ pre space', 'ab') as f:
 
665
            f.write(
 
666
                b'\r\nAdding some\r\nDOS format lines\r\n')
 
667
        with open('b1/sub/dir/nolastnewline.txt', 'ab') as f:
 
668
            f.write(b'\n')
655
669
        self.tree1.rename_one('sub/dir/ pre space',
656
670
                              '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')
 
671
        self.tree1.commit('Modified files', rev_id=b'a@cset-0-5')
 
672
        bundle = self.get_valid_bundle(b'a@cset-0-4', b'a@cset-0-5')
659
673
 
660
674
        self.tree1.rename_one('sub/dir/WithCaps.txt', 'temp')
661
675
        self.tree1.rename_one('with space.txt', 'WithCaps.txt')
662
676
        self.tree1.rename_one('temp', 'with space.txt')
663
 
        self.tree1.commit(u'swap filenames', rev_id='a@cset-0-6',
 
677
        self.tree1.commit(u'swap filenames', rev_id=b'a@cset-0-6',
664
678
                          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')
 
679
        bundle = self.get_valid_bundle(b'a@cset-0-5', b'a@cset-0-6')
 
680
        other = self.get_checkout(b'a@cset-0-5')
667
681
        tree1_inv = get_inventory_text(self.tree1.branch.repository,
668
 
                                       'a@cset-0-5')
 
682
                                       b'a@cset-0-5')
669
683
        tree2_inv = get_inventory_text(other.branch.repository,
670
 
                                       'a@cset-0-5')
 
684
                                       b'a@cset-0-5')
671
685
        self.assertEqualDiff(tree1_inv, tree2_inv)
672
686
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
673
 
        other.commit('rename file', rev_id='a@cset-0-6b')
 
687
        other.commit('rename file', rev_id=b'a@cset-0-6b')
674
688
        self.tree1.merge_from_branch(other.branch)
675
 
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
 
689
        self.tree1.commit(u'Merge', rev_id=b'a@cset-0-7',
676
690
                          verbose=False)
677
 
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
 
691
        bundle = self.get_valid_bundle(b'a@cset-0-6', b'a@cset-0-7')
678
692
 
679
693
    def _test_symlink_bundle(self, link_name, link_target, new_link_target):
680
 
        link_id = 'link-1'
 
694
        link_id = b'link-1'
681
695
 
682
 
        self.requireFeature(tests.SymlinkFeature)
 
696
        self.requireFeature(features.SymlinkFeature)
683
697
        self.tree1 = self.make_branch_and_tree('b1')
684
698
        self.b1 = self.tree1.branch
685
699
 
686
700
        tt = TreeTransform(self.tree1)
687
701
        tt.new_symlink(link_name, tt.root, link_target, link_id)
688
702
        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:
 
703
        self.tree1.commit('add symlink', rev_id=b'l@cset-0-1')
 
704
        bundle = self.get_valid_bundle(b'null:', b'l@cset-0-1')
 
705
        if getattr(bundle, 'revision_tree', None) is not None:
692
706
            # 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))
 
707
            bund_tree = bundle.revision_tree(self.b1.repository, b'l@cset-0-1')
 
708
            self.assertEqual(
 
709
                link_target, bund_tree.get_symlink_target(link_name))
695
710
 
696
711
        tt = TreeTransform(self.tree1)
697
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
712
        trans_id = tt.trans_id_tree_path(link_name)
698
713
        tt.adjust_path('link2', tt.root, trans_id)
699
714
        tt.delete_contents(trans_id)
700
715
        tt.create_symlink(new_link_target, trans_id)
701
716
        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:
 
717
        self.tree1.commit('rename and change symlink', rev_id=b'l@cset-0-2')
 
718
        bundle = self.get_valid_bundle(b'l@cset-0-1', b'l@cset-0-2')
 
719
        if getattr(bundle, 'revision_tree', None) is not None:
705
720
            # Not all bundle formats supports revision_tree
706
 
            bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
 
721
            bund_tree = bundle.revision_tree(self.b1.repository, b'l@cset-0-2')
707
722
            self.assertEqual(new_link_target,
708
 
                             bund_tree.get_symlink_target(link_id))
 
723
                             bund_tree.get_symlink_target('link2'))
709
724
 
710
725
        tt = TreeTransform(self.tree1)
711
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
726
        trans_id = tt.trans_id_tree_path('link2')
712
727
        tt.delete_contents(trans_id)
713
728
        tt.create_symlink('jupiter', trans_id)
714
729
        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')
 
730
        self.tree1.commit('just change symlink target', rev_id=b'l@cset-0-3')
 
731
        bundle = self.get_valid_bundle(b'l@cset-0-2', b'l@cset-0-3')
717
732
 
718
733
        tt = TreeTransform(self.tree1)
719
 
        trans_id = tt.trans_id_tree_file_id(link_id)
 
734
        trans_id = tt.trans_id_tree_path('link2')
720
735
        tt.delete_contents(trans_id)
721
736
        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')
 
737
        self.tree1.commit('Delete symlink', rev_id=b'l@cset-0-4')
 
738
        bundle = self.get_valid_bundle(b'l@cset-0-3', b'l@cset-0-4')
724
739
 
725
740
    def test_symlink_bundle(self):
726
741
        self._test_symlink_bundle('link', 'bar/foo', 'mars')
727
742
 
728
743
    def test_unicode_symlink_bundle(self):
729
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
744
        self.requireFeature(features.UnicodeFilenameFeature)
730
745
        self._test_symlink_bundle(u'\N{Euro Sign}link',
731
746
                                  u'bar/\N{Euro Sign}foo',
732
747
                                  u'mars\N{Euro Sign}')
737
752
        tt = TreeTransform(self.tree1)
738
753
 
739
754
        # 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')
 
755
        tt.new_file('file', tt.root, [
 
756
                    b'\x00\n\x00\r\x01\n\x02\r\xff'], b'binary-1')
 
757
        tt.new_file('file2', tt.root, [b'\x01\n\x02\r\x03\n\x04\r\xff'],
 
758
                    b'binary-2')
743
759
        tt.apply()
744
 
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
745
 
        self.get_valid_bundle('null:', 'b@cset-0-1')
 
760
        self.tree1.commit('add binary', rev_id=b'b@cset-0-1')
 
761
        self.get_valid_bundle(b'null:', b'b@cset-0-1')
746
762
 
747
763
        # Delete
748
764
        tt = TreeTransform(self.tree1)
749
 
        trans_id = tt.trans_id_tree_file_id('binary-1')
 
765
        trans_id = tt.trans_id_tree_path('file')
750
766
        tt.delete_contents(trans_id)
751
767
        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')
 
768
        self.tree1.commit('delete binary', rev_id=b'b@cset-0-2')
 
769
        self.get_valid_bundle(b'b@cset-0-1', b'b@cset-0-2')
754
770
 
755
771
        # Rename & modify
756
772
        tt = TreeTransform(self.tree1)
757
 
        trans_id = tt.trans_id_tree_file_id('binary-2')
 
773
        trans_id = tt.trans_id_tree_path('file2')
758
774
        tt.adjust_path('file3', tt.root, trans_id)
759
775
        tt.delete_contents(trans_id)
760
 
        tt.create_file('file\rcontents\x00\n\x00', trans_id)
 
776
        tt.create_file([b'file\rcontents\x00\n\x00'], trans_id)
761
777
        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')
 
778
        self.tree1.commit('rename and modify binary', rev_id=b'b@cset-0-3')
 
779
        self.get_valid_bundle(b'b@cset-0-2', b'b@cset-0-3')
764
780
 
765
781
        # Modify
766
782
        tt = TreeTransform(self.tree1)
767
 
        trans_id = tt.trans_id_tree_file_id('binary-2')
 
783
        trans_id = tt.trans_id_tree_path('file3')
768
784
        tt.delete_contents(trans_id)
769
 
        tt.create_file('\x00file\rcontents', trans_id)
 
785
        tt.create_file([b'\x00file\rcontents'], trans_id)
770
786
        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')
 
787
        self.tree1.commit('just modify binary', rev_id=b'b@cset-0-4')
 
788
        self.get_valid_bundle(b'b@cset-0-3', b'b@cset-0-4')
773
789
 
774
790
        # Rollup
775
 
        self.get_valid_bundle('null:', 'b@cset-0-4')
 
791
        self.get_valid_bundle(b'null:', b'b@cset-0-4')
776
792
 
777
793
    def test_last_modified(self):
778
794
        self.tree1 = self.make_branch_and_tree('b1')
779
795
        self.b1 = self.tree1.branch
780
796
        tt = TreeTransform(self.tree1)
781
 
        tt.new_file('file', tt.root, 'file', 'file')
 
797
        tt.new_file('file', tt.root, [b'file'], b'file')
782
798
        tt.apply()
783
 
        self.tree1.commit('create file', rev_id='a@lmod-0-1')
 
799
        self.tree1.commit('create file', rev_id=b'a@lmod-0-1')
784
800
 
785
801
        tt = TreeTransform(self.tree1)
786
 
        trans_id = tt.trans_id_tree_file_id('file')
 
802
        trans_id = tt.trans_id_tree_path('file')
787
803
        tt.delete_contents(trans_id)
788
 
        tt.create_file('file2', trans_id)
 
804
        tt.create_file([b'file2'], trans_id)
789
805
        tt.apply()
790
 
        self.tree1.commit('modify text', rev_id='a@lmod-0-2a')
 
806
        self.tree1.commit('modify text', rev_id=b'a@lmod-0-2a')
791
807
 
792
 
        other = self.get_checkout('a@lmod-0-1')
 
808
        other = self.get_checkout(b'a@lmod-0-1')
793
809
        tt = TreeTransform(other)
794
 
        trans_id = tt.trans_id_tree_file_id('file')
 
810
        trans_id = tt.trans_id_tree_path('file2')
795
811
        tt.delete_contents(trans_id)
796
 
        tt.create_file('file2', trans_id)
 
812
        tt.create_file([b'file2'], trans_id)
797
813
        tt.apply()
798
 
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
 
814
        other.commit('modify text in another tree', rev_id=b'a@lmod-0-2b')
799
815
        self.tree1.merge_from_branch(other.branch)
800
 
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
 
816
        self.tree1.commit(u'Merge', rev_id=b'a@lmod-0-3',
801
817
                          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')
 
818
        self.tree1.commit(u'Merge', rev_id=b'a@lmod-0-4')
 
819
        bundle = self.get_valid_bundle(b'a@lmod-0-2a', b'a@lmod-0-4')
804
820
 
805
821
    def test_hide_history(self):
806
822
        self.tree1 = self.make_branch_and_tree('b1')
807
823
        self.b1 = self.tree1.branch
808
824
 
809
 
        open('b1/one', 'wb').write('one\n')
 
825
        with open('b1/one', 'wb') as f:
 
826
            f.write(b'one\n')
810
827
        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')
 
828
        self.tree1.commit('add file', rev_id=b'a@cset-0-1')
 
829
        with open('b1/one', 'wb') as f:
 
830
            f.write(b'two\n')
 
831
        self.tree1.commit('modify', rev_id=b'a@cset-0-2')
 
832
        with open('b1/one', 'wb') as f:
 
833
            f.write(b'three\n')
 
834
        self.tree1.commit('modify', rev_id=b'a@cset-0-3')
 
835
        bundle_file = BytesIO()
 
836
        rev_ids = write_bundle(self.tree1.branch.repository, b'a@cset-0-3',
 
837
                               b'a@cset-0-1', bundle_file, format=self.format)
 
838
        self.assertNotContainsRe(bundle_file.getvalue(), b'\btwo\b')
 
839
        self.assertContainsRe(self.get_raw(bundle_file), b'one')
 
840
        self.assertContainsRe(self.get_raw(bundle_file), b'three')
822
841
 
823
842
    def test_bundle_same_basis(self):
824
843
        """Ensure using the basis as the target doesn't cause an error"""
825
844
        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)
 
845
        self.tree1.commit('add file', rev_id=b'a@cset-0-1')
 
846
        bundle_file = BytesIO()
 
847
        rev_ids = write_bundle(self.tree1.branch.repository, b'a@cset-0-1',
 
848
                               b'a@cset-0-1', bundle_file)
830
849
 
831
850
    @staticmethod
832
851
    def get_raw(bundle_file):
833
852
        return bundle_file.getvalue()
834
853
 
835
854
    def test_unicode_bundle(self):
836
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
855
        self.requireFeature(features.UnicodeFilenameFeature)
837
856
        # Handle international characters
838
857
        os.mkdir('b1')
839
858
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
842
861
        self.b1 = self.tree1.branch
843
862
 
844
863
        f.write((u'A file\n'
845
 
            u'With international man of mystery\n'
846
 
            u'William Dod\xe9\n').encode('utf-8'))
 
864
                 u'With international man of mystery\n'
 
865
                 u'William Dod\xe9\n').encode('utf-8'))
847
866
        f.close()
848
867
 
849
 
        self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
 
868
        self.tree1.add([u'with Dod\N{Euro Sign}'], [b'withdod-id'])
850
869
        self.tree1.commit(u'i18n commit from William Dod\xe9',
851
 
                          rev_id='i18n-1', committer=u'William Dod\xe9')
 
870
                          rev_id=b'i18n-1', committer=u'William Dod\xe9')
852
871
 
853
872
        # Add
854
 
        bundle = self.get_valid_bundle('null:', 'i18n-1')
 
873
        bundle = self.get_valid_bundle(b'null:', b'i18n-1')
855
874
 
856
875
        # Modified
857
876
        f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
858
877
        f.write(u'Modified \xb5\n'.encode('utf8'))
859
878
        f.close()
860
 
        self.tree1.commit(u'modified', rev_id='i18n-2')
 
879
        self.tree1.commit(u'modified', rev_id=b'i18n-2')
861
880
 
862
 
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
 
881
        bundle = self.get_valid_bundle(b'i18n-1', b'i18n-2')
863
882
 
864
883
        # Renamed
865
884
        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',
 
885
        self.tree1.commit(u'renamed, the new i18n man', rev_id=b'i18n-3',
867
886
                          committer=u'Erik B\xe5gfors')
868
887
 
869
 
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
 
888
        bundle = self.get_valid_bundle(b'i18n-2', b'i18n-3')
870
889
 
871
890
        # Removed
872
891
        self.tree1.remove([u'B\N{Euro Sign}gfors'])
873
 
        self.tree1.commit(u'removed', rev_id='i18n-4')
 
892
        self.tree1.commit(u'removed', rev_id=b'i18n-4')
874
893
 
875
 
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
 
894
        bundle = self.get_valid_bundle(b'i18n-3', b'i18n-4')
876
895
 
877
896
        # Rollup
878
 
        bundle = self.get_valid_bundle('null:', 'i18n-4')
879
 
 
 
897
        bundle = self.get_valid_bundle(b'null:', b'i18n-4')
880
898
 
881
899
    def test_whitespace_bundle(self):
882
900
        if sys.platform in ('win32', 'cygwin'):
891
909
        #       once we actually support them
892
910
 
893
911
        # Added
894
 
        self.tree1.commit('funky whitespace', rev_id='white-1')
 
912
        self.tree1.commit('funky whitespace', rev_id=b'white-1')
895
913
 
896
 
        bundle = self.get_valid_bundle('null:', 'white-1')
 
914
        bundle = self.get_valid_bundle(b'null:', b'white-1')
897
915
 
898
916
        # Modified
899
 
        open('b1/trailing space ', 'ab').write('add some text\n')
900
 
        self.tree1.commit('add text', rev_id='white-2')
 
917
        with open('b1/trailing space ', 'ab') as f:
 
918
            f.write(b'add some text\n')
 
919
        self.tree1.commit('add text', rev_id=b'white-2')
901
920
 
902
 
        bundle = self.get_valid_bundle('white-1', 'white-2')
 
921
        bundle = self.get_valid_bundle(b'white-1', b'white-2')
903
922
 
904
923
        # Renamed
905
924
        self.tree1.rename_one('trailing space ', ' start and end space ')
906
 
        self.tree1.commit('rename', rev_id='white-3')
 
925
        self.tree1.commit('rename', rev_id=b'white-3')
907
926
 
908
 
        bundle = self.get_valid_bundle('white-2', 'white-3')
 
927
        bundle = self.get_valid_bundle(b'white-2', b'white-3')
909
928
 
910
929
        # Removed
911
930
        self.tree1.remove([' start and end space '])
912
 
        self.tree1.commit('removed', rev_id='white-4')
 
931
        self.tree1.commit('removed', rev_id=b'white-4')
913
932
 
914
 
        bundle = self.get_valid_bundle('white-3', 'white-4')
 
933
        bundle = self.get_valid_bundle(b'white-3', b'white-4')
915
934
 
916
935
        # Now test a complet roll-up
917
 
        bundle = self.get_valid_bundle('null:', 'white-4')
 
936
        bundle = self.get_valid_bundle(b'null:', b'white-4')
918
937
 
919
938
    def test_alt_timezone_bundle(self):
920
939
        self.tree1 = self.make_branch_and_memory_tree('b1')
927
946
        builder.finish_tree()
928
947
 
929
948
        # Asia/Colombo offset = 5 hours 30 minutes
930
 
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
 
949
        self.tree1.commit('non-hour offset timezone', rev_id=b'tz-1',
931
950
                          timezone=19800, timestamp=1152544886.0)
932
951
 
933
 
        bundle = self.get_valid_bundle('null:', 'tz-1')
 
952
        bundle = self.get_valid_bundle(b'null:', b'tz-1')
934
953
 
935
954
        rev = bundle.revisions[0]
936
955
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
941
960
    def test_bundle_root_id(self):
942
961
        self.tree1 = self.make_branch_and_tree('b1')
943
962
        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)
 
963
        self.tree1.commit('message', rev_id=b'revid1')
 
964
        bundle = self.get_valid_bundle(b'null:', b'revid1')
 
965
        tree = self.get_bundle_tree(bundle, b'revid1')
 
966
        root_revision = tree.get_file_revision(u'')
 
967
        self.assertEqual(b'revid1', root_revision)
948
968
 
949
969
    def test_install_revisions(self):
950
970
        self.tree1 = self.make_branch_and_tree('b1')
951
971
        self.b1 = self.tree1.branch
952
 
        self.tree1.commit('message', rev_id='rev2a')
953
 
        bundle = self.get_valid_bundle('null:', 'rev2a')
 
972
        self.tree1.commit('message', rev_id=b'rev2a')
 
973
        bundle = self.get_valid_bundle(b'null:', b'rev2a')
954
974
        branch2 = self.make_branch('b2')
955
 
        self.assertFalse(branch2.repository.has_revision('rev2a'))
 
975
        self.assertFalse(branch2.repository.has_revision(b'rev2a'))
956
976
        target_revision = bundle.install_revisions(branch2.repository)
957
 
        self.assertTrue(branch2.repository.has_revision('rev2a'))
958
 
        self.assertEqual('rev2a', target_revision)
 
977
        self.assertTrue(branch2.repository.has_revision(b'rev2a'))
 
978
        self.assertEqual(b'rev2a', target_revision)
959
979
 
960
980
    def test_bundle_empty_property(self):
961
981
        """Test serializing revision properties with an empty value."""
962
982
        tree = self.make_branch_and_memory_tree('tree')
963
983
        tree.lock_write()
964
984
        self.addCleanup(tree.unlock)
965
 
        tree.add([''], ['TREE_ROOT'])
966
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
985
        tree.add([''], [b'TREE_ROOT'])
 
986
        tree.commit('One', revprops={u'one': 'two',
 
987
                                     u'empty': ''}, rev_id=b'rev1')
967
988
        self.b1 = tree.branch
968
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
989
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
969
990
        bundle = read_bundle(bundle_sio)
970
991
        revision_info = bundle.revisions[0]
971
 
        self.assertEqual('rev1', revision_info.revision_id)
 
992
        self.assertEqual(b'rev1', revision_info.revision_id)
972
993
        rev = revision_info.as_revision()
973
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
994
        self.assertEqual({'branch-nick': 'tree', 'empty': '', 'one': 'two'},
974
995
                         rev.properties)
975
996
 
976
997
    def test_bundle_sorted_properties(self):
979
1000
        tree.lock_write()
980
1001
        self.addCleanup(tree.unlock)
981
1002
 
982
 
        tree.add([''], ['TREE_ROOT'])
983
 
        tree.commit('One', rev_id='rev1',
984
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
1003
        tree.add([''], [b'TREE_ROOT'])
 
1004
        tree.commit('One', rev_id=b'rev1',
 
1005
                    revprops={u'a': '4', u'b': '3', u'c': '2', u'd': '1'})
985
1006
        self.b1 = tree.branch
986
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1007
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
987
1008
        bundle = read_bundle(bundle_sio)
988
1009
        revision_info = bundle.revisions[0]
989
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1010
        self.assertEqual(b'rev1', revision_info.revision_id)
990
1011
        rev = revision_info.as_revision()
991
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
992
 
                          'd':'1'}, rev.properties)
 
1012
        self.assertEqual({'branch-nick': 'tree', 'a': '4', 'b': '3', 'c': '2',
 
1013
                          'd': '1'}, rev.properties)
993
1014
 
994
1015
    def test_bundle_unicode_properties(self):
995
1016
        """We should be able to round trip a non-ascii property."""
997
1018
        tree.lock_write()
998
1019
        self.addCleanup(tree.unlock)
999
1020
 
1000
 
        tree.add([''], ['TREE_ROOT'])
 
1021
        tree.add([''], [b'TREE_ROOT'])
1001
1022
        # Revisions themselves do not require anything about revision property
1002
1023
        # keys, other than that they are a basestring, and do not contain
1003
1024
        # whitespace.
1004
1025
        # However, Testaments assert than they are str(), and thus should not
1005
1026
        # be Unicode.
1006
 
        tree.commit('One', rev_id='rev1',
1007
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
1027
        tree.commit('One', rev_id=b'rev1',
 
1028
                    revprops={u'omega': u'\u03a9', u'alpha': u'\u03b1'})
1008
1029
        self.b1 = tree.branch
1009
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1030
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1010
1031
        bundle = read_bundle(bundle_sio)
1011
1032
        revision_info = bundle.revisions[0]
1012
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1033
        self.assertEqual(b'rev1', revision_info.revision_id)
1013
1034
        rev = revision_info.as_revision()
1014
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1015
 
                          'alpha':u'\u03b1'}, rev.properties)
 
1035
        self.assertEqual({'branch-nick': 'tree', 'omega': u'\u03a9',
 
1036
                          'alpha': u'\u03b1'}, rev.properties)
1016
1037
 
1017
1038
    def test_bundle_with_ghosts(self):
1018
1039
        tree = self.make_branch_and_tree('tree')
1019
1040
        self.b1 = tree.branch
1020
 
        self.build_tree_contents([('tree/file', 'content1')])
 
1041
        self.build_tree_contents([('tree/file', b'content1')])
1021
1042
        tree.add(['file'])
1022
1043
        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')
 
1044
        self.build_tree_contents([('tree/file', b'content2')])
 
1045
        tree.add_parent_tree_id(b'ghost')
 
1046
        tree.commit('rev2', rev_id=b'rev2')
 
1047
        bundle = self.get_valid_bundle(b'null:', b'rev2')
1027
1048
 
1028
1049
    def make_simple_tree(self, format=None):
1029
1050
        tree = self.make_branch_and_tree('b1', format=format)
1034
1055
 
1035
1056
    def test_across_serializers(self):
1036
1057
        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])
 
1058
        tree.commit('hello', rev_id=b'rev1')
 
1059
        tree.commit('hello', rev_id=b'rev2')
 
1060
        bundle = read_bundle(self.create_bundle_text(b'null:', b'rev2')[0])
1040
1061
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1041
1062
        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"')
 
1063
        inv_text = repo._get_inventory_xml(b'rev2')
 
1064
        self.assertNotContainsRe(inv_text, b'format="5"')
 
1065
        self.assertContainsRe(inv_text, b'format="7"')
1045
1066
 
1046
1067
    def make_repo_with_installed_revisions(self):
1047
1068
        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])
 
1069
        tree.commit('hello', rev_id=b'rev1')
 
1070
        tree.commit('hello', rev_id=b'rev2')
 
1071
        bundle = read_bundle(self.create_bundle_text(b'null:', b'rev2')[0])
1051
1072
        repo = self.make_repository('repo', format='dirstate-with-subtree')
1052
1073
        bundle.install_revisions(repo)
1053
1074
        return repo
1054
1075
 
1055
1076
    def test_across_models(self):
1056
1077
        repo = self.make_repo_with_installed_revisions()
1057
 
        inv = repo.get_inventory('rev2')
1058
 
        self.assertEqual('rev2', inv.root.revision)
 
1078
        inv = repo.get_inventory(b'rev2')
 
1079
        self.assertEqual(b'rev2', inv.root.revision)
1059
1080
        root_id = inv.root.file_id
1060
1081
        repo.lock_read()
1061
1082
        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')]))
 
1083
        self.assertEqual({(root_id, b'rev1'): (),
 
1084
                          (root_id, b'rev2'): ((root_id, b'rev1'),)},
 
1085
                         repo.texts.get_parent_map([(root_id, b'rev1'), (root_id, b'rev2')]))
1065
1086
 
1066
1087
    def test_inv_hash_across_serializers(self):
1067
1088
        repo = self.make_repo_with_installed_revisions()
1068
 
        recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
1069
 
        xml = repo._get_inventory_xml('rev2')
 
1089
        recorded_inv_sha1 = repo.get_revision(b'rev2').inventory_sha1
 
1090
        xml = repo._get_inventory_xml(b'rev2')
1070
1091
        self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1071
1092
 
1072
1093
    def test_across_models_incompatible(self):
1073
1094
        tree = self.make_simple_tree('dirstate-with-subtree')
1074
 
        tree.commit('hello', rev_id='rev1')
1075
 
        tree.commit('hello', rev_id='rev2')
 
1095
        tree.commit('hello', rev_id=b'rev1')
 
1096
        tree.commit('hello', rev_id=b'rev2')
1076
1097
        try:
1077
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1098
            bundle = read_bundle(self.create_bundle_text(b'null:', b'rev1')[0])
1078
1099
        except errors.IncompatibleBundleFormat:
1079
1100
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1080
1101
        repo = self.make_repository('repo', format='knit')
1081
1102
        bundle.install_revisions(repo)
1082
1103
 
1083
 
        bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
 
1104
        bundle = read_bundle(self.create_bundle_text(b'null:', b'rev2')[0])
1084
1105
        self.assertRaises(errors.IncompatibleRevision,
1085
1106
                          bundle.install_revisions, repo)
1086
1107
 
1087
1108
    def test_get_merge_request(self):
1088
1109
        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])
 
1110
        tree.commit('hello', rev_id=b'rev1')
 
1111
        tree.commit('hello', rev_id=b'rev2')
 
1112
        bundle = read_bundle(self.create_bundle_text(b'null:', b'rev1')[0])
1092
1113
        result = bundle.get_merge_request(tree.branch.repository)
1093
 
        self.assertEqual((None, 'rev1', 'inapplicable'), result)
 
1114
        self.assertEqual((None, b'rev1', 'inapplicable'), result)
1094
1115
 
1095
1116
    def test_with_subtree(self):
1096
1117
        tree = self.make_branch_and_tree('tree',
1099
1120
        subtree = self.make_branch_and_tree('tree/subtree',
1100
1121
                                            format='dirstate-with-subtree')
1101
1122
        tree.add('subtree')
1102
 
        tree.commit('hello', rev_id='rev1')
 
1123
        tree.commit('hello', rev_id=b'rev1')
1103
1124
        try:
1104
 
            bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
 
1125
            bundle = read_bundle(self.create_bundle_text(b'null:', b'rev1')[0])
1105
1126
        except errors.IncompatibleBundleFormat:
1106
1127
            raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1107
1128
        if isinstance(bundle, v09.BundleInfo09):
1116
1137
        self.tree1 = self.make_branch_and_tree('tree')
1117
1138
        self.b1 = self.tree1.branch
1118
1139
        try:
1119
 
            self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
 
1140
            self.tree1.commit('Revision/id/with/slashes', rev_id=b'rev/id')
1120
1141
        except ValueError:
1121
1142
            raise tests.TestSkipped(
1122
1143
                "Repository doesn't support revision ids with slashes")
1123
 
        bundle = self.get_valid_bundle('null:', 'rev/id')
 
1144
        bundle = self.get_valid_bundle(b'null:', b'rev/id')
1124
1145
 
1125
1146
    def test_skip_file(self):
1126
1147
        """Make sure we don't accidentally write to the wrong versionedfile"""
1127
1148
        self.tree1 = self.make_branch_and_tree('tree')
1128
1149
        self.b1 = self.tree1.branch
1129
1150
        # 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')])
 
1151
        self.build_tree_contents([('tree/file2', b'contents1')])
 
1152
        self.tree1.add('file2', b'file2-id')
 
1153
        self.tree1.commit('rev1', rev_id=b'reva')
 
1154
        self.build_tree_contents([('tree/file3', b'contents2')])
1134
1155
        # rev2 is present in bundle, and done by fetch
1135
1156
        # having file1 in the bunle causes file1's versionedfile to be opened.
1136
 
        self.tree1.add('file3', 'file3-id')
1137
 
        self.tree1.commit('rev2')
 
1157
        self.tree1.add('file3', b'file3-id')
 
1158
        rev2 = self.tree1.commit('rev2')
1138
1159
        # 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')
 
1160
        target = self.tree1.controldir.sprout('target').open_workingtree()
 
1161
        self.build_tree_contents([('tree/file2', b'contents3')])
 
1162
        self.tree1.commit('rev3', rev_id=b'rev3')
 
1163
        bundle = self.get_valid_bundle(b'reva', b'rev3')
1143
1164
        if getattr(bundle, 'get_bundle_reader', None) is None:
1144
1165
            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')
 
1166
        file_ids = set(
 
1167
            (f, r) for b, m, k, r, f in bundle.get_bundle_reader().iter_records()
 
1168
            if f is not None)
 
1169
        self.assertEqual(
 
1170
            {(b'file2-id', b'rev3'), (b'file3-id', rev2)}, file_ids)
1150
1171
        bundle.install_revisions(target.branch.repository)
1151
1172
 
1152
1173
 
1159
1180
        tree = self.make_branch_and_memory_tree('tree')
1160
1181
        tree.lock_write()
1161
1182
        self.addCleanup(tree.unlock)
1162
 
        tree.add([''], ['TREE_ROOT'])
1163
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1183
        tree.add([''], [b'TREE_ROOT'])
 
1184
        tree.commit('One', revprops={u'one': 'two',
 
1185
                                     u'empty': ''}, rev_id=b'rev1')
1164
1186
        self.b1 = tree.branch
1165
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1187
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1166
1188
        self.assertContainsRe(bundle_sio.getvalue(),
1167
 
                              '# properties:\n'
1168
 
                              '#   branch-nick: tree\n'
1169
 
                              '#   empty: \n'
1170
 
                              '#   one: two\n'
1171
 
                             )
 
1189
                              b'# properties:\n'
 
1190
                              b'#   branch-nick: tree\n'
 
1191
                              b'#   empty: \n'
 
1192
                              b'#   one: two\n'
 
1193
                              )
1172
1194
        bundle = read_bundle(bundle_sio)
1173
1195
        revision_info = bundle.revisions[0]
1174
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1196
        self.assertEqual(b'rev1', revision_info.revision_id)
1175
1197
        rev = revision_info.as_revision()
1176
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1198
        self.assertEqual({'branch-nick': 'tree', 'empty': '', 'one': 'two'},
1177
1199
                         rev.properties)
1178
1200
 
1179
1201
    def get_bundle_tree(self, bundle, revision_id):
1180
1202
        repository = self.make_repository('repo')
1181
 
        return bundle.revision_tree(repository, 'revid1')
 
1203
        return bundle.revision_tree(repository, b'revid1')
1182
1204
 
1183
1205
    def test_bundle_empty_property_alt(self):
1184
1206
        """Test serializing revision properties with an empty value.
1191
1213
        tree = self.make_branch_and_memory_tree('tree')
1192
1214
        tree.lock_write()
1193
1215
        self.addCleanup(tree.unlock)
1194
 
        tree.add([''], ['TREE_ROOT'])
1195
 
        tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
 
1216
        tree.add([''], [b'TREE_ROOT'])
 
1217
        tree.commit('One', revprops={u'one': 'two',
 
1218
                                     u'empty': ''}, rev_id=b'rev1')
1196
1219
        self.b1 = tree.branch
1197
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1220
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1198
1221
        txt = bundle_sio.getvalue()
1199
 
        loc = txt.find('#   empty: ') + len('#   empty:')
 
1222
        loc = txt.find(b'#   empty: ') + len(b'#   empty:')
1200
1223
        # Create a new bundle, which strips the trailing space after empty
1201
 
        bundle_sio = StringIO(txt[:loc] + txt[loc+1:])
 
1224
        bundle_sio = BytesIO(txt[:loc] + txt[loc + 1:])
1202
1225
 
1203
1226
        self.assertContainsRe(bundle_sio.getvalue(),
1204
 
                              '# properties:\n'
1205
 
                              '#   branch-nick: tree\n'
1206
 
                              '#   empty:\n'
1207
 
                              '#   one: two\n'
1208
 
                             )
 
1227
                              b'# properties:\n'
 
1228
                              b'#   branch-nick: tree\n'
 
1229
                              b'#   empty:\n'
 
1230
                              b'#   one: two\n'
 
1231
                              )
1209
1232
        bundle = read_bundle(bundle_sio)
1210
1233
        revision_info = bundle.revisions[0]
1211
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1234
        self.assertEqual(b'rev1', revision_info.revision_id)
1212
1235
        rev = revision_info.as_revision()
1213
 
        self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
 
1236
        self.assertEqual({'branch-nick': 'tree', 'empty': '', 'one': 'two'},
1214
1237
                         rev.properties)
1215
1238
 
1216
1239
    def test_bundle_sorted_properties(self):
1219
1242
        tree.lock_write()
1220
1243
        self.addCleanup(tree.unlock)
1221
1244
 
1222
 
        tree.add([''], ['TREE_ROOT'])
1223
 
        tree.commit('One', rev_id='rev1',
1224
 
                    revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
 
1245
        tree.add([''], [b'TREE_ROOT'])
 
1246
        tree.commit('One', rev_id=b'rev1',
 
1247
                    revprops={u'a': '4', u'b': '3', u'c': '2', u'd': '1'})
1225
1248
        self.b1 = tree.branch
1226
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1249
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1227
1250
        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
 
                             )
 
1251
                              b'# properties:\n'
 
1252
                              b'#   a: 4\n'
 
1253
                              b'#   b: 3\n'
 
1254
                              b'#   branch-nick: tree\n'
 
1255
                              b'#   c: 2\n'
 
1256
                              b'#   d: 1\n'
 
1257
                              )
1235
1258
        bundle = read_bundle(bundle_sio)
1236
1259
        revision_info = bundle.revisions[0]
1237
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1260
        self.assertEqual(b'rev1', revision_info.revision_id)
1238
1261
        rev = revision_info.as_revision()
1239
 
        self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
1240
 
                          'd':'1'}, rev.properties)
 
1262
        self.assertEqual({'branch-nick': 'tree', 'a': '4', 'b': '3', 'c': '2',
 
1263
                          'd': '1'}, rev.properties)
1241
1264
 
1242
1265
    def test_bundle_unicode_properties(self):
1243
1266
        """We should be able to round trip a non-ascii property."""
1245
1268
        tree.lock_write()
1246
1269
        self.addCleanup(tree.unlock)
1247
1270
 
1248
 
        tree.add([''], ['TREE_ROOT'])
 
1271
        tree.add([''], [b'TREE_ROOT'])
1249
1272
        # Revisions themselves do not require anything about revision property
1250
1273
        # keys, other than that they are a basestring, and do not contain
1251
1274
        # whitespace.
1252
1275
        # However, Testaments assert than they are str(), and thus should not
1253
1276
        # be Unicode.
1254
 
        tree.commit('One', rev_id='rev1',
1255
 
                    revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
 
1277
        tree.commit('One', rev_id=b'rev1',
 
1278
                    revprops={u'omega': u'\u03a9', u'alpha': u'\u03b1'})
1256
1279
        self.b1 = tree.branch
1257
 
        bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
 
1280
        bundle_sio, revision_ids = self.create_bundle_text(b'null:', b'rev1')
1258
1281
        self.assertContainsRe(bundle_sio.getvalue(),
1259
 
                              '# properties:\n'
1260
 
                              '#   alpha: \xce\xb1\n'
1261
 
                              '#   branch-nick: tree\n'
1262
 
                              '#   omega: \xce\xa9\n'
1263
 
                             )
 
1282
                              b'# properties:\n'
 
1283
                              b'#   alpha: \xce\xb1\n'
 
1284
                              b'#   branch-nick: tree\n'
 
1285
                              b'#   omega: \xce\xa9\n'
 
1286
                              )
1264
1287
        bundle = read_bundle(bundle_sio)
1265
1288
        revision_info = bundle.revisions[0]
1266
 
        self.assertEqual('rev1', revision_info.revision_id)
 
1289
        self.assertEqual(b'rev1', revision_info.revision_id)
1267
1290
        rev = revision_info.as_revision()
1268
 
        self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1269
 
                          'alpha':u'\u03b1'}, rev.properties)
 
1291
        self.assertEqual({'branch-nick': 'tree', 'omega': u'\u03a9',
 
1292
                          'alpha': u'\u03b1'}, rev.properties)
1270
1293
 
1271
1294
 
1272
1295
class V09BundleKnit2Tester(V08BundleTester):
1319
1342
            self.assertEqual(len(branch_rev.parent_ids),
1320
1343
                             len(bundle_rev.parent_ids))
1321
1344
        self.assertEqual(set(rev_ids),
1322
 
                         set([r.revision_id for r in bundle.real_revisions]))
 
1345
                         {r.revision_id for r in bundle.real_revisions})
1323
1346
        self.valid_apply_bundle(base_rev_id, bundle,
1324
 
                                   checkout_dir=checkout_dir)
 
1347
                                checkout_dir=checkout_dir)
1325
1348
 
1326
1349
        return bundle
1327
1350
 
1331
1354
 
1332
1355
        :return: The in-memory bundle
1333
1356
        """
1334
 
        from bzrlib.bundle import serializer
 
1357
        from ..bundle import serializer
1335
1358
        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()
 
1359
        new_text = self.get_raw(BytesIO(b''.join(bundle_txt)))
 
1360
        new_text = new_text.replace(b'<file file_id="exe-1"',
 
1361
                                    b'<file executable="y" file_id="exe-1"')
 
1362
        new_text = new_text.replace(b'B260', b'B275')
 
1363
        bundle_txt = BytesIO()
1341
1364
        bundle_txt.write(serializer._get_bundle_header('4'))
1342
 
        bundle_txt.write('\n')
1343
 
        bundle_txt.write(new_text.encode('bz2'))
 
1365
        bundle_txt.write(b'\n')
 
1366
        bundle_txt.write(bz2.compress(new_text))
1344
1367
        bundle_txt.seek(0)
1345
1368
        bundle = read_bundle(bundle_txt)
1346
1369
        self.valid_apply_bundle(base_rev_id, bundle)
1347
1370
        return bundle
1348
1371
 
1349
1372
    def create_bundle_text(self, base_rev_id, rev_id):
1350
 
        bundle_txt = StringIO()
 
1373
        bundle_txt = BytesIO()
1351
1374
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1352
1375
                               bundle_txt, format=self.format)
1353
1376
        bundle_txt.seek(0)
1354
1377
        self.assertEqual(bundle_txt.readline(),
1355
 
                         '# Bazaar revision bundle v%s\n' % self.format)
1356
 
        self.assertEqual(bundle_txt.readline(), '#\n')
 
1378
                         b'# Bazaar revision bundle v%s\n' % self.format.encode('ascii'))
 
1379
        self.assertEqual(bundle_txt.readline(), b'#\n')
1357
1380
        rev = self.b1.repository.get_revision(rev_id)
1358
1381
        bundle_txt.seek(0)
1359
1382
        return bundle_txt, rev_ids
1365
1388
 
1366
1389
    def test_creation(self):
1367
1390
        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()
 
1391
        self.build_tree_contents([('tree/file', b'contents1\nstatic\n')])
 
1392
        tree.add('file', b'fileid-2')
 
1393
        tree.commit('added file', rev_id=b'rev1')
 
1394
        self.build_tree_contents([('tree/file', b'contents2\nstatic\n')])
 
1395
        tree.commit('changed file', rev_id=b'rev2')
 
1396
        s = BytesIO()
1374
1397
        serializer = BundleSerializerV4('1.0')
1375
 
        serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
 
1398
        with tree.lock_read():
 
1399
            serializer.write_bundle(
 
1400
                tree.branch.repository, b'rev2', b'null:', s)
1376
1401
        s.seek(0)
1377
1402
        tree2 = self.make_branch_and_tree('target')
1378
1403
        target_repo = tree2.branch.repository
1380
1405
        target_repo.lock_read()
1381
1406
        self.addCleanup(target_repo.unlock)
1382
1407
        # Turn the 'iterators_of_bytes' back into simple strings for comparison
1383
 
        repo_texts = dict((i, ''.join(content)) for i, content
 
1408
        repo_texts = dict((i, b''.join(content)) for i, content
1384
1409
                          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'},
 
1410
            [(b'fileid-2', b'rev1', '1'),
 
1411
             (b'fileid-2', b'rev2', '2')]))
 
1412
        self.assertEqual({'1': b'contents1\nstatic\n',
 
1413
                          '2': b'contents2\nstatic\n'},
1389
1414
                         repo_texts)
1390
 
        rtree = target_repo.revision_tree('rev2')
 
1415
        rtree = target_repo.revision_tree(b'rev2')
1391
1416
        inventory_vf = target_repo.inventories
1392
1417
        # If the inventory store has a graph, it must match the revision graph.
1393
1418
        self.assertSubset(
1394
 
            [inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
1395
 
            [None, (('rev1',),)])
 
1419
            [inventory_vf.get_parent_map([(b'rev2',)])[(b'rev2',)]],
 
1420
            [None, ((b'rev1',),)])
1396
1421
        self.assertEqual('changed file',
1397
 
                         target_repo.get_revision('rev2').message)
 
1422
                         target_repo.get_revision(b'rev2').message)
1398
1423
 
1399
1424
    @staticmethod
1400
1425
    def get_raw(bundle_file):
1402
1427
        line = bundle_file.readline()
1403
1428
        line = bundle_file.readline()
1404
1429
        lines = bundle_file.readlines()
1405
 
        return ''.join(lines).decode('bz2')
 
1430
        return bz2.decompress(b''.join(lines))
1406
1431
 
1407
1432
    def test_copy_signatures(self):
1408
1433
        tree_a = self.make_branch_and_tree('tree_a')
1409
 
        import bzrlib.gpg
1410
 
        import bzrlib.commit as commit
1411
 
        oldstrategy = bzrlib.gpg.GPGStrategy
 
1434
        import breezy.gpg
 
1435
        import breezy.commit as commit
 
1436
        oldstrategy = breezy.gpg.GPGStrategy
1412
1437
        branch = tree_a.branch
1413
1438
        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'))
 
1439
        tree_a.commit("base", allow_pointless=True, rev_id=b'A')
 
1440
        self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
1416
1441
        try:
1417
 
            from bzrlib.testament import Testament
 
1442
            from ..bzr.testament import Testament
1418
1443
            # 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)
 
1444
            breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
 
1445
            new_config = test_commit.MustSignConfig()
 
1446
            commit.Commit(config_stack=new_config).commit(message="base",
 
1447
                                                          allow_pointless=True,
 
1448
                                                          rev_id=b'B',
 
1449
                                                          working_tree=tree_a)
 
1450
 
1425
1451
            def sign(text):
1426
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
1427
 
            self.assertTrue(repo_a.has_signature_for_revision_id('B'))
 
1452
                return breezy.gpg.LoopbackGPGStrategy(None).sign(text)
 
1453
            self.assertTrue(repo_a.has_signature_for_revision_id(b'B'))
1428
1454
        finally:
1429
 
            bzrlib.gpg.GPGStrategy = oldstrategy
 
1455
            breezy.gpg.GPGStrategy = oldstrategy
1430
1456
        tree_b = self.make_branch_and_tree('tree_b')
1431
1457
        repo_b = tree_b.branch.repository
1432
 
        s = StringIO()
 
1458
        s = BytesIO()
1433
1459
        serializer = BundleSerializerV4('4')
1434
 
        serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
 
1460
        with tree_a.lock_read():
 
1461
            serializer.write_bundle(
 
1462
                tree_a.branch.repository, b'B', b'null:', s)
1435
1463
        s.seek(0)
1436
1464
        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'))
 
1465
        self.assertTrue(repo_b.has_signature_for_revision_id(b'B'))
 
1466
        self.assertEqual(repo_b.get_signature_text(b'B'),
 
1467
                         repo_a.get_signature_text(b'B'))
1440
1468
        s.seek(0)
1441
1469
        # ensure repeat installs are harmless
1442
1470
        install_bundle(repo_b, serializer.read(s))
1443
1471
 
1444
1472
 
1445
 
class V4WeaveBundleTester(V4BundleTester):
1446
 
 
1447
 
    def bzrdir_format(self):
1448
 
        return 'metaweave'
1449
 
 
1450
 
 
1451
1473
class V4_2aBundleTester(V4BundleTester):
1452
1474
 
1453
1475
    def bzrdir_format(self):
1459
1481
 
1460
1482
        :return: The in-memory bundle
1461
1483
        """
1462
 
        from bzrlib.bundle import serializer
 
1484
        from ..bundle import serializer
1463
1485
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1464
 
        new_text = self.get_raw(StringIO(''.join(bundle_txt)))
 
1486
        new_text = self.get_raw(BytesIO(b''.join(bundle_txt)))
1465
1487
        # We are going to be replacing some text to set the executable bit on a
1466
1488
        # 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()
 
1489
        self.assertContainsRe(new_text, b'(?m)B244\n\ni 1\n<inventory')
 
1490
        new_text = new_text.replace(b'<file file_id="exe-1"',
 
1491
                                    b'<file executable="y" file_id="exe-1"')
 
1492
        new_text = new_text.replace(b'B244', b'B259')
 
1493
        bundle_txt = BytesIO()
1472
1494
        bundle_txt.write(serializer._get_bundle_header('4'))
1473
 
        bundle_txt.write('\n')
1474
 
        bundle_txt.write(new_text.encode('bz2'))
 
1495
        bundle_txt.write(b'\n')
 
1496
        bundle_txt.write(bz2.compress(new_text))
1475
1497
        bundle_txt.seek(0)
1476
1498
        bundle = read_bundle(bundle_txt)
1477
1499
        self.valid_apply_bundle(base_rev_id, bundle)
1480
1502
    def make_merged_branch(self):
1481
1503
        builder = self.make_branch_builder('source')
1482
1504
        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
 
            ])
 
1505
        builder.build_snapshot(None, [
 
1506
            ('add', ('', b'root-id', 'directory', None)),
 
1507
            ('add', ('file', b'file-id', 'file', b'original content\n')),
 
1508
            ], revision_id=b'a@cset-0-1')
 
1509
        builder.build_snapshot([b'a@cset-0-1'], [
 
1510
            ('modify', ('file', b'new-content\n')),
 
1511
            ], revision_id=b'a@cset-0-2a')
 
1512
        builder.build_snapshot([b'a@cset-0-1'], [
 
1513
            ('add', ('other-file', b'file2-id', 'file', b'file2-content\n')),
 
1514
            ], revision_id=b'a@cset-0-2b')
 
1515
        builder.build_snapshot([b'a@cset-0-2a', b'a@cset-0-2b'], [
 
1516
            ('add', ('other-file', b'file2-id', 'file', b'file2-content\n')),
 
1517
            ], revision_id=b'a@cset-0-3')
1496
1518
        builder.finish_series()
1497
1519
        self.b1 = builder.get_branch()
1498
1520
        self.b1.lock_read()
1501
1523
    def make_bundle_just_inventories(self, base_revision_id,
1502
1524
                                     target_revision_id,
1503
1525
                                     revision_ids):
1504
 
        sio = StringIO()
 
1526
        sio = BytesIO()
1505
1527
        writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
1506
1528
                                         self.b1.repository, sio)
1507
1529
        writer.bundle.begin()
1512
1534
 
1513
1535
    def test_single_inventory_multiple_parents_as_xml(self):
1514
1536
        self.make_merged_branch()
1515
 
        sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1516
 
                                                ['a@cset-0-3'])
 
1537
        sio = self.make_bundle_just_inventories(b'a@cset-0-1', b'a@cset-0-3',
 
1538
                                                [b'a@cset-0-3'])
1517
1539
        reader = v4.BundleReader(sio, stream_input=False)
1518
1540
        records = list(reader.iter_records())
1519
1541
        self.assertEqual(1, len(records))
1520
1542
        (bytes, metadata, repo_kind, revision_id,
1521
1543
         file_id) = records[0]
1522
1544
        self.assertIs(None, file_id)
1523
 
        self.assertEqual('a@cset-0-3', revision_id)
 
1545
        self.assertEqual(b'a@cset-0-3', revision_id)
1524
1546
        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)
 
1547
        self.assertEqual({b'parents': [b'a@cset-0-2a', b'a@cset-0-2b'],
 
1548
                          b'sha1': b'09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1549
                          b'storage_kind': b'mpdiff',
 
1550
                          }, metadata)
1529
1551
        # We should have an mpdiff that takes some lines from both parents.
1530
1552
        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)
 
1553
            b'i 1\n'
 
1554
            b'<inventory format="10" revision_id="a@cset-0-3">\n'
 
1555
            b'\n'
 
1556
            b'c 0 1 1 2\n'
 
1557
            b'c 1 3 3 2\n', bytes)
1536
1558
 
1537
1559
    def test_single_inv_no_parents_as_xml(self):
1538
1560
        self.make_merged_branch()
1539
 
        sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
1540
 
                                                ['a@cset-0-1'])
 
1561
        sio = self.make_bundle_just_inventories(b'null:', b'a@cset-0-1',
 
1562
                                                [b'a@cset-0-1'])
1541
1563
        reader = v4.BundleReader(sio, stream_input=False)
1542
1564
        records = list(reader.iter_records())
1543
1565
        self.assertEqual(1, len(records))
1544
1566
        (bytes, metadata, repo_kind, revision_id,
1545
1567
         file_id) = records[0]
1546
1568
        self.assertIs(None, file_id)
1547
 
        self.assertEqual('a@cset-0-1', revision_id)
 
1569
        self.assertEqual(b'a@cset-0-1', revision_id)
1548
1570
        self.assertEqual('inventory', repo_kind)
1549
 
        self.assertEqual({'parents': [],
1550
 
                          'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
1551
 
                          'storage_kind': 'mpdiff',
1552
 
                         }, metadata)
 
1571
        self.assertEqual({b'parents': [],
 
1572
                          b'sha1': b'a13f42b142d544aac9b085c42595d304150e31a2',
 
1573
                          b'storage_kind': b'mpdiff',
 
1574
                          }, metadata)
1553
1575
        # We should have an mpdiff that takes some lines from both parents.
1554
1576
        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)
 
1577
            b'i 4\n'
 
1578
            b'<inventory format="10" revision_id="a@cset-0-1">\n'
 
1579
            b'<directory file_id="root-id" name=""'
 
1580
            b' revision="a@cset-0-1" />\n'
 
1581
            b'<file file_id="file-id" name="file" parent_id="root-id"'
 
1582
            b' revision="a@cset-0-1"'
 
1583
            b' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
 
1584
            b' text_size="17" />\n'
 
1585
            b'</inventory>\n'
 
1586
            b'\n', bytes)
1565
1587
 
1566
1588
    def test_multiple_inventories_as_xml(self):
1567
1589
        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'])
 
1590
        sio = self.make_bundle_just_inventories(b'a@cset-0-1', b'a@cset-0-3',
 
1591
                                                [b'a@cset-0-2a', b'a@cset-0-2b', b'a@cset-0-3'])
1570
1592
        reader = v4.BundleReader(sio, stream_input=False)
1571
1593
        records = list(reader.iter_records())
1572
1594
        self.assertEqual(3, len(records))
1573
1595
        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'],
 
1596
        self.assertEqual([b'a@cset-0-2a', b'a@cset-0-2b', b'a@cset-0-3'],
1575
1597
                         revision_ids)
1576
1598
        metadata_2a = records[0][1]
1577
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1578
 
                          'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
1579
 
                          'storage_kind': 'mpdiff',
1580
 
                         }, metadata_2a)
 
1599
        self.assertEqual({b'parents': [b'a@cset-0-1'],
 
1600
                          b'sha1': b'1e105886d62d510763e22885eec733b66f5f09bf',
 
1601
                          b'storage_kind': b'mpdiff',
 
1602
                          }, metadata_2a)
1581
1603
        metadata_2b = records[1][1]
1582
 
        self.assertEqual({'parents': ['a@cset-0-1'],
1583
 
                          'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
1584
 
                          'storage_kind': 'mpdiff',
1585
 
                         }, metadata_2b)
 
1604
        self.assertEqual({b'parents': [b'a@cset-0-1'],
 
1605
                          b'sha1': b'f03f12574bdb5ed2204c28636c98a8547544ccd8',
 
1606
                          b'storage_kind': b'mpdiff',
 
1607
                          }, metadata_2b)
1586
1608
        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)
 
1609
        self.assertEqual({b'parents': [b'a@cset-0-2a', b'a@cset-0-2b'],
 
1610
                          b'sha1': b'09c53b0c4de0895e11a2aacc34fef60a6e70865c',
 
1611
                          b'storage_kind': b'mpdiff',
 
1612
                          }, metadata_3)
1591
1613
        bytes_2a = records[0][0]
1592
1614
        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)
 
1615
            b'i 1\n'
 
1616
            b'<inventory format="10" revision_id="a@cset-0-2a">\n'
 
1617
            b'\n'
 
1618
            b'c 0 1 1 1\n'
 
1619
            b'i 1\n'
 
1620
            b'<file file_id="file-id" name="file" parent_id="root-id"'
 
1621
            b' revision="a@cset-0-2a"'
 
1622
            b' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
 
1623
            b' text_size="12" />\n'
 
1624
            b'\n'
 
1625
            b'c 0 3 3 1\n', bytes_2a)
1604
1626
        bytes_2b = records[1][0]
1605
1627
        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)
 
1628
            b'i 1\n'
 
1629
            b'<inventory format="10" revision_id="a@cset-0-2b">\n'
 
1630
            b'\n'
 
1631
            b'c 0 1 1 2\n'
 
1632
            b'i 1\n'
 
1633
            b'<file file_id="file2-id" name="other-file" parent_id="root-id"'
 
1634
            b' revision="a@cset-0-2b"'
 
1635
            b' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
 
1636
            b' text_size="14" />\n'
 
1637
            b'\n'
 
1638
            b'c 0 3 4 1\n', bytes_2b)
1617
1639
        bytes_3 = records[2][0]
1618
1640
        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)
 
1641
            b'i 1\n'
 
1642
            b'<inventory format="10" revision_id="a@cset-0-3">\n'
 
1643
            b'\n'
 
1644
            b'c 0 1 1 2\n'
 
1645
            b'c 1 3 3 2\n', bytes_3)
1624
1646
 
1625
1647
    def test_creating_bundle_preserves_chk_pages(self):
1626
1648
        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)
 
1649
        target = self.b1.controldir.sprout('target',
 
1650
                                           revision_id=b'a@cset-0-2a').open_branch()
 
1651
        bundle_txt, rev_ids = self.create_bundle_text(b'a@cset-0-2a',
 
1652
                                                      b'a@cset-0-3')
 
1653
        self.assertEqual(set([b'a@cset-0-2b', b'a@cset-0-3']), set(rev_ids))
1632
1654
        bundle = read_bundle(bundle_txt)
1633
1655
        target.lock_write()
1634
1656
        self.addCleanup(target.unlock)
1635
1657
        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')
 
1658
        inv1 = next(self.b1.repository.inventories.get_record_stream([
 
1659
            (b'a@cset-0-3',)], 'unordered',
 
1660
            True)).get_bytes_as('fulltext')
 
1661
        inv2 = next(target.repository.inventories.get_record_stream([
 
1662
            (b'a@cset-0-3',)], 'unordered',
 
1663
            True)).get_bytes_as('fulltext')
1642
1664
        self.assertEqualDiff(inv1, inv2)
1643
1665
 
1644
1666
 
1649
1671
 
1650
1672
        self.build_tree(['b1/one'])
1651
1673
        wt.add('one')
1652
 
        wt.commit('add one', rev_id='a@cset-0-1')
 
1674
        wt.commit('add one', rev_id=b'a@cset-0-1')
1653
1675
        self.build_tree(['b1/two'])
1654
1676
        wt.add('two')
1655
 
        wt.commit('add two', rev_id='a@cset-0-2',
1656
 
                  revprops={'branch-nick':'test'})
 
1677
        wt.commit('add two', rev_id=b'a@cset-0-2',
 
1678
                  revprops={u'branch-nick': 'test'})
1657
1679
 
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))
 
1680
        bundle_txt = BytesIO()
 
1681
        rev_ids = write_bundle(wt.branch.repository, b'a@cset-0-2',
 
1682
                               b'a@cset-0-1', bundle_txt, self.format)
 
1683
        self.assertEqual({b'a@cset-0-2'}, set(rev_ids))
1662
1684
        bundle_txt.seek(0, 0)
1663
1685
        return bundle_txt
1664
1686
 
1665
1687
    def check_valid(self, bundle):
1666
1688
        """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])
 
1689
        self.assertEqual([b'a@cset-0-2'],
 
1690
                         [r.revision_id for r in bundle.real_revisions])
1669
1691
 
1670
1692
    def test_extra_whitespace(self):
1671
1693
        bundle_txt = self.build_test_bundle()
1674
1696
        # Adding one extra newline used to give us
1675
1697
        # TypeError: float() argument must be a string or a number
1676
1698
        bundle_txt.seek(0, 2)
1677
 
        bundle_txt.write('\n')
 
1699
        bundle_txt.write(b'\n')
1678
1700
        bundle_txt.seek(0)
1679
1701
 
1680
1702
        bundle = read_bundle(bundle_txt)
1687
1709
        # Adding two extra newlines used to give us
1688
1710
        # MalformedPatches: The first line of all patches should be ...
1689
1711
        bundle_txt.seek(0, 2)
1690
 
        bundle_txt.write('\n\n')
 
1712
        bundle_txt.write(b'\n\n')
1691
1713
        bundle_txt.seek(0)
1692
1714
 
1693
1715
        bundle = read_bundle(bundle_txt)
1707
1729
        # test is concerned with the exact case where the serializer
1708
1730
        # creates a blank line at the end, and fails if that
1709
1731
        # line is stripped
1710
 
        self.assertEqual('\n\n', raw[-2:])
1711
 
        bundle_txt = StringIO(raw[:-1])
 
1732
        self.assertEqual(b'\n\n', raw[-2:])
 
1733
        bundle_txt = BytesIO(raw[:-1])
1712
1734
 
1713
1735
        bundle = read_bundle(bundle_txt)
1714
1736
        self.check_valid(bundle)
1716
1738
    def test_opening_text(self):
1717
1739
        bundle_txt = self.build_test_bundle()
1718
1740
 
1719
 
        bundle_txt = StringIO("Some random\nemail comments\n"
1720
 
                              + bundle_txt.getvalue())
 
1741
        bundle_txt = BytesIO(
 
1742
            b"Some random\nemail comments\n" + bundle_txt.getvalue())
1721
1743
 
1722
1744
        bundle = read_bundle(bundle_txt)
1723
1745
        self.check_valid(bundle)
1725
1747
    def test_trailing_text(self):
1726
1748
        bundle_txt = self.build_test_bundle()
1727
1749
 
1728
 
        bundle_txt = StringIO(bundle_txt.getvalue() +
1729
 
                              "Some trailing\nrandom\ntext\n")
 
1750
        bundle_txt = BytesIO(
 
1751
            bundle_txt.getvalue() + b"Some trailing\nrandom\ntext\n")
1730
1752
 
1731
1753
        bundle = read_bundle(bundle_txt)
1732
1754
        self.check_valid(bundle)
1740
1762
class TestBundleWriterReader(tests.TestCase):
1741
1763
 
1742
1764
    def test_roundtrip_record(self):
1743
 
        fileobj = StringIO()
 
1765
        fileobj = BytesIO()
1744
1766
        writer = v4.BundleWriter(fileobj)
1745
1767
        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')
 
1768
        writer.add_info_record({b'foo': b'bar'})
 
1769
        writer._add_record(b"Record body", {b'parents': [b'1', b'3'],
 
1770
                                            b'storage_kind': b'fulltext'}, 'file', b'revid', b'fileid')
1749
1771
        writer.end()
1750
1772
        fileobj.seek(0)
1751
1773
        reader = v4.BundleReader(fileobj, stream_input=True)
1752
1774
        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)
 
1775
        record = next(record_iter)
 
1776
        self.assertEqual((None, {b'foo': b'bar', b'storage_kind': b'header'},
 
1777
                          'info', None, None), record)
 
1778
        record = next(record_iter)
 
1779
        self.assertEqual((b"Record body", {b'storage_kind': b'fulltext',
 
1780
                                           b'parents': [b'1', b'3']}, 'file', b'revid', b'fileid'),
 
1781
                         record)
1760
1782
 
1761
1783
    def test_roundtrip_record_memory_hungry(self):
1762
 
        fileobj = StringIO()
 
1784
        fileobj = BytesIO()
1763
1785
        writer = v4.BundleWriter(fileobj)
1764
1786
        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')
 
1787
        writer.add_info_record({b'foo': b'bar'})
 
1788
        writer._add_record(b"Record body", {b'parents': [b'1', b'3'],
 
1789
                                            b'storage_kind': b'fulltext'}, 'file', b'revid', b'fileid')
1768
1790
        writer.end()
1769
1791
        fileobj.seek(0)
1770
1792
        reader = v4.BundleReader(fileobj, stream_input=False)
1771
1793
        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)
 
1794
        record = next(record_iter)
 
1795
        self.assertEqual((None, {b'foo': b'bar', b'storage_kind': b'header'},
 
1796
                          'info', None, None), record)
 
1797
        record = next(record_iter)
 
1798
        self.assertEqual((b"Record body", {b'storage_kind': b'fulltext',
 
1799
                                           b'parents': [b'1', b'3']}, 'file', b'revid', b'fileid'),
 
1800
                         record)
1779
1801
 
1780
1802
    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))
 
1803
        self.assertEqual(b'revision/rev1',
 
1804
                         v4.BundleWriter.encode_name('revision', b'rev1'))
 
1805
        self.assertEqual(b'file/rev//1/file-id-1',
 
1806
                         v4.BundleWriter.encode_name('file', b'rev/1', b'file-id-1'))
 
1807
        self.assertEqual(b'info',
 
1808
                         v4.BundleWriter.encode_name('info', None, None))
1787
1809
 
1788
1810
    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'))
 
1811
        self.assertEqual(('revision', b'rev1', None),
 
1812
                         v4.BundleReader.decode_name(b'revision/rev1'))
 
1813
        self.assertEqual(('file', b'rev/1', b'file-id-1'),
 
1814
                         v4.BundleReader.decode_name(b'file/rev//1/file-id-1'))
1793
1815
        self.assertEqual(('info', None, None),
1794
 
                         v4.BundleReader.decode_name('info'))
 
1816
                         v4.BundleReader.decode_name(b'info'))
1795
1817
 
1796
1818
    def test_too_many_names(self):
1797
 
        fileobj = StringIO()
 
1819
        fileobj = BytesIO()
1798
1820
        writer = v4.BundleWriter(fileobj)
1799
1821
        writer.begin()
1800
 
        writer.add_info_record(foo='bar')
1801
 
        writer._container.add_bytes_record('blah', ['two', 'names'])
 
1822
        writer.add_info_record({b'foo': b'bar'})
 
1823
        writer._container.add_bytes_record(b'blah', [(b'two', ), (b'names', )])
1802
1824
        writer.end()
1803
1825
        fileobj.seek(0)
1804
1826
        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()
 
1827
        record = next(record_iter)
 
1828
        self.assertEqual((None, {b'foo': b'bar', b'storage_kind': b'header'},
 
1829
                          'info', None, None), record)
 
1830
        self.assertRaises(errors.BadBundle, next, record_iter)