~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-21 22:29:49 UTC
  • Revision ID: mbp@sourcefrog.net-20050321222949-232c2093a6eadd80
fixup doctest for new module structure

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004-2006 by 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
from cStringIO import StringIO
18
 
import os
19
 
import sys
20
 
import tempfile
21
 
 
22
 
from bzrlib import inventory, treebuilder
23
 
from bzrlib.builtins import merge
24
 
from bzrlib.bzrdir import BzrDir
25
 
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
26
 
from bzrlib.bundle.bundle_data import BundleTree
27
 
from bzrlib.bundle.serializer import write_bundle, read_bundle
28
 
from bzrlib.branch import Branch
29
 
from bzrlib.diff import internal_diff
30
 
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle, 
31
 
                           NoSuchFile,)
32
 
from bzrlib.merge import Merge3Merger
33
 
from bzrlib.osutils import has_symlinks, sha_file
34
 
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
35
 
                          TestCase, TestSkipped)
36
 
from bzrlib.transform import TreeTransform
37
 
from bzrlib.workingtree import WorkingTree
38
 
 
39
 
 
40
 
class MockTree(object):
41
 
    def __init__(self):
42
 
        from bzrlib.inventory import InventoryDirectory, ROOT_ID
43
 
        object.__init__(self)
44
 
        self.paths = {ROOT_ID: ""}
45
 
        self.ids = {"": ROOT_ID}
46
 
        self.contents = {}
47
 
        self.root = InventoryDirectory(ROOT_ID, '', None)
48
 
 
49
 
    inventory = property(lambda x:x)
50
 
 
51
 
    def __iter__(self):
52
 
        return self.paths.iterkeys()
53
 
 
54
 
    def __getitem__(self, file_id):
55
 
        if file_id == self.root.file_id:
56
 
            return self.root
57
 
        else:
58
 
            return self.make_entry(file_id, self.paths[file_id])
59
 
 
60
 
    def parent_id(self, file_id):
61
 
        parent_dir = os.path.dirname(self.paths[file_id])
62
 
        if parent_dir == "":
63
 
            return None
64
 
        return self.ids[parent_dir]
65
 
 
66
 
    def iter_entries(self):
67
 
        for path, file_id in self.ids.iteritems():
68
 
            yield path, self[file_id]
69
 
 
70
 
    def get_file_kind(self, file_id):
71
 
        if file_id in self.contents:
72
 
            kind = 'file'
73
 
        else:
74
 
            kind = 'directory'
75
 
        return kind
76
 
 
77
 
    def make_entry(self, file_id, path):
78
 
        from bzrlib.inventory import (InventoryEntry, InventoryFile
79
 
                                    , InventoryDirectory, InventoryLink)
80
 
        name = os.path.basename(path)
81
 
        kind = self.get_file_kind(file_id)
82
 
        parent_id = self.parent_id(file_id)
83
 
        text_sha_1, text_size = self.contents_stats(file_id)
84
 
        if kind == 'directory':
85
 
            ie = InventoryDirectory(file_id, name, parent_id)
86
 
        elif kind == 'file':
87
 
            ie = InventoryFile(file_id, name, parent_id)
88
 
        elif kind == 'symlink':
89
 
            ie = InventoryLink(file_id, name, parent_id)
90
 
        else:
91
 
            raise BzrError('unknown kind %r' % kind)
92
 
        ie.text_sha1 = text_sha_1
93
 
        ie.text_size = text_size
94
 
        return ie
95
 
 
96
 
    def add_dir(self, file_id, path):
97
 
        self.paths[file_id] = path
98
 
        self.ids[path] = file_id
99
 
    
100
 
    def add_file(self, file_id, path, contents):
101
 
        self.add_dir(file_id, path)
102
 
        self.contents[file_id] = contents
103
 
 
104
 
    def path2id(self, path):
105
 
        return self.ids.get(path)
106
 
 
107
 
    def id2path(self, file_id):
108
 
        return self.paths.get(file_id)
109
 
 
110
 
    def has_id(self, file_id):
111
 
        return self.id2path(file_id) is not None
112
 
 
113
 
    def get_file(self, file_id):
114
 
        result = StringIO()
115
 
        result.write(self.contents[file_id])
116
 
        result.seek(0,0)
117
 
        return result
118
 
 
119
 
    def contents_stats(self, file_id):
120
 
        if file_id not in self.contents:
121
 
            return None, None
122
 
        text_sha1 = sha_file(self.get_file(file_id))
123
 
        return text_sha1, len(self.contents[file_id])
124
 
 
125
 
 
126
 
class BTreeTester(TestCase):
127
 
    """A simple unittest tester for the BundleTree class."""
128
 
 
129
 
    def make_tree_1(self):
130
 
        mtree = MockTree()
131
 
        mtree.add_dir("a", "grandparent")
132
 
        mtree.add_dir("b", "grandparent/parent")
