/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_branch.py

  • Committer: mernst at mit
  • Date: 2008-10-16 10:57:16 UTC
  • mto: This revision was merged to the branch mainline in revision 3799.
  • Revision ID: mernst@csail.mit.edu-20081016105716-v8x8n5t2pf7f6uds
Improved documentation of stacked and lightweight branches

These patches improve the User Guide's documentation of stacked and
lightweight branches.

Section "1.2.6 Putting the concepts together" should mention stacked
branches and the difference between them and lightweight branches.  It
should also contain links to further details of the common scenarios.

Section "5.3.4 Getting a lightweight checkout" should mention stacked
branches as an option, and should link to all the options, not just some of
them.  It should also clarify that lightweight only applies to checkouts,
not to arbitrary branches.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 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
"""Tests for the Branch facility that are not interface  tests.
 
18
 
 
19
For interface tests see tests/branch_implementations/*.py.
 
20
 
 
21
For concrete class tests see this file, and for meta-branch tests
 
22
also see this file.
 
23
"""
 
24
 
 
25
from StringIO import StringIO
 
26
 
 
27
from bzrlib import (
 
28
    branch as _mod_branch,
 
29
    bzrdir,
 
30
    config,
 
31
    errors,
 
32
    trace,
 
33
    urlutils,
 
34
    )
 
35
from bzrlib.branch import (
 
36
    Branch,
 
37
    BranchHooks,
 
38
    BranchFormat,
 
39
    BranchReferenceFormat,
 
40
    BzrBranch5,
 
41
    BzrBranchFormat5,
 
42
    BzrBranchFormat6,
 
43
    PullResult,
 
44
    _run_with_write_locked_target,
 
45
    )
 
46
from bzrlib.bzrdir import (BzrDirMetaFormat1, BzrDirMeta1, 
 
47
                           BzrDir, BzrDirFormat)
 
48
from bzrlib.errors import (NotBranchError,
 
49
                           UnknownFormatError,
 
50
                           UnknownHook,
 
51
                           UnsupportedFormatError,
 
52
                           )
 
53
 
 
54
from bzrlib.tests import TestCase, TestCaseWithTransport
 
55
from bzrlib.transport import get_transport
 
56
 
 
57
 
 
58
class TestDefaultFormat(TestCase):
 
59
 
 
60
    def test_default_format(self):
 
61
        # update this if you change the default branch format
 
62
        self.assertIsInstance(BranchFormat.get_default_format(),
 
63
                BzrBranchFormat6)
 
64
 
 
65
    def test_default_format_is_same_as_bzrdir_default(self):
 
66
        # XXX: it might be nice if there was only one place the default was
 
67
        # set, but at the moment that's not true -- mbp 20070814 -- 
 
68
        # https://bugs.launchpad.net/bzr/+bug/132376
 
69
        self.assertEqual(BranchFormat.get_default_format(),
 
70
                BzrDirFormat.get_default_format().get_branch_format())
 
71
 
 
72
    def test_get_set_default_format(self):
 
73
        # set the format and then set it back again
 
74
        old_format = BranchFormat.get_default_format()
 
75
        BranchFormat.set_default_format(SampleBranchFormat())
 
76
        try:
 
77
            # the default branch format is used by the meta dir format
 
78
            # which is not the default bzrdir format at this point
 
79
            dir = BzrDirMetaFormat1().initialize('memory:///')
 
80
            result = dir.create_branch()
 
81
            self.assertEqual(result, 'A branch')
 
82
        finally:
 
83
            BranchFormat.set_default_format(old_format)
 
84
        self.assertEqual(old_format, BranchFormat.get_default_format())
 
85
 
 
86
 
 
87
class TestBranchFormat5(TestCaseWithTransport):
 
88
    """Tests specific to branch format 5"""
 
89
 
 
90
    def test_branch_format_5_uses_lockdir(self):
 
91
        url = self.get_url()
 
92
        bzrdir = BzrDirMetaFormat1().initialize(url)
 
