~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_bundle.py

  • Committer: Robert Collins
  • Date: 2005-10-11 07:00:25 UTC
  • mto: This revision was merged to the branch mainline in revision 1443.
  • Revision ID: robertc@robertcollins.net-20051011070025-bac6b53cb6186dfd
create a config module - there is enough config logic to make this worthwhile, and start testing config processing.

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
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
 
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),
294
 
            [inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
295
 
        btree.note_deletion("grandparent/parent/file")
296
 
        btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
297
 
        btree.note_last_changed("grandparent/alt_parent/fool", 
298
 
                                "revisionidiguess")
299
 
        self.assertEqual(self.sorted_ids(btree),
300
 
            [inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
301
 
 
302
 
 
303
 
class BundleTester(TestCaseWithTransport):
304
 
 
305
 
    def create_bundle_text(self, base_rev_id, rev_id):
306
 
        bundle_txt = StringIO()
307
 
        rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id, 
308
 
                               bundle_txt)
309
 
        bundle_txt.seek(0)
310
 
        self.assertEqual(bundle_txt.readline(), 
311
 
                         '# Bazaar revision bundle v0.8\n')
312
 
        self.assertEqual(bundle_txt.readline(), '#\n')
313
 
 
314
 
        rev = self.b1.repository.get_revision(rev_id)
315
 
        self.assertEqual(bundle_txt.readline().decode('utf-8'),
316
 
                         u'# message:\n')
317
 
 
318
 
        open(',,bundle', 'wb').write(bundle_txt.getvalue())
319
 
        bundle_txt.seek(0)
320
 
        return bundle_txt, rev_ids
321
 
 
322
 
    def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
323
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
324
 
        Make sure that the text generated is valid, and that it
325
 
        can be applied against the base, and generate the same information.
326
 
        
327
 
        :return: The in-memory bundle 
328
 
        """
329
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
330
 
 
331
 
        # This should also validate the generated bundle 
332
 
        bundle = read_bundle(bundle_txt)
333
 
        repository = self.b1.repository
334
 
        for bundle_rev in bundle.real_revisions:
335
 
            # These really should have already been checked when we read the
336
 
            # bundle, since it computes the sha1 hash for the revision, which
337
 
            # only will match if everything is okay, but lets be explicit about
338
 
            # it
339
 
            branch_rev = repository.get_revision(bundle_rev.revision_id)
340
 
            for a in ('inventory_sha1', 'revision_id', 'parent_ids',
341
 
                      'timestamp', 'timezone', 'message', 'committer', 
342
 
                      'parent_ids', 'properties'):
343
 
                self.assertEqual(getattr(branch_rev, a), 
344
 
                                 getattr(bundle_rev, a))
345
 
            self.assertEqual(len(branch_rev.parent_ids), 
346
 
                             len(bundle_rev.parent_ids))
347
 
        self.assertEqual(rev_ids, 
348
 
                         [r.revision_id for r in bundle.real_revisions])
349
 
        self.valid_apply_bundle(base_rev_id, bundle,
350
 
                                   checkout_dir=checkout_dir)
351
 
 
352
 
        return bundle
353
 
 
354
 
    def get_invalid_bundle(self, base_rev_id, rev_id):
355
 
        """Create a bundle from base_rev_id -> rev_id in built-in branch.
356
 
        Munge the text so that it's invalid.
357
 
        
358
 
        :return: The in-memory bundle
359
 
        """
360
 
        bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
361
 
        new_text = bundle_txt.getvalue().replace('executable:no', 
362
 
                                               'executable:yes')
363
 
        bundle_txt = StringIO(new_text)
364
 
        bundle = read_bundle(bundle_txt)
365
 
        self.valid_apply_bundle(base_rev_id, bundle)
366
 
        return bundle 
367
 
 
368
 
    def test_non_bundle(self):
369
 
        self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
370
 
 
371
 
    def test_malformed(self):
372
 
        self.assertRaises(BadBundle, read_bundle, 
373
 
                          StringIO('# Bazaar revision bundle v'))
374
 
 
375
 
    def test_crlf_bundle(self):
376
 
        try:
377
 
            read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
378
 
        except BadBundle:
379
 
            # It is currently permitted for bundles with crlf line endings to
380
 
            # make read_bundle raise a BadBundle, but this should be fixed.
381
 
            # Anything else, especially NotABundle, is an error.
382
 
            pass
383
 
 
384
 
    def get_checkout(self, rev_id, checkout_dir=None):
385
 
        """Get a new tree, with the specified revision in it.
386
 
        """
387
 
 
388
 
        if checkout_dir is None:
389
 
            checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
390
 
        else:
391
 
            if not os.path.exists(checkout_dir):
392
 
                os.mkdir(checkout_dir)
393
 
        tree = BzrDir.create_standalone_workingtree(checkout_dir)
394
 
        s = StringIO()
395
 
        ancestors = write_bundle(self.b1.repository, rev_id, None, s)
396
 
        s.seek(0)
397
 
        assert isinstance(s.getvalue(), str), (
398
 
            "Bundle isn't a bytestring:\n %s..." % repr(s.getvalue())[:40])
399
 
        install_bundle(tree.branch.repository, read_bundle(s))
400
 
        for ancestor in ancestors:
401
 
            old = self.b1.repository.revision_tree(ancestor)
402
 
            new = tree.branch.repository.revision_tree(ancestor)
403
 
 
404
 
            # Check that there aren't any inventory level changes
405
 
            delta = new.changes_from(old)
406
 
            self.assertFalse(delta.has_changed(),
407
 
                             'Revision %s not copied correctly.'
408
 
                             % (ancestor,))
409
 
 
410
 
            # Now check that the file contents are all correct
411
 
            for inventory_id in old:
412
 
                try:
413
 
                    old_file = old.get_file(inventory_id)
414
 
                except:
415
 
                    continue
416
 
                if old_file is None:
417
 
                    continue
418
 
                self.assertEqual(old_file.read(),
419
 
                                 new.get_file(inventory_id).read())
420
 
        if rev_id is not None:
421
 
            rh = self.b1.revision_history()
422
 
            tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
423
 
            tree.update()
424
 
            delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
425
 
            self.assertFalse(delta.has_changed(),
426
 
                             'Working tree has modifications')
427
 
        return tree
428
 
 
429
 
    def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
430
 
        """Get the base revision, apply the changes, and make
431
 
        sure everything matches the builtin branch.
432
 
        """
433
 
        to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
434
 
        repository = to_tree.branch.repository
435
 
        self.assertIs(repository.has_revision(base_rev_id), True)
436
 
        for rev in info.real_revisions:
437
 
            self.assert_(not repository.has_revision(rev.revision_id),
438
 
                'Revision {%s} present before applying bundle' 
439
 
                % rev.revision_id)
440
 
        merge_bundle(info, to_tree, True, Merge3Merger, False, False)
441
 
 
442
 
        for rev in info.real_revisions:
443
 
            self.assert_(repository.has_revision(rev.revision_id),
444
 
                'Missing revision {%s} after applying bundle' 
445
 
                % rev.revision_id)
446
 
 
447
 
        self.assert_(to_tree.branch.repository.has_revision(info.target))
448
 
        # Do we also want to verify that all the texts have been added?
449
 
 
450
 
        self.assert_(info.target in to_tree.pending_merges())
451
 
 
452
 
 
453
 
        rev = info.real_revisions[-1]
454
 
        base_tree = self.b1.repository.revision_tree(rev.revision_id)
455
 
        to_tree = to_tree.branch.repository.revision_tree(rev.revision_id)
456
 
        
457
 
        # TODO: make sure the target tree is identical to base tree
458
 
        #       we might also check the working tree.
459
 
 
460
 
        base_files = list(base_tree.list_files())
461
 
        to_files = list(to_tree.list_files())
462
 
        self.assertEqual(len(base_files), len(to_files))
463
 
        for base_file, to_file in zip(base_files, to_files):
464
 
            self.assertEqual(base_file, to_file)
465
 
 
466
 
        for path, status, kind, fileid, entry in base_files:
467
 
            # Check that the meta information is the same
468
 
            self.assertEqual(base_tree.get_file_size(fileid),
469
 
                    to_tree.get_file_size(fileid))
470
 
            self.assertEqual(base_tree.get_file_sha1(fileid),
471
 
                    to_tree.get_file_sha1(fileid))
472
 
            # Check that the contents are the same
473
 
            # This is pretty expensive
474
 
            # self.assertEqual(base_tree.get_file(fileid).read(),
475
 
            #         to_tree.get_file(fileid).read())
476
 
 
477
 
    def test_bundle(self):
478
 
        self.tree1 = self.make_branch_and_tree('b1')
479
 
        self.b1 = self.tree1.branch
480
 
 
481
 
        open('b1/one', 'wb').write('one\n')
482
 
        self.tree1.add('one')
483
 
        self.tree1.commit('add one', rev_id='a@cset-0-1')
484
 
 
485
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-1')
486
 
        # FIXME: The current write_bundle api no longer supports
487
 
        #        setting a custom summary message
488
 
        #        We should re-introduce the ability, and update
489
 
        #        the tests to make sure it works.
490
 
        # bundle = self.get_valid_bundle(None, 'a@cset-0-1',
491
 
        #         message='With a specialized message')
492
 
 
493
 
        # Make sure we can handle files with spaces, tabs, other
494
 
        # bogus characters
495
 
        self.build_tree([
496
 
                'b1/with space.txt'
497
 
                , 'b1/dir/'
498
 
                , 'b1/dir/filein subdir.c'
499
 
                , 'b1/dir/WithCaps.txt'
500
 
                , 'b1/dir/ pre space'
501
 
                , 'b1/sub/'
502
 
                , 'b1/sub/sub/'
503
 
                , 'b1/sub/sub/nonempty.txt'
504
 
                ])
505
 
        open('b1/sub/sub/emptyfile.txt', 'wb').close()
506
 
        open('b1/dir/nolastnewline.txt', 'wb').write('bloop')
507
 
        tt = TreeTransform(self.tree1)
508
 
        tt.new_file('executable', tt.root, '#!/bin/sh\n', 'exe-1', True)
509
 
        tt.apply()
510
 
        self.tree1.add([
511
 
                'with space.txt'
512
 
                , 'dir'
513
 
                , 'dir/filein subdir.c'
514
 
                , 'dir/WithCaps.txt'
515
 
                , 'dir/ pre space'
516
 
                , 'dir/nolastnewline.txt'
517
 
                , 'sub'
518
 
                , 'sub/sub'
519
 
                , 'sub/sub/nonempty.txt'
520
 
                , 'sub/sub/emptyfile.txt'
521
 
                ])
522
 
        self.tree1.commit('add whitespace', rev_id='a@cset-0-2')
523
 
 
524
 
        bundle = self.get_valid_bundle('a@cset-0-1', 'a@cset-0-2')
525
 
 
526
 
        # Check a rollup bundle 
527
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-2')
528
 
 
529
 
        # Now delete entries
530
 
        self.tree1.remove(
531
 
                ['sub/sub/nonempty.txt'
532
 
                , 'sub/sub/emptyfile.txt'
533
 
                , 'sub/sub'
534
 
                ])
535
 
        tt = TreeTransform(self.tree1)
536
 
        trans_id = tt.trans_id_tree_file_id('exe-1')
537
 
        tt.set_executability(False, trans_id)
538
 
        tt.apply()
539
 
        self.tree1.commit('removed', rev_id='a@cset-0-3')
540
 
        
541
 
        bundle = self.get_valid_bundle('a@cset-0-2', 'a@cset-0-3')
542
 
        self.assertRaises(TestamentMismatch, self.get_invalid_bundle, 
543
 
                          'a@cset-0-2', 'a@cset-0-3')
544
 
        # Check a rollup bundle 
545
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-3')
546
 
 
547
 
        # Now move the directory
548
 
        self.tree1.rename_one('dir', 'sub/dir')
549
 
        self.tree1.commit('rename dir', rev_id='a@cset-0-4')
550
 
 
551
 
        bundle = self.get_valid_bundle('a@cset-0-3', 'a@cset-0-4')
552
 
        # Check a rollup bundle 
553
 
        bundle = self.get_valid_bundle(None, 'a@cset-0-4')
554
 
 
555
 
        # Modified files
556
 
        open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
557
 
        open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
558
 
        open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
559
 
        self.tree1.rename_one('sub/dir/ pre space', 
560
 
                              'sub/ start space')
561
 
        self.tree1.commit('Modified files', rev_id='a@cset-0-5')
562
 
        bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
563
 
 
564
 
        self.tree1.rename_one('sub/dir/WithCaps.txt', 'temp')
565
 
        self.tree1.rename_one('with space.txt', 'WithCaps.txt')
566
 
        self.tree1.rename_one('temp', 'with space.txt')
567
 
        self.tree1.commit(u'swap filenames', rev_id='a@cset-0-6',
568
 
                          verbose=False)
569
 
        bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
570
 
        other = self.get_checkout('a@cset-0-5')
571
 
        other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
572
 
        other.commit('rename file', rev_id='a@cset-0-6b')
573
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
574
 
        self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
575
 
                          verbose=False)
576
 
        bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
577
 
 
578
 
    def test_symlink_bundle(self):
579
 
        if not has_symlinks():
580
 
            raise TestSkipped("No symlink support")
581
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
582
 
        self.b1 = self.tree1.branch
583
 
        tt = TreeTransform(self.tree1)
584
 
        tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
585
 
        tt.apply()
586
 
        self.tree1.commit('add symlink', rev_id='l@cset-0-1')
587
 
        self.get_valid_bundle(None, 'l@cset-0-1')
588
 
        tt = TreeTransform(self.tree1)
589
 
        trans_id = tt.trans_id_tree_file_id('link-1')
590
 
        tt.adjust_path('link2', tt.root, trans_id)
591
 
        tt.delete_contents(trans_id)
592
 
        tt.create_symlink('mars', trans_id)
593
 
        tt.apply()
594
 
        self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
595
 
        self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
596
 
        tt = TreeTransform(self.tree1)
597
 
        trans_id = tt.trans_id_tree_file_id('link-1')
598
 
        tt.delete_contents(trans_id)
599
 
        tt.create_symlink('jupiter', trans_id)
600
 
        tt.apply()
601
 
        self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
602
 
        self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
603
 
        tt = TreeTransform(self.tree1)
604
 
        trans_id = tt.trans_id_tree_file_id('link-1')
605
 
        tt.delete_contents(trans_id)
606
 
        tt.apply()
607
 
        self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
608
 
        self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
609
 
 
610
 
    def test_binary_bundle(self):
611
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
612
 
        self.b1 = self.tree1.branch
613
 
        tt = TreeTransform(self.tree1)
614
 
        
615
 
        # Add
616
 
        tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
617
 
        tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
618
 
        tt.apply()
619
 
        self.tree1.commit('add binary', rev_id='b@cset-0-1')
620
 
        self.get_valid_bundle(None, 'b@cset-0-1')
621
 
 
622
 
        # Delete
623
 
        tt = TreeTransform(self.tree1)
624
 
        trans_id = tt.trans_id_tree_file_id('binary-1')
625
 
        tt.delete_contents(trans_id)
626
 
        tt.apply()
627
 
        self.tree1.commit('delete binary', rev_id='b@cset-0-2')
628
 
        self.get_valid_bundle('b@cset-0-1', 'b@cset-0-2')
629
 
 
630
 
        # Rename & modify
631
 
        tt = TreeTransform(self.tree1)
632
 
        trans_id = tt.trans_id_tree_file_id('binary-2')
633
 
        tt.adjust_path('file3', tt.root, trans_id)
634
 
        tt.delete_contents(trans_id)
635
 
        tt.create_file('file\rcontents\x00\n\x00', trans_id)
636
 
        tt.apply()
637
 
        self.tree1.commit('rename and modify binary', rev_id='b@cset-0-3')
638
 
        self.get_valid_bundle('b@cset-0-2', 'b@cset-0-3')
639
 
 
640
 
        # Modify
641
 
        tt = TreeTransform(self.tree1)
642
 
        trans_id = tt.trans_id_tree_file_id('binary-2')
643
 
        tt.delete_contents(trans_id)
644
 
        tt.create_file('\x00file\rcontents', trans_id)
645
 
        tt.apply()
646
 
        self.tree1.commit('just modify binary', rev_id='b@cset-0-4')
647
 
        self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
648
 
 
649
 
        # Rollup
650
 
        self.get_valid_bundle(None, 'b@cset-0-4')
651
 
 
652
 
    def test_last_modified(self):
653
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
654
 
        self.b1 = self.tree1.branch
655
 
        tt = TreeTransform(self.tree1)
656
 
        tt.new_file('file', tt.root, 'file', 'file')
657
 
        tt.apply()
658
 
        self.tree1.commit('create file', rev_id='a@lmod-0-1')
659
 
 
660
 
        tt = TreeTransform(self.tree1)
661
 
        trans_id = tt.trans_id_tree_file_id('file')
662
 
        tt.delete_contents(trans_id)
663
 
        tt.create_file('file2', trans_id)
664
 
        tt.apply()
665
 
        self.tree1.commit('modify text', rev_id='a@lmod-0-2a')
666
 
 
667
 
        other = self.get_checkout('a@lmod-0-1')
668
 
        tt = TreeTransform(other)
669
 
        trans_id = tt.trans_id_tree_file_id('file')
670
 
        tt.delete_contents(trans_id)
671
 
        tt.create_file('file2', trans_id)
672
 
        tt.apply()
673
 
        other.commit('modify text in another tree', rev_id='a@lmod-0-2b')
674
 
        merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
675
 
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-3',
676
 
                          verbose=False)
677
 
        self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
678
 
        bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
679
 
 
680
 
    def test_hide_history(self):
681
 
        self.tree1 = BzrDir.create_standalone_workingtree('b1')
682
 
        self.b1 = self.tree1.branch
683
 
 
684
 
        open('b1/one', 'wb').write('one\n')
685
 
        self.tree1.add('one')
686
 
        self.tree1.commit('add file', rev_id='a@cset-0-1')
687
 
        open('b1/one', 'wb').write('two\n')
688
 
        self.tree1.commit('modify', rev_id='a@cset-0-2')
689
 
        open('b1/one', 'wb').write('three\n')
690
 
        self.tree1.commit('modify', rev_id='a@cset-0-3')
691
 
        bundle_file = StringIO()
692
 
        rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
693
 
                               'a@cset-0-1', bundle_file)
694
 
        self.assertNotContainsRe(bundle_file.getvalue(), 'two')
695
 
        self.assertContainsRe(bundle_file.getvalue(), 'one')
696
 
        self.assertContainsRe(bundle_file.getvalue(), 'three')
697
 
 
698
 
    def test_unicode_bundle(self):
699
 
        # Handle international characters
700
 
        os.mkdir('b1')
701
 
        try:
702
 
            f = open(u'b1/with Dod\xe9', 'wb')
703
 
        except UnicodeEncodeError:
704
 
            raise TestSkipped("Filesystem doesn't support unicode")
705
 
 
706
 
        self.tree1 = self.make_branch_and_tree('b1')
707
 
        self.b1 = self.tree1.branch
708
 
 
709
 
        f.write((u'A file\n'
710
 
            u'With international man of mystery\n'
711
 
            u'William Dod\xe9\n').encode('utf-8'))
712
 
        f.close()
713
 
 
714
 
        self.tree1.add([u'with Dod\xe9'])
715
 
        self.tree1.commit(u'i18n commit from William Dod\xe9', 
716
 
                          rev_id='i18n-1', committer=u'William Dod\xe9')
717
 
 
718
 
        # Add
719
 
        bundle = self.get_valid_bundle(None, 'i18n-1')
720
 
 
721
 
        # Modified
722
 
        f = open(u'b1/with Dod\xe9', 'wb')
723
 
        f.write(u'Modified \xb5\n'.encode('utf8'))
724
 
        f.close()
725
 
        self.tree1.commit(u'modified', rev_id='i18n-2')
726
 
 
727
 
        bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
728
 
        
729
 
        # Renamed
730
 
        self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
731
 
        self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
732
 
                          committer=u'Erik B\xe5gfors')
733
 
 
734
 
        bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
735
 
 
736
 
        # Removed
737
 
        self.tree1.remove([u'B\xe5gfors'])
738
 
        self.tree1.commit(u'removed', rev_id='i18n-4')
739
 
 
740
 
        bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
741
 
 
742
 
        # Rollup
743
 
        bundle = self.get_valid_bundle(None, 'i18n-4')
744
 
 
745
 
 
746
 
    def test_whitespace_bundle(self):
747
 
        if sys.platform in ('win32', 'cygwin'):
748
 
            raise TestSkipped('Windows doesn\'t support filenames'
749
 
                              ' with tabs or trailing spaces')
750
 
        self.tree1 = self.make_branch_and_tree('b1')
751
 
        self.b1 = self.tree1.branch
752
 
 
753
 
        self.build_tree(['b1/trailing space '])
754
 
        self.tree1.add(['trailing space '])
755
 
        # TODO: jam 20060701 Check for handling files with '\t' characters
756
 
        #       once we actually support them
757
 
 
758
 
        # Added
759
 
        self.tree1.commit('funky whitespace', rev_id='white-1')
760
 
 
761
 
        bundle = self.get_valid_bundle(None, 'white-1')
762
 
 
763
 
        # Modified
764
 
        open('b1/trailing space ', 'ab').write('add some text\n')
765
 
        self.tree1.commit('add text', rev_id='white-2')
766
 
 
767
 
        bundle = self.get_valid_bundle('white-1', 'white-2')
768
 
 
769
 
        # Renamed
770
 
        self.tree1.rename_one('trailing space ', ' start and end space ')
771
 
        self.tree1.commit('rename', rev_id='white-3')
772
 
 
773
 
        bundle = self.get_valid_bundle('white-2', 'white-3')
774
 
 
775
 
        # Removed
776
 
        self.tree1.remove([' start and end space '])
777
 
        self.tree1.commit('removed', rev_id='white-4')
778
 
 
779
 
        bundle = self.get_valid_bundle('white-3', 'white-4')
780
 
        
781
 
        # Now test a complet roll-up
782
 
        bundle = self.get_valid_bundle(None, 'white-4')
783
 
 
784
 
    def test_alt_timezone_bundle(self):
785
 
        self.tree1 = self.make_branch_and_tree('b1')
786
 
        self.b1 = self.tree1.branch
787
 
 
788
 
        self.build_tree(['b1/newfile'])
789
 
        self.tree1.add(['newfile'])
790
 
 
791
 
        # Asia/Colombo offset = 5 hours 30 minutes
792
 
        self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
793
 
                          timezone=19800, timestamp=1152544886.0)
794
 
 
795
 
        bundle = self.get_valid_bundle(None, 'tz-1')
796
 
        
797
 
        rev = bundle.revisions[0]
798
 
        self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
799
 
        self.assertEqual(19800, rev.timezone)
800
 
        self.assertEqual(1152544886.0, rev.timestamp)
801
 
 
802
 
 
803
 
class MungedBundleTester(TestCaseWithTransport):
804
 
 
805
 
    def build_test_bundle(self):
806
 
        wt = self.make_branch_and_tree('b1')
807
 
 
808
 
        self.build_tree(['b1/one'])
809
 
        wt.add('one')
810
 
        wt.commit('add one', rev_id='a@cset-0-1')
811
 
        self.build_tree(['b1/two'])
812
 
        wt.add('two')
813
 
        wt.commit('add two', rev_id='a@cset-0-2',
814
 
                  revprops={'branch-nick':'test'})
815
 
 
816
 
        bundle_txt = StringIO()
817
 
        rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
818
 
                               'a@cset-0-1', bundle_txt)
819
 
        self.assertEqual(['a@cset-0-2'], rev_ids)
820
 
        bundle_txt.seek(0, 0)
821
 
        return bundle_txt
822
 
 
823
 
    def check_valid(self, bundle):
824
 
        """Check that after whatever munging, the final object is valid."""
825
 
        self.assertEqual(['a@cset-0-2'],
826
 
            [r.revision_id for r in bundle.real_revisions])
827
 
 
828
 
    def test_extra_whitespace(self):
829
 
        bundle_txt = self.build_test_bundle()
830
 
 
831
 
        # Seek to the end of the file
832
 
        # Adding one extra newline used to give us
833
 
        # TypeError: float() argument must be a string or a number
834
 
        bundle_txt.seek(0, 2)
835
 
        bundle_txt.write('\n')
836
 
        bundle_txt.seek(0)
837
 
 
838
 
        bundle = read_bundle(bundle_txt)
839
 
        self.check_valid(bundle)
840
 
 
841
 
    def test_extra_whitespace_2(self):
842
 
        bundle_txt = self.build_test_bundle()
843
 
 
844
 
        # Seek to the end of the file
845
 
        # Adding two extra newlines used to give us
846
 
        # MalformedPatches: The first line of all patches should be ...
847
 
        bundle_txt.seek(0, 2)
848
 
        bundle_txt.write('\n\n')
849
 
        bundle_txt.seek(0)
850
 
 
851
 
        bundle = read_bundle(bundle_txt)
852
 
        self.check_valid(bundle)
853
 
 
854
 
    def test_missing_trailing_whitespace(self):
855
 
        bundle_txt = self.build_test_bundle()
856
 
 
857
 
        # Remove a trailing newline, it shouldn't kill the parser
858
 
        raw = bundle_txt.getvalue()
859
 
        # The contents of the bundle don't have to be this, but this
860
 
        # test is concerned with the exact case where the serializer
861
 
        # creates a blank line at the end, and fails if that
862
 
        # line is stripped
863
 
        self.assertEqual('\n\n', raw[-2:])
864
 
        bundle_txt = StringIO(raw[:-1])
865
 
 
866
 
        bundle = read_bundle(bundle_txt)
867
 
        self.check_valid(bundle)
868
 
 
869
 
    def test_opening_text(self):
870
 
        bundle_txt = self.build_test_bundle()
871
 
 
872
 
        bundle_txt = StringIO("Some random\nemail comments\n"
873
 
                              + bundle_txt.getvalue())
874
 
 
875
 
        bundle = read_bundle(bundle_txt)
876
 
        self.check_valid(bundle)
877
 
 
878
 
    def test_trailing_text(self):
879
 
        bundle_txt = self.build_test_bundle()
880
 
 
881
 
        bundle_txt = StringIO(bundle_txt.getvalue() +
882
 
                              "Some trailing\nrandom\ntext\n")
883
 
 
884
 
        bundle = read_bundle(bundle_txt)
885
 
        self.check_valid(bundle)
886