133
 
        mtree.add_file("c", "grandparent/parent/file", "Hello\n")
134
 
        mtree.add_dir("d", "grandparent/alt_parent")
135
 
        return BundleTree(mtree, ''), mtree
136
 
        
137
 
    def test_renames(self):
138
 
        """Ensure that file renames have the proper effect on children"""
139
 
        btree = self.make_tree_1()[0]
140
 
        self.assertEqual(btree.old_path("grandparent"), "grandparent")
141
 
        self.assertEqual(btree.old_path("grandparent/parent"), 
142
 
                         "grandparent/parent")
143
 
        self.assertEqual(btree.old_path("grandparent/parent/file"),
144
 
                         "grandparent/parent/file")
145
 
 
146
 
        self.assertEqual(btree.id2path("a"), "grandparent")
147
 
        self.assertEqual(btree.id2path("b"), "grandparent/parent")
148
 
        self.assertEqual(btree.id2path("c"), "grandparent/parent/file")
149
 
 
150
 
        self.assertEqual(btree.path2id("grandparent"), "a")
151
 
        self.assertEqual(btree.path2id("grandparent/parent"), "b")
152
 
        self.assertEqual(btree.path2id("grandparent/parent/file"), "c")
153
 
 
154
 
        assert btree.path2id("grandparent2") is None
155
 
        assert btree.path2id("grandparent2/parent") is None
156
 
        assert btree.path2id("grandparent2/parent/file") is None
157
 
 
158
 
        btree.note_rename("grandparent", "grandparent2")
159
 
        assert btree.old_path("grandparent") is None
160
 
        assert btree.old_path("grandparent/parent") is None
161
 
        assert btree.old_path("grandparent/parent/file") is None
162
 
 
163
 
        self.assertEqual(btree.id2path("a"), "grandparent2")
164
 
        self.assertEqual(btree.id2path("b"), "grandparent2/parent")
165
 
        self.assertEqual(btree.id2path("c"), "grandparent2/parent/file")
166
 
 
167
 
        self.assertEqual(btree.path2id("grandparent2"), "a")
168
 
        self.assertEqual(btree.path2id("grandparent2/parent"), "b")
169
 
        self.assertEqual(btree.path2id("grandparent2/parent/file"), "c")
170
 
 
171
 
        assert btree.path2id("grandparent") is None
172
 
        assert btree.path2id("grandparent/parent") is None
173
 
        assert btree.path2id("grandparent/parent/file") is None
174
 
 
175
 
        btree.note_rename("grandparent/parent", "grandparent2/parent2")
176
 
        self.assertEqual(btree.id2path("a"), "grandparent2")
177
 
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
178
 
        self.assertEqual(btree.id2path("c"), "grandparent2/parent2/file")
179
 
 
180
 
        self.assertEqual(btree.path2id("grandparent2"), "a")
181
 
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
182
 
        self.assertEqual(btree.path2id("grandparent2/parent2/file"), "c")
183
 
 
184
 
        assert btree.path2id("grandparent2/parent") is None
185
 
        assert btree.path2id("grandparent2/parent/file") is None
186
 
 
187
 
        btree.note_rename("grandparent/parent/file", 
188
 
                          "grandparent2/parent2/file2")
189
 
        self.assertEqual(btree.id2path("a"), "grandparent2")
190
 
        self.assertEqual(btree.id2path("b"), "grandparent2/parent2")
191
 
        self.assertEqual(btree.id2path("c"), "grandparent2/parent2/file2")
192
 
 
193
 
        self.assertEqual(btree.path2id("grandparent2"), "a")
194
 
        self.assertEqual(btree.path2id("grandparent2/parent2"), "b")
195
 
        self.assertEqual(btree.path2id("grandparent2/parent2/file2"), "c")
196
 
 
197
 
        assert btree.path2id("grandparent2/parent2/file") is None
198
 
 
199
 
    def test_moves(self):
200
 
        """Ensure that file moves have the proper effect on children"""
201
 
        btree = self.make_tree_1()[0]
202
 
        btree.note_rename("grandparent/parent/file", 
203
 
                          "grandparent/alt_parent/file")
204
 
        self.assertEqual(btree.id2path("c"), "grandparent/alt_parent/file")
205
 
        self.assertEqual(btree.path2id("grandparent/alt_parent/file"), "c")
206
 
        assert btree.path2id("grandparent/parent/file") is None
207
 
 
208
 
    def unified_diff(self, old, new):
209
 
        out = StringIO()
210
 
        internal_diff("old", old, "new", new, out)
211
 
        out.seek(0,0)
212
 
        return out.read()
213
 
 
214
 
    def make_tree_2(self):
215
 
        btree = self.make_tree_1()[0]
216
 
        btree.note_rename("grandparent/parent/file", 
217
 
                          "grandparent/alt_parent/file")
218
 
        assert btree.id2path("e") is None
219
 
        assert btree.path2id("grandparent/parent/file") is None