93
        bzrdir.create_repository()
 
94
        branch = bzrdir.create_branch()
 
95
        t = self.get_transport()
 
96
        self.log("branch instance is %r" % branch)
 
97
        self.assert_(isinstance(branch, BzrBranch5))
 
98
        self.assertIsDirectory('.', t)
 
99
        self.assertIsDirectory('.bzr/branch', t)
 
100
        self.assertIsDirectory('.bzr/branch/lock', t)
 
101
        branch.lock_write()
 
102
        try:
 
103
            self.assertIsDirectory('.bzr/branch/lock/held', t)
 
104
        finally:
 
105
            branch.unlock()
 
106
 
 
107
    def test_set_push_location(self):
 
108
        from bzrlib.config import (locations_config_filename,
 
109
                                   ensure_config_dir_exists)
 
110
        ensure_config_dir_exists()
 
111
        fn = locations_config_filename()
 
112
        # write correct newlines to locations.conf
 
113
        # by default ConfigObj uses native line-endings for new files
 
114
        # but uses already existing line-endings if file is not empty
 
115
        f = open(fn, 'wb')
 
116
        try:
 
117
            f.write('# comment\n')
 
118
        finally:
 
119
            f.close()
 
120
 
 
121
        branch = self.make_branch('.', format='knit')
 
122
        branch.set_push_location('foo')
 
123
        local_path = urlutils.local_path_from_url(branch.base[:-1])
 
124
        self.assertFileEqual("# comment\n"
 
125
                             "[%s]\n"
 
126
                             "push_location = foo\n"
 
127
                             "push_location:policy = norecurse\n" % local_path,
 
128
                             fn)
 
129
 
 
130
    # TODO RBC 20051029 test getting a push location from a branch in a
 
131
    # recursive section - that is, it appends the branch name.
 
132
 
 
133
 
 
134
class SampleBranchFormat(BranchFormat):
 
135
    """A sample format
 
136
 
 
137
    this format is initializable, unsupported to aid in testing the 
 
138
    open and open_downlevel routines.
 
139
    """
 
140
 
 
141
    def get_format_string(self):
 
142
        """See BzrBranchFormat.get_format_string()."""
 
143
        return "Sample branch format."
 
144
 
 
145
    def initialize(self, a_bzrdir):
 
146
        """Format 4 branches cannot be created."""
 
147
        t = a_bzrdir.get_branch_transport(self)
 
148
        t.put_bytes('format', self.get_format_string())
 
149
        return 'A branch'
 
150
 
 
151
    def is_supported(self):
 
152
        return False
 
153
 
 
154
    def open(self, transport, _found=False):
 
155
        return "opened branch."
 
156
 
 
157
 
 
158
class TestBzrBranchFormat(TestCaseWithTransport):
 
159
    """Tests for the BzrBranchFormat facility."""
 
160
 
 
161
    def test_find_format(self):
 
162
        # is the right format object found for a branch?
 
163
        # create a branch with a few known format objects.
 
164
        # this is not quite the same as 
 
165
        self.build_tree(["foo/", "bar/"])
 
166
        def check_format(format, url):
 
167
            dir = format._matchingbzrdir.initialize(url)
 
168
            dir.create_repository()
 
169
            format.initialize(dir)
 
170
            found_format = BranchFormat.find_format(dir)
 
171
            self.failUnless(isinstance(found_format, format.__class__))
 
172
        check_format(BzrBranchFormat5(), "bar")
 
173
        
 
174
    def test_find_format_not_branch(self):
 
175
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
176
        self.assertRaises(NotBranchError,
 
177
                          BranchFormat.find_format,
 
178
                          dir)
 
179
 
 
180
    def test_find_format_unknown_format(self):
 
181
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
182
        SampleBranchFormat().initialize(dir)
 
183
        self.assertRaises(UnknownFormatError,
 
184
                          BranchFormat.find_format,
 
185
                          dir)
 
186
 
 
187
    def test_register_unregister_format(self):
 
188
        format = SampleBranchFormat()
 
