/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/tests/test_bundle.py

  • Committer: Jelmer Vernooij
  • Date: 2020-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

Show diffs side-by-side

added added

removed removed

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