220
 
        btree.note_id("e", "grandparent/parent/file")
221
 
        return btree
222
 
 
223
 
    def test_adds(self):
224
 
        """File/inventory adds"""
225
 
        btree = self.make_tree_2()
226
 
        add_patch = self.unified_diff([], ["Extra cheese\n"])
227
 
        btree.note_patch("grandparent/parent/file", add_patch)
228
 
        btree.note_id('f', 'grandparent/parent/symlink', kind='symlink')
229
 
        btree.note_target('grandparent/parent/symlink', 'venus')
230
 
        self.adds_test(btree)
231
 
 
232
 
    def adds_test(self, btree):
233
 
        self.assertEqual(btree.id2path("e"), "grandparent/parent/file")
234
 
        self.assertEqual(btree.path2id("grandparent/parent/file"), "e")
235
 
        self.assertEqual(btree.get_file("e").read(), "Extra cheese\n")
236
 
        self.assertEqual(btree.get_symlink_target('f'), 'venus')
237
 
 
238
 
    def test_adds2(self):
239
 
        """File/inventory adds, with patch-compatibile renames"""
240
 
        btree = self.make_tree_2()
241
 
        btree.contents_by_id = False
242
 
        add_patch = self.unified_diff(["Hello\n"], ["Extra cheese\n"])
243
 
        btree.note_patch("grandparent/parent/file", add_patch)
244
 
        btree.note_id('f', 'grandparent/parent/symlink', kind='symlink')
245
 
        btree.note_target('grandparent/parent/symlink', 'venus')
246
 
        self.adds_test(btree)
247
 
 
248
 
    def make_tree_3(self):
249
 
        btree, mtree = self.make_tree_1()
250
 
        mtree.add_file("e", "grandparent/parent/topping", "Anchovies\n")
251
 
        btree.note_rename("grandparent/parent/file", 
252
 
                          "grandparent/alt_parent/file")
253
 
        btree.note_rename("grandparent/parent/topping", 
254
 
                          "grandparent/alt_parent/stopping")
255
 
        return btree
256
 
 
257
 
    def get_file_test(self, btree):
258
 
        self.assertEqual(btree.get_file("e").read(), "Lemon\n")
259
 
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
260
 
 
261
 
    def test_get_file(self):
262
 
        """Get file contents"""
263
 
        btree = self.make_tree_3()
264
 
        mod_patch = self.unified_diff(["Anchovies\n"], ["Lemon\n"])
265
 
        btree.note_patch("grandparent/alt_parent/stopping", mod_patch)
266
 
        self.get_file_test(btree)
267
 
 
268
 
    def test_get_file2(self):
269
 
        """Get file contents, with patch-compatibile renames"""
270
 
        btree = self.make_tree_3()
271
 
        btree.contents_by_id = False
272
 
        mod_patch = self.unified_diff([], ["Lemon\n"])
273
 
        btree.note_patch("grandparent/alt_parent/stopping", mod_patch)
274
 
        mod_patch = self.unified_diff([], ["Hello\n"])
275
 
        btree.note_patch("grandparent/alt_parent/file", mod_patch)
276
 
        self.get_file_test(btree)
277
 
 
278
 
    def test_delete(self):
279
 
        "Deletion by bundle"
280
 
        btree = self.make_tree_1()[0]
281
 
        self.assertEqual(btree.get_file("c").read(), "Hello\n")
282
 
        btree.note_deletion("grandparent/parent/file")
283
 
        assert btree.id2path("c") is None
284
 
        assert btree.path2id("grandparent/parent/file") is None
285
 
 
286
 
    def sorted_ids(self, tree):
287
 
        ids = list(tree)
288
 
        ids.sort()
289
 
        return ids
290
 
 
291
 
    def test_iteration(self):
292
 
        """Ensure that iteration through ids works properly"""
293
 
        btree = self.make_tree_1()[0]
294
 
        self.assertEqual(self.sorted_ids(btree),
295
 
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
296
 
        btree.note_deletion("grandparent/parent/file")
297
 
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
298
 
        btree.note_last_changed("grandparent/alt_parent/fool", 
299
 
                                "revisionidiguess")
300
 
        self.assertEqual(self.sorted_ids(btree),
301
 
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
302
 
 
303
 
 
304
 
class BundleTester(TestCaseWithTransport):
305
 
 
306
 
    def create_bundle_text(self, base_rev_id, rev_id):
307
 
        bundle_txt = StringIO()
308
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
309
 
                               bundle_txt)
310
 
        bundle_txt.seek(0)
311
 
        self.assertEqual(bundle_txt.readline(), 
312
 
                         '# Bazaar revision bundle v0.8\n')
313
 
        self.assertEqual(bundle_txt.readline(), '#\n')
314
 
 
315
 
        rev = self.b1.repository.get_revision(rev_id)
316
 
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
317
 
                         u'# message:\n')
318
 
 
319
 
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
320
 
        bundle_txt.seek(0)