189
        # make a control dir
 
190
        dir = bzrdir.BzrDirMetaFormat1().initialize(self.get_url())
 
191
        # make a branch
 
192
        format.initialize(dir)
 
193
        # register a format for it.
 
194
        BranchFormat.register_format(format)
 
195
        # which branch.Open will refuse (not supported)
 
196
        self.assertRaises(UnsupportedFormatError, Branch.open, self.get_url())
 
197
        self.make_branch_and_tree('foo')
 
198
        # but open_downlevel will work
 
199
        self.assertEqual(format.open(dir), bzrdir.BzrDir.open(self.get_url()).open_branch(unsupported=True))
 
200
        # unregister the format
 
201
        BranchFormat.unregister_format(format)
 
202
        self.make_branch_and_tree('bar')
 
203
 
 
204
 
 
205
class TestBranch67(object):
 
206
    """Common tests for both branch 6 and 7 which are mostly the same."""
 
207
 
 
208
    def get_format_name(self):
 
209
        raise NotImplementedError(self.get_format_name)
 
210
 
 
211
    def get_format_name_subtree(self):
 
212
        raise NotImplementedError(self.get_format_name)
 
213
 
 
214
    def get_class(self):
 
215
        raise NotImplementedError(self.get_class)
 
216
 
 
217
    def test_creation(self):
 
218
        format = BzrDirMetaFormat1()
 
219
        format.set_branch_format(_mod_branch.BzrBranchFormat6())
 
220
        branch = self.make_branch('a', format=format)
 
221
        self.assertIsInstance(branch, self.get_class())
 
222
        branch = self.make_branch('b', format=self.get_format_name())
 
223
        self.assertIsInstance(branch, self.get_class())
 
224
        branch = _mod_branch.Branch.open('a')
 
225
        self.assertIsInstance(branch, self.get_class())
 
226
 
 
227
    def test_layout(self):
 
228
        branch = self.make_branch('a', format=self.get_format_name())
 
229
        self.failUnlessExists('a/.bzr/branch/last-revision')
 
230
        self.failIfExists('a/.bzr/branch/revision-history')
 
231
 
 
232
    def test_config(self):
 
233
        """Ensure that all configuration data is stored in the branch"""
 
234
        branch = self.make_branch('a', format=self.get_format_name())
 
235
        branch.set_parent('http://bazaar-vcs.org')
 
236
        self.failIfExists('a/.bzr/branch/parent')
 
237
        self.assertEqual('http://bazaar-vcs.org', branch.get_parent())
 
238
        branch.set_push_location('sftp://bazaar-vcs.org')
 
239
        config = branch.get_config()._get_branch_data_config()
 
240
        self.assertEqual('sftp://bazaar-vcs.org',
 
241
                         config.get_user_option('push_location'))
 
242
        branch.set_bound_location('ftp://bazaar-vcs.org')
 
243
        self.failIfExists('a/.bzr/branch/bound')
 
244
        self.assertEqual('ftp://bazaar-vcs.org', branch.get_bound_location())
 
245
 
 
246
    def test_set_revision_history(self):
 
247
        builder = self.make_branch_builder('.', format=self.get_format_name())
 
248
        builder.build_snapshot('foo', None,
 
249
            [('add', ('', None, 'directory', None))],
 
250
            message='foo')
 
251
        builder.build_snapshot('bar', None, [], message='bar')
 
252
        branch = builder.get_branch()
 
253
        branch.lock_write()
 
254
        self.addCleanup(branch.unlock)
 
255
        branch.set_revision_history(['foo', 'bar'])
 
256
        branch.set_revision_history(['foo'])
 
257
        self.assertRaises(errors.NotLefthandHistory,
 
258
                          branch.set_revision_history, ['bar'])
 
259
 
 
260
    def do_checkout_test(self, lightweight=False):
 
261
        tree = self.make_branch_and_tree('source',
 
262
            format=self.get_format_name_subtree())
 
