/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: John Arbash Meinel
  • Date: 2006-07-03 18:27:35 UTC
  • mto: This revision was merged to the branch mainline in revision 1851.
  • Revision ID: john@arbash-meinel.com-20060703182735-3081f13e92d7f657
WorkingTree.open_containing() was directly calling os.getcwdu(), which on mac returns the wrong normalization, and on win32 would have the wrong slashes

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