321
 
        return bundle_txt, rev_ids
322
 
 
323
 
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
324
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
325
 
        Make sure that the text generated is valid, and that it
326
 
        can be applied against the base, and generate the same information.
327
 
        
328
 
        :return: The in-memory bundle 
329
 
        """
330
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
331
 
 
332
 
        # This should also validate the generated bundle 
333
 
        bundle = read_bundle(bundle_txt)
334
 
        repository = self.b1.repository
335
 
        for bundle_rev in bundle.real_revisions:
336
 
            # These really should have already been checked when we read the
337
 
            # bundle, since it computes the sha1 hash for the revision, which
338
 
            # only will match if everything is okay, but lets be explicit about
339
 
            # it
340
 
            branch_rev = repository.get_revision(bundle_rev.revision_id)
341
 
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
342
 
                      'timestamp', 'timezone', 'message', 'committer', 
343
 
                      'parent_ids', 'properties'):
344
 
                self.assertEqual(getattr(branch_rev, a), 
345
 
                                 getattr(bundle_rev, a))
346
 
            self.assertEqual(len(branch_rev.parent_ids), 
347
 
                             len(bundle_rev.parent_ids))
348
 
        self.assertEqual(rev_ids, 
349
 
                         [r.revision_id for r in bundle.real_revisions])
350
 
        self.valid_apply_bundle(base_rev_id, bundle,
351
 
                                   checkout_dir=checkout_dir)
352
 
 
353
 
        return bundle
354
 
 
355
 
    def get_invalid_bundle(self, base_rev_id, rev_id):
356
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
357
 
        Munge the text so that it's invalid.
358
 
        
359
 
        :return: The in-memory bundle
360
 
        """
361
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
362
 
        new_text = bundle_txt.getvalue().replace('executable:no', 
363
 
                                               'executable:yes')
364
 
        bundle_txt = StringIO(new_text)
365
 
        bundle = read_bundle(bundle_txt)
366
 
        self.valid_apply_bundle(base_rev_id, bundle)
367
 
        return bundle 
368
 
 
369
 
    def test_non_bundle(self):
370
 
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
371
 
 
372
 
    def test_malformed(self):
373
 
        self.assertRaises(BadBundle, read_bundle, 
374
 
                          StringIO('# Bazaar revision bundle v'))
375
 
 
376
 
    def test_crlf_bundle(self):
377
 
        try:
378
 
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
379
 
        except BadBundle:
380
 
            # It is currently permitted for bundles with crlf line endings to
381
 
            # make read_bundle raise a BadBundle, but this should be fixed.
382
 
            # Anything else, especially NotABundle, is an error.
383
 
            pass
384
 
 
385
 
    def get_checkout(self, rev_id, checkout_dir=None):
386
 
        """Get a new tree, with the specified revision in it.
387
 
        """
388
 
 
389
 
        if checkout_dir is None:
390
 
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
391
 
        else:
392
 
            if not os.path.exists(checkout_dir):
393
 
                os.mkdir(checkout_dir)
394
 
        tree = BzrDir.create_standalone_workingtree(checkout_dir)
395
 
        s = StringIO()
396
 
        ancestors = write_bundle(self.b1.repository, rev_id, None, s)
397
 
        s.seek(0)