263
        subtree = self.make_branch_and_tree('source/subtree',
 
264
            format=self.get_format_name_subtree())
 
265
        subsubtree = self.make_branch_and_tree('source/subtree/subsubtree',
 
266
            format=self.get_format_name_subtree())
 
267
        self.build_tree(['source/subtree/file',
 
268
                         'source/subtree/subsubtree/file'])
 
269
        subsubtree.add('file')
 
270
        subtree.add('file')
 
271
        subtree.add_reference(subsubtree)
 
272
        tree.add_reference(subtree)
 
273
        tree.commit('a revision')
 
274
        subtree.commit('a subtree file')
 
275
        subsubtree.commit('a subsubtree file')
 
276
        tree.branch.create_checkout('target', lightweight=lightweight)
 
277
        self.failUnlessExists('target')
 
278
        self.failUnlessExists('target/subtree')
 
279
        self.failUnlessExists('target/subtree/file')
 
280
        self.failUnlessExists('target/subtree/subsubtree/file')
 
281
        subbranch = _mod_branch.Branch.open('target/subtree/subsubtree')
 
282
        if lightweight:
 
283
            self.assertEndsWith(subbranch.base, 'source/subtree/subsubtree/')
 
284
        else:
 
285
            self.assertEndsWith(subbranch.base, 'target/subtree/subsubtree/')
 
286
 
 
287
    def test_checkout_with_references(self):
 
288
        self.do_checkout_test()
 
289
 
 
290
    def test_light_checkout_with_references(self):
 
291
        self.do_checkout_test(lightweight=True)
 
292
 
 
293
    def test_set_push(self):
 
294
        branch = self.make_branch('source', format=self.get_format_name())
 
295
        branch.get_config().set_user_option('push_location', 'old',
 
296
            store=config.STORE_LOCATION)
 
297
        warnings = []
 
298
        def warning(*args):
 
299
            warnings.append(args[0] % args[1:])
 
300
        _warning = trace.warning
 
301
        trace.warning = warning
 
302
        try:
 
303
            branch.set_push_location('new')
 
304
        finally:
 
305
            trace.warning = _warning
 
306
        self.assertEqual(warnings[0], 'Value "new" is masked by "old" from '
 
307
                         'locations.conf')
 
308
 
 
309
 
 
310
class TestBranch6(TestBranch67, TestCaseWithTransport):
 
311
 
 
312
    def get_class(self):
 
313
        return _mod_branch.BzrBranch6
 
314
 
 
315
    def get_format_name(self):
 
316
        return "dirstate-tags"
 
317
 
 
318
    def get_format_name_subtree(self):
 
319
        return "dirstate-with-subtree"
 
320
 
 
321
    def test_set_stacked_on_url_errors(self):
 
322
        branch = self.make_branch('a', format=self.get_format_name())
 
323
        self.assertRaises(errors.UnstackableBranchFormat,
 
324
            branch.set_stacked_on_url, None)
 
325
 
 
326
    def test_default_stacked_location(self):
 
327
        branch = self.make_branch('a', format=self.get_format_name())
 
328
        self.assertRaises(errors.UnstackableBranchFormat, branch.get_stacked_on_url)
 
329
 
 
330
 
 
331
class TestBranch7(TestBranch67, TestCaseWithTransport):
 
332
 
 
333
    def get_class(self):
 
334
        return _mod_branch.BzrBranch7
 
335
 
 
336
    def get_format_name(self):
 
337
        return "development"
 
338
 
 
339
    def get_format_name_subtree(self):
 
340
        return "development-subtree"
 
341
 
 
342
    def test_set_stacked_on_url_unstackable_repo(self):
 
343
        repo = self.make_repository('a', format='dirstate-tags')
 
344
        control = repo.bzrdir
 
345
        branch = _mod_branch.BzrBranchFormat7().initialize(control)
 
346
        target = self.make_branch('b')
 
347
        self.assertRaises(errors.UnstackableRepositoryFormat,
 
348
            branch.set_stacked_on_url, target.base)
 
