/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/bzr/tests/test_bundle.py

  • Committer: Jelmer Vernooij
  • Date: 2020-07-18 15:20:23 UTC
  • mto: (7490.40.61 work)
  • mto: This revision was merged to the branch mainline in revision 7519.
  • Revision ID: jelmer@jelmer.uk-20200718152023-cabh92o24ke217te
Ignore missing revs.

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