/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-06 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

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