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

  • Committer: Robert Collins
  • Date: 2010-05-11 08:36:16 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511083616-b8fjb19zomwupid0
Make all lock methods return Result objects, rather than lock_read returning self, as per John's review.

Show diffs side-by-side

added added

removed removed

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