349
 
 
350
    def test_clone_stacked_on_unstackable_repo(self):
 
351
        repo = self.make_repository('a', format='dirstate-tags')
 
352
        control = repo.bzrdir
 
353
        branch = _mod_branch.BzrBranchFormat7().initialize(control)
 
354
        # Calling clone should not raise UnstackableRepositoryFormat.
 
355
        cloned_bzrdir = control.clone('cloned')
 
356
 
 
357
    def _test_default_stacked_location(self):
 
358
        branch = self.make_branch('a', format=self.get_format_name())
 
359
        self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
 
360
 
 
361
    def test_stack_and_unstack(self):
 
362
        branch = self.make_branch('a', format=self.get_format_name())
 
363
        target = self.make_branch_and_tree('b', format=self.get_format_name())
 
364
        branch.set_stacked_on_url(target.branch.base)
 
365
        self.assertEqual(target.branch.base, branch.get_stacked_on_url())
 
366
        revid = target.commit('foo')
 
367
        self.assertTrue(branch.repository.has_revision(revid))
 
368
        branch.set_stacked_on_url(None)
 
369
        self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
 
370
        self.assertFalse(branch.repository.has_revision(revid))
 
371
 
 
372
    def test_open_opens_stacked_reference(self):
 
373
        branch = self.make_branch('a', format=self.get_format_name())
 
374
        target = self.make_branch_and_tree('b', format=self.get_format_name())
 
375
        branch.set_stacked_on_url(target.branch.base)
 
376
        branch = branch.bzrdir.open_branch()
 
377
        revid = target.commit('foo')
 
378
        self.assertTrue(branch.repository.has_revision(revid))
 
379
 
 
380
 
 
381
class TestBranchReference(TestCaseWithTransport):
 
382
    """Tests for the branch reference facility."""
 
383
 
 
384
    def test_create_open_reference(self):
 
385
        bzrdirformat = bzrdir.BzrDirMetaFormat1()
 
386
        t = get_transport(self.get_url('.'))
 
387
        t.mkdir('repo')
 
388
        dir = bzrdirformat.initialize(self.get_url('repo'))
 
389
        dir.create_repository()
 
390
        target_branch = dir.create_branch()
 
391
        t.mkdir('branch')
 
392
        branch_dir = bzrdirformat.initialize(self.get_url('branch'))
 
393
        made_branch = BranchReferenceFormat().initialize(branch_dir, target_branch)
 
394
        self.assertEqual(made_branch.base, target_branch.base)
 
395
        opened_branch = branch_dir.open_branch()
 
396
        self.assertEqual(opened_branch.base, target_branch.base)
 
397
 
 
398
    def test_get_reference(self):
 
399
        """For a BranchReference, get_reference should reutrn the location."""
 
400
        branch = self.make_branch('target')
 
401
        checkout = branch.create_checkout('checkout', lightweight=True)
 
402
        reference_url = branch.bzrdir.root_transport.abspath('') + '/'
 
403
        # if the api for create_checkout changes to return different checkout types
 
404
        # then this file read will fail.
 
405
        self.assertFileEqual(reference_url, 'checkout/.bzr/branch/location')
 
406
        self.assertEqual(reference_url,
 
407
            _mod_branch.BranchReferenceFormat().get_reference(checkout.bzrdir))
 
408
 
 
409
 
 
410
class TestHooks(TestCase):
 
411
 
 
412
    def test_constructor(self):
 
413
        """Check that creating a BranchHooks instance has the right defaults."""
 
414
        hooks = BranchHooks()
 
415
        self.assertTrue("set_rh" in hooks, "set_rh not in %s" % hooks)
 
416
        self.assertTrue("post_push" in hooks, "post_push not in %s" % hooks)
 
417
        self.assertTrue("post_commit" in hooks, "post_commit not in %s" % hooks)
 
418
        self.assertTrue("pre_commit" in hooks, "pre_commit not in %s" % hooks)
 
419
        self.assertTrue("post_pull" in hooks, "post_pull not in %s" % hooks)
 