398
 
        assert isinstance(s.getvalue(), str), (
399
 
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
400
 
        install_bundle(tree.branch.repository, read_bundle(s))
401
 
        for ancestor in ancestors:
402
 
            old = self.b1.repository.revision_tree(ancestor)
403
 
            new = tree.branch.repository.revision_tree(ancestor)
404
 
 
405
 
            # Check that there aren't any inventory level changes
406
 
            delta = new.changes_from(old)
407
 
            self.assertFalse(delta.has_changed(),
408
 
                             'Revision %s not copied correctly.'
409
 
                             % (ancestor,))
410
 
 
411
 
            # Now check that the file contents are all correct
412
 
            for inventory_id in old:
413
 
                try:
414
 
                    old_file = old.get_file(inventory_id)
415
 
                except NoSuchFile:
416
 
                    continue
417
 
                if old_file is None:
418
 
                    continue
419
 
                self.assertEqual(old_file.read(),
420
 
                                 new.get_file(inventory_id).read())
421
 
        if rev_id is not None:
422
 
            rh = self.b1.revision_history()
423
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
424
 
            tree.update()
425
 
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
426
 
            self.assertFalse(delta.has_changed(),
427
 
                             'Working tree has modifications')
428
 
        return tree
429
 
 
430
 
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
431
 
        """Get the base revision, apply the changes, and make
432
 
        sure everything matches the builtin branch.
433
 
        """
434
 
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
435
 
        original_parents = to_tree.get_parent_ids()
436
 
        repository = to_tree.branch.repository
437
 
        original_parents = to_tree.get_parent_ids()
438
 
        self.assertIs(repository.has_revision(base_rev_id), True)
439
 
        for rev in info.real_revisions:
440
 
            self.assert_(not repository.has_revision(rev.revision_id),
441
 
                'Revision {%s} present before applying bundle' 
442
 
                % rev.revision_id)
443
 
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
444
 
 
445
 
        for rev in info.real_revisions:
446
 
            self.assert_(repository.has_revision(rev.revision_id),
447
 
                'Missing revision {%s} after applying bundle' 
448
 
                % rev.revision_id)
449
 
 
450
 
        self.assert_(to_tree.branch.repository.has_revision(info.target))
451
 
        # Do we also want to verify that all the texts have been added?
452
 
 
453
 
        self.assertEqual(original_parents + [info.target],
454
 
            to_tree.get_parent_ids())
455
 
 
456
 
        rev = info.real_revisions[-1]
457
 
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
458
 
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
459
 
        
460
 
        # TODO: make sure the target tree is identical to base tree
461
 
        #       we might also check the working tree.
462
 
 
463
 
        base_files = list(base_tree.list_files())
464
 
        to_files = list(to_tree.list_files())
465
 
        self.assertEqual(len(base_files), len(to_files))
466
 
        for base_file, to_file in zip(base_files, to_files):
467
 
            self.assertEqual(base_file, to_file)
468
 
 
469
 
        for path, status, kind, fileid, entry in base_files:
470
 
            # Check that the meta information is the same
471
 
            self.assertEqual(base_tree.get_file_size(fileid),
472
 
                    to_tree.get_file_size(fileid))
473
 
            self.assertEqual(base_tree.get_file_sha1(fileid),
474
 
                    to_tree.get_file_sha1(fileid))
475
 
            # Check that the contents are the same
476
 
            # This is pretty expensive
477
 
            # self.assertEqual(base_tree.get_file(fileid).read(),
478
 
            #         to_tree.get_file(fileid).read())
479
 
 
480
 
    def test_bundle(self):
481
 
        self.tree1 = self.make_branch_and_tree('b1')
482
 
        self.b1 = self.tree1.branch
483
 
 
484
 
        open('b1/one', 'wb').write('one\n')
485
 
        self.tree1.add('one')
486
 
        self.tree1.commit('add one', rev_id='a@cset-0-1')
487
 
 
488
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
489
 
        # FIXME: The current write_bundle api no longer supports
490
 
        #        setting a custom summary message
491
 
        #        We should re-introduce the ability, and update
492
 
        #        the tests to make sure it works.
493
 
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
494
 
        #         message='With a specialized message')
495
 
 
496
 
        # Make sure we can handle files with spaces, tabs, other
497
 
        # bogus characters
498
 
        self.build_tree([
499
 
                'b1/with space.txt'
500
 
                , 'b1/dir/'
501
 
                , 'b1/dir/filein subdir.c'
502
 
                , 'b1/dir/WithCaps.txt'
503
 
                , 'b1/dir/ pre space'
504
 
                , 'b1/sub/'
505
 
                , 'b1/sub/sub/'
506
 
                , 'b1/sub/sub/nonempty.txt'
507
 
                ])
508
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
509
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
510
 
        tt = TreeTransform(self.tree1)
511
 
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
512
 
        tt.apply()
513
 
        self.tree1.add([
514
 
                'with space.txt'
515
 
                , 'dir'
516
 
                , 'dir/filein subdir.c'
517
 
                , 'dir/WithCaps.txt'
518
 
                , 'dir/ pre space'
519
 
                , 'dir/nolastnewline.txt'
520
 
                , 'sub'
521
 
                , 'sub/sub'
522
 
                , 'sub/sub/nonempty.txt'
523
 
                , 'sub/sub/emptyfile.txt'
524
 
                ])
525
 
        self.tree1.commit('add whitespace', rev_id='a@cset-0-2')
526
 
 
527
 
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
528
 
 
529
 
        # Check a rollup bundle 
530
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
531
 
 
532
 
        # Now delete entries
533
 
        self.tree1.remove(
534
 
                ['sub/sub/nonempty.txt'
535
 
                , 'sub/sub/emptyfile.txt'
536
 
                , 'sub/sub'
537
 
                ])
538
 
        tt = TreeTransform(self.tree1)
539
 
        trans_id = tt.trans_id_tree_file_id('exe-1')
540
 
        tt.set_executability(False, trans_id)
541
 
        tt.apply()
542
 
        self.tree1.commit('removed', rev_id='a@cset-0-3')
543
 
        
544
 
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
545
 
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
546
 
                          'a@cset-0-2', 'a@cset-0-3')
547
 
        # Check a rollup bundle 
548
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
549
 
 
550
 
        # Now move the directory
551
 
        self.tree1.rename_one('dir', 'sub/dir')
552
 
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
553
 
 
554
 
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
555
 
        # Check a rollup bundle 
556
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
557
 
 
558
 
        # Modified files
