/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: Martin Pool
  • Date: 2005-08-24 08:59:32 UTC
  • Revision ID: mbp@sourcefrog.net-20050824085932-c61f1f1f1c930e13
- Add a simple UIFactory 

  The idea of this is to let a client of bzrlib set some 
  policy about how output is displayed.

  In this revision all that's done is that progress bars
  are constructed by a policy established by the application
  rather than being randomly constructed in the library 
  or passed down the calls.  This avoids progress bars
  popping up while running the test suite and cleans up
  some code.

Show diffs side-by-side

added added

removed removed

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