420
        self.assertTrue("post_uncommit" in hooks, "post_uncommit not in %s" % hooks)
 
421
        self.assertTrue("post_change_branch_tip" in hooks,
 
422
                        "post_change_branch_tip not in %s" % hooks)
 
423
 
 
424
    def test_installed_hooks_are_BranchHooks(self):
 
425
        """The installed hooks object should be a BranchHooks."""
 
426
        # the installed hooks are saved in self._preserved_hooks.
 
427
        self.assertIsInstance(self._preserved_hooks[_mod_branch.Branch], BranchHooks)
 
428
 
 
429
 
 
430
class TestPullResult(TestCase):
 
431
 
 
432
    def test_pull_result_to_int(self):
 
433
        # to support old code, the pull result can be used as an int
 
434
        r = PullResult()
 
435
        r.old_revno = 10
 
436
        r.new_revno = 20
 
437
        # this usage of results is not recommended for new code (because it
 
438
        # doesn't describe very well what happened), but for api stability
 
439
        # it's still supported
 
440
        a = "%d revisions pulled" % r
 
441
        self.assertEqual(a, "10 revisions pulled")
 
442
 
 
443
 
 
444
 
 
445
class _StubLockable(object):
 
446
    """Helper for TestRunWithWriteLockedTarget."""
 
447
 
 
448
    def __init__(self, calls, unlock_exc=None):
 
449
        self.calls = calls
 
450
        self.unlock_exc = unlock_exc
 
451
 
 
452
    def lock_write(self):
 
453
        self.calls.append('lock_write')
 
454
    
 
455
    def unlock(self):
 
456
        self.calls.append('unlock')
 
457
        if self.unlock_exc is not None:
 
458
            raise self.unlock_exc
 
459
 
 
460
 
 
461
class _ErrorFromCallable(Exception):
 
462
    """Helper for TestRunWithWriteLockedTarget."""
 
463
 
 
464
 
 
465
class _ErrorFromUnlock(Exception):
 
466
    """Helper for TestRunWithWriteLockedTarget."""
 
467
 
 
468
 
 
469
class TestRunWithWriteLockedTarget(TestCase):
 
470
    """Tests for _run_with_write_locked_target."""
 
471
 
 
472
    def setUp(self):
 
473
        self._calls = []
 
474
 
 
475
    def func_that_returns_ok(self):
 
476
        self._calls.append('func called')
 
477
        return 'ok'
 
478
 
 
479
    def func_that_raises(self):
 
480
        self._calls.append('func called')
 
481
        raise _ErrorFromCallable()
 
482
 
 
483
    def test_success_unlocks(self):
 
484
        lockable = _StubLockable(self._calls)
 
485
        result = _run_with_write_locked_target(
 
486
            lockable, self.func_that_returns_ok)
 
487
        self.assertEqual('ok', result)
 
488
        self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
 
489
 
 
490
    def test_exception_unlocks_and_propagates(self):
 
491
        lockable = _StubLockable(self._calls)
 
492
        self.assertRaises(_ErrorFromCallable,
 
493
            _run_with_write_locked_target, lockable, self.func_that_raises)
 
494
        self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
 
495
 
 
496
    def test_callable_succeeds_but_error_during_unlock(self):
 
497
        lockable = _StubLockable(self._calls, unlock_exc=_ErrorFromUnlock())
 
498
        self.assertRaises(_ErrorFromUnlock,
 
499
            _run_with_write_locked_target, lockable, self.func_that_returns_ok)
 
500
        self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
 
501
 
 
502
    def test_error_during_unlock_does_not_mask_original_error(self):
 
503
        lockable = _StubLockable(self._calls, unlock_exc=_ErrorFromUnlock())
 
504
        self.assertRaises(_ErrorFromCallable,
 
505
            _run_with_write_locked_target, lockable, self.func_that_raises)
 
506
        self.assertEqual(['lock_write', 'func called', 'unlock'], self._calls)
 
507
 
 
508