559
 
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
560
 
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
561
 
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
562
 
        self.tree1.rename_one('sub/dir/ pre space', 
563
 
                              'sub/ start space')
564
 
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
565
 
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
566
 
 
567
 
        self.tree1.rename_one('sub/dir/WithCaps.txt', 'temp')
568
 
        self.tree1.rename_one('with space.txt', 'WithCaps.txt')
569
 
        self.tree1.rename_one('temp', 'with space.txt')
570
 
        self.tree1.commit(u'swap filenames', rev_id='a@cset-0-6',
571
 
                          verbose=False)
572
 
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
573
 
        other = self.get_checkout('a@cset-0-5')
574
 
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
575
 
        other.commit('rename file', rev_id='a@cset-0-6b')
576
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
577
 
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
578
 
                          verbose=False)
579
 
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
580
 
 
581
 
    def test_symlink_bundle(self):
582
 
        if not has_symlinks():
583
 
            raise TestSkipped("No symlink support")
584
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
585
 
        self.b1 = self.tree1.branch
586
 
        tt = TreeTransform(self.tree1)
587
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
588
 
        tt.apply()
589
 
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
590
 
        self.get_valid_bundle(None, 'l@cset-0-1')
591
 
        tt = TreeTransform(self.tree1)
592
 
        trans_id = tt.trans_id_tree_file_id('link-1')
593
 
        tt.adjust_path('link2', tt.root, trans_id)
594
 
        tt.delete_contents(trans_id)
595
 
        tt.create_symlink('mars', trans_id)
596
 
        tt.apply()
597
 
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
598
 
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
599
 
        tt = TreeTransform(self.tree1)
600
 
        trans_id = tt.trans_id_tree_file_id('link-1')
601
 
        tt.delete_contents(trans_id)
602
 
        tt.create_symlink('jupiter', trans_id)
603
 
        tt.apply()
604
 
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
605
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
606
 
        tt = TreeTransform(self.tree1)
607
 
        trans_id = tt.trans_id_tree_file_id('link-1')
608
 
        tt.delete_contents(trans_id)
609
 
        tt.apply()
610
 
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
611
 
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
612
 
 
613
 
    def test_binary_bundle(self):
614
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
615
 
        self.b1 = self.tree1.branch
616
 
        tt = TreeTransform(self.tree1)
617
 
        
618
 
        # Add
619
 
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
620
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
621
 
        tt.apply()
622
 
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
623
 
        self.get_valid_bundle(None, 'b@cset-0-1')
624
 
 
625
 
        # Delete
626
 
        tt = TreeTransform(self.tree1)
627
 
        trans_id = tt.trans_id_tree_file_id('binary-1')
628
 
        tt.delete_contents(trans_id)
629
 
        tt.apply()
630
 
        self.tree1.commit('delete binary', rev_id='b@cset-0-2')
631
 
        self.get_valid_bundle('b@cset-0-1', 'b@cset-0-2')
632
 
 
633
 
        # Rename & modify
634
 
        tt = TreeTransform(self.tree1)
635
 
        trans_id = tt.trans_id_tree_file_id('binary-2')
636
 
        tt.adjust_path('file3', tt.root, trans_id)
637
 
        tt.delete_contents(trans_id)
638
 
        tt.create_file('file\rcontents\x00\n\x00', trans_id)
639
 
        tt.apply()
640
 
        self.tree1.commit('rename and modify binary', rev_id='b@cset-0-3')
641
 
        self.get_valid_bundle('b@cset-0-2', 'b@cset-0-3')
642
 
 
643
 
        # Modify
644
 
        tt = TreeTransform(self.tree1)
645
 
        trans_id = tt.trans_id_tree_file_id('binary-2')
646
 
        tt.delete_contents(trans_id)
647
 
        tt.create_file('\x00file\rcontents', trans_id)
648
 
        tt.apply()
649
 
        self.tree1.commit('just modify binary', rev_id='b@cset-0-4')
650
 
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
651
 
 
652
 
        # Rollup
653
 
        self.get_valid_bundle(None, 'b@cset-0-4')
654
 
 
655
 
    def test_last_modified(self):
656
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
657
 
        self.b1 = self.tree1.branch
658
 
        tt = TreeTransform(self.tree1)
659
 
        tt.new_file('file', tt.root, 'file', 'file')
660
 
        tt.apply()
661
 
        self.tree1.commit('create file', rev_id='a@lmod-0-1')
662
 
 
663
 
        tt = TreeTransform(self.tree1)
664
 
        trans_id = tt.trans_id_tree_file_id('file')
665
 
        tt.delete_contents(trans_id)
666
 
        tt.create_file('file2', trans_id)
667
 
        tt.apply()
668
 
        self.tree1.commit('modify text', rev_id='a@lmod-0-2a')
669
 
 
670
 
        other = self.get_checkout('a@lmod-0-1')
671
 
        tt = TreeTransform(other)
672
 
        trans_id = tt.trans_id_tree_file_id('file')
673
 
        tt.delete_contents(trans_id)
674
 
        tt.create_file('file2', trans_id)
675
 
        tt.apply()
676
 
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
677
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
678
 
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
679
 
                          verbose=False)
680
 
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
681
 
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
682
 
 
683
 
    def test_hide_history(self):
684
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
685
 
        self.b1 = self.tree1.branch
686
 
 
687
 
        open('b1/one', 'wb').write('one\n')
688
 
        self.tree1.add('one')
689
 
        self.tree1.commit('add file', rev_id='a@cset-0-1')
690
 
        open('b1/one', 'wb').write('two\n')
691
 
        self.tree1.commit('modify', rev_id='a@cset-0-2')
692
 
        open('b1/one', 'wb').write('three\n')
693
 
        self.tree1.commit('modify', rev_id='a@cset-0-3')
694
 
        bundle_file = StringIO()
695
 
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
696
 
                               'a@cset-0-1', bundle_file)
697
 
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
698
 
        self.assertContainsRe(bundle_file.getvalue(), 'one')
699
 
        self.assertContainsRe(bundle_file.getvalue(), 'three')
700
 
 
701
 
    def test_unicode_bundle(self):
702
 
        # Handle international characters
703
 
        os.mkdir('b1')
704
 
        try:
705
 
            f = open(u'b1/with Dod\xe9', 'wb')
706
 
        except UnicodeEncodeError:
707
 
            raise TestSkipped("Filesystem doesn't support unicode")
708
 
 
709
 
        self.tree1 = self.make_branch_and_tree('b1')
710
 
        self.b1 = self.tree1.branch
711
 
 
712
 
        f.write((u'A file\n'
713
 
            u'With international man of mystery\n'
714
 
            u'William Dod\xe9\n').encode('utf-8'))
715
 
        f.close()
716
 
 
717
 
        self.tree1.add([u'with Dod\xe9'])
718
 
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
719
 
                          rev_id='i18n-1', committer=u'William Dod\xe9')
720
 
 
721
 
        # Add
722
 
        bundle = self.get_valid_bundle(None, 'i18n-1')
723
 
 
724
 
        # Modified
725
 
        f = open(u'b1/with Dod\xe9', 'wb')
726
 
        f.write(u'Modified \xb5\n'.encode('utf8'))
727
 
        f.close()
728
 
        self.tree1.commit(u'modified', rev_id='i18n-2')
729
 
 
730
 
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
731
 
        
732
 
        # Renamed
733
 
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
734
 
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
735
 
                          committer=u'Erik B\xe5gfors')
736
 
 
737
 
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
738
 
 
739
 
        # Removed
740
 
        self.tree1.remove([u'B\xe5gfors'])
741
 
        self.tree1.commit(u'removed', rev_id='i18n-4')
742
 
 
743
 
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
744
 
 
745
 
        # Rollup
746
 
        bundle = self.get_valid_bundle(None, 'i18n-4')
747
 
 
748
 
 
749
 
    def test_whitespace_bundle(self):
750
 
        if sys.platform in ('win32', 'cygwin'):
751
 
            raise TestSkipped('Windows doesn\'t support filenames'
752
 
                              ' with tabs or trailing spaces')
753
 
        self.tree1 = self.make_branch_and_tree('b1')
754
 
        self.b1 = self.tree1.branch
755
 
 
756
 
        self.build_tree(['b1/trailing space '])
757
 
        self.tree1.add(['trailing space '])
758
 
        # TODO: jam 20060701 Check for handling files with '\t' characters
759
 
        #       once we actually support them
760
 
 
761
 
        # Added
762
 
        self.tree1.commit('funky whitespace', rev_id='white-1')
763
 
 
764
 
        bundle = self.get_valid_bundle(None, 'white-1')
765
 
 
766
 
        # Modified
767
 
        open('b1/trailing space ', 'ab').write('add some text\n')
768
 
        self.tree1.commit('add text', rev_id='white-2')
769
 
 
770
 
        bundle = self.get_valid_bundle('white-1', 'white-2')
771
 
 
772
 
        # Renamed
773
 
        self.tree1.rename_one('trailing space ', ' start and end space ')
774
 
        self.tree1.commit('rename', rev_id='white-3')
775
 
 
776
 
        bundle = self.get_valid_bundle('white-2', 'white-3')
777
 
 
778
 
        # Removed
779
 
        self.tree1.remove([' start and end space '])
780
 
        self.tree1.commit('removed', rev_id='white-4')
781
 
 
782
 
        bundle = self.get_valid_bundle('white-3', 'white-4')
783
 
        
784
 
        # Now test a complet roll-up
785
 
        bundle = self.get_valid_bundle(None, 'white-4')
786
 
 
787
 
    def test_alt_timezone_bundle(self):
788
 
        self.tree1 = self.make_branch_and_memory_tree('b1')
789
 
        self.b1 = self.tree1.branch
790
 
        builder = treebuilder.TreeBuilder()
791
 
 
792
 
        self.tree1.lock_write()
793
 
        builder.start_tree(self.tree1)
794
 
        builder.build(['newfile'])
795
 
        builder.finish_tree()
796
 
 
797
 
        # Asia/Colombo offset = 5 hours 30 minutes
798
 
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
799
 
                          timezone=19800, timestamp=1152544886.0)
800
 
 
801
 
        bundle = self.get_valid_bundle(None, 'tz-1')
802
 
        
803
 
        rev = bundle.revisions[0]
804
 
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
805
 
        self.assertEqual(19800, rev.timezone)
806
 
        self.assertEqual(1152544886.0, rev.timestamp)
807
 
        self.tree1.unlock()
808
 
 
809
 
    def test_bundle_root_id(self):
810
 
        self.tree1 = self.make_branch_and_tree('b1')
811
 
        self.b1 = self.tree1.branch
812
 
        self.tree1.commit('message', rev_id='revid1')
813
 
        bundle = self.get_valid_bundle(None, 'revid1')
814
 
        tree = bundle.revision_tree(self.b1.repository, 'revid1')
815
 
        self.assertEqual('revid1', tree.inventory.root.revision)
816
 
 
817
 
 
818
 
class MungedBundleTester(TestCaseWithTransport):
819
 
 
820
 
    def build_test_bundle(self):
821
 
        wt = self.make_branch_and_tree('b1')
822
 
 
823
 
        self.build_tree(['b1/one'])
824
 
        wt.add('one')
825
 
        wt.commit('add one', rev_id='a@cset-0-1')
826
 
        self.build_tree(['b1/two'])
827
 
        wt.add('two')
828
 
        wt.commit('add two', rev_id='a@cset-0-2',
829
 
                  revprops={'branch-nick':'test'})
830
 
 
831
 
        bundle_txt = StringIO()
832
 
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
833
 
                               'a@cset-0-1', bundle_txt)
834
 
        self.assertEqual(['a@cset-0-2'], rev_ids)
835
 
        bundle_txt.seek(0, 0)
836
 
        return bundle_txt
837
 
 
838
 
    def check_valid(self, bundle):
839
 
        """Check that after whatever munging, the final object is valid."""
840
 
        self.assertEqual(['a@cset-0-2'],
841
 
            [r.revision_id for r in bundle.real_revisions])
842
 
 
843
 
    def test_extra_whitespace(self):
844
 
        bundle_txt = self.build_test_bundle()
845
 
 
846
 
        # Seek to the end of the file
847
 
        # Adding one extra newline used to give us
848
 
        # TypeError: float() argument must be a string or a number
849
 
        bundle_txt.seek(0, 2)
850
 
        bundle_txt.write('\n')
851
 
        bundle_txt.seek(0)
852
 
 
853
 
        bundle = read_bundle(bundle_txt)
854
 
        self.check_valid(bundle)
855
 
 
856
 
    def test_extra_whitespace_2(self):
857
 
        bundle_txt = self.build_test_bundle()
858
 
 
859
 
        # Seek to the end of the file
860
 
        # Adding two extra newlines used to give us
861
 
        # MalformedPatches: The first line of all patches should be ...
862
 
        bundle_txt.seek(0, 2)
863
 
        bundle_txt.write('\n\n')
864
 
        bundle_txt.seek(0)
865
 
 
866
 
        bundle = read_bundle(bundle_txt)
867
 
        self.check_valid(bundle)
868
 
 
869
 
    def test_missing_trailing_whitespace(self):
870
 
        bundle_txt = self.build_test_bundle()
871
 
 
872
 
        # Remove a trailing newline, it shouldn't kill the parser
873
 
        raw = bundle_txt.getvalue()
874
 
        # The contents of the bundle don't have to be this, but this
875
 
        # test is concerned with the exact case where the serializer
876
 
        # creates a blank line at the end, and fails if that
877
 
        # line is stripped
878
 
        self.assertEqual('\n\n', raw[-2:])
879
 
        bundle_txt = StringIO(raw[:-1])
880
 
 
881
 
        bundle = read_bundle(bundle_txt)
882
 
        self.check_valid(bundle)
883
 
 
884
 
    def test_opening_text(self):
885
 
        bundle_txt = self.build_test_bundle()
886
 
 
887
 
        bundle_txt = StringIO("Some random\nemail comments\n"
888
 
                              + bundle_txt.getvalue())
889
 
 
890
 
        bundle = read_bundle(bundle_txt)
891
 
        self.check_valid(bundle)
892
 
 
893
 
    def test_trailing_text(self):
894
 
        bundle_txt = self.build_test_bundle()
895
 
 
896
 
        bundle_txt = StringIO(bundle_txt.getvalue() +
897
 
                              "Some trailing\nrandom\ntext\n")
898
 
 
899
 
        bundle = read_bundle(bundle_txt)
900
 
        self.check_valid(bundle)
901