1# Copyright (C) 2008-2012, 2016 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17"""Tests for Branch.get_stacked_on_url and set_stacked_on_url."""
18
19from breezy import (
20    branch as _mod_branch,
21    controldir,
22    check,
23    errors,
24    )
25from breezy.revision import NULL_REVISION
26from breezy.tests import fixtures, TestNotApplicable, transport_util
27from breezy.tests.per_branch import TestCaseWithBranch
28
29
30unstackable_format_errors = (
31    _mod_branch.UnstackableBranchFormat,
32    errors.UnstackableRepositoryFormat,
33    )
34
35
36class TestStacking(TestCaseWithBranch):
37
38    def check_lines_added_or_present(self, stacked_branch, revid):
39        # similar to a failure seen in bug 288751 by mbp 20081120
40        stacked_repo = stacked_branch.repository
41        with stacked_repo.lock_read():
42            list(stacked_repo.inventories.iter_lines_added_or_present_in_keys(
43                [(revid,)]))
44
45    def test_get_set_stacked_on_url(self):
46        # branches must either:
47        # raise UnstackableBranchFormat or
48        # raise UnstackableRepositoryFormat or
49        # permit stacking to be done and then return the stacked location.
50        branch = self.make_branch('branch')
51        target = self.make_branch('target')
52        try:
53            branch.set_stacked_on_url(target.base)
54        except unstackable_format_errors:
55            # if the set failed, so must the get
56            self.assertRaises(unstackable_format_errors,
57                              branch.get_stacked_on_url)
58            self.assertFalse(branch._format.supports_stacking())
59            return
60        self.assertTrue(branch._format.supports_stacking())
61        # now we have a stacked branch:
62        self.assertEqual(target.base, branch.get_stacked_on_url())
63        branch.set_stacked_on_url(None)
64        self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
65
66    def test_get_set_stacked_on_relative(self):
67        # Branches can be stacked on other branches using relative paths.
68        branch = self.make_branch('branch')
69        target = self.make_branch('target')
70        try:
71            branch.set_stacked_on_url('../target')
72        except unstackable_format_errors:
73            # if the set failed, so must the get
74            self.assertRaises(unstackable_format_errors,
75                              branch.get_stacked_on_url)
76            return
77        self.assertEqual('../target', branch.get_stacked_on_url())
78
79    def test_set_stacked_on_same_branch_raises(self):
80        # Stacking on the same branch silently raises and doesn't execute the
81        # change. Reported in bug 376243.
82        branch = self.make_branch('branch')
83        try:
84            self.assertRaises(errors.UnstackableLocationError,
85                              branch.set_stacked_on_url, '../branch')
86        except unstackable_format_errors:
87            # if the set failed, so must the get
88            self.assertRaises(unstackable_format_errors,
89                              branch.get_stacked_on_url)
90            return
91        self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
92
93    def test_set_stacked_on_same_branch_after_being_stacked_raises(self):
94        # Stacking on the same branch silently raises and doesn't execute the
95        # change.
96        branch = self.make_branch('branch')
97        target = self.make_branch('target')
98        try:
99            branch.set_stacked_on_url('../target')
100        except unstackable_format_errors:
101            # if the set failed, so must the get
102            self.assertRaises(unstackable_format_errors,
103                              branch.get_stacked_on_url)
104            return
105        self.assertRaises(errors.UnstackableLocationError,
106                          branch.set_stacked_on_url, '../branch')
107        self.assertEqual('../target', branch.get_stacked_on_url())
108
109    def assertRevisionInRepository(self, repo_path, revid):
110        """Check that a revision is in a repository, disregarding stacking."""
111        repo = controldir.ControlDir.open(repo_path).open_repository()
112        self.assertTrue(repo.has_revision(revid))
113
114    def assertRevisionNotInRepository(self, repo_path, revid):
115        """Check that a revision is not in a repository, disregarding stacking."""
116        repo = controldir.ControlDir.open(repo_path).open_repository()
117        self.assertFalse(repo.has_revision(revid))
118
119    def test_get_graph_stacked(self):
120        """A stacked repository shows the graph of its parent."""
121        trunk_tree = self.make_branch_and_tree('mainline')
122        trunk_revid = trunk_tree.commit('mainline')
123        # make a new branch, and stack on the existing one.  we don't use
124        # sprout(stacked=True) here because if that is buggy and copies data
125        # it would cause a false pass of this test.
126        new_branch = self.make_branch('new_branch')
127        try:
128            new_branch.set_stacked_on_url(trunk_tree.branch.base)
129        except unstackable_format_errors as e:
130            raise TestNotApplicable(e)
131        # reading the graph from the stacked branch's repository should see
132        # data from the stacked-on branch
133        new_repo = new_branch.repository
134        with new_repo.lock_read():
135            self.assertEqual(new_repo.get_parent_map([trunk_revid]),
136                             {trunk_revid: (NULL_REVISION, )})
137
138    def test_sprout_stacked(self):
139        # We have a mainline
140        trunk_tree = self.make_branch_and_tree('mainline')
141        trunk_revid = trunk_tree.commit('mainline')
142        # and make branch from it which is stacked
143        try:
144            new_dir = trunk_tree.controldir.sprout('newbranch', stacked=True)
145        except unstackable_format_errors as e:
146            raise TestNotApplicable(e)
147        # stacked repository
148        self.assertRevisionNotInRepository('newbranch', trunk_revid)
149        tree = new_dir.open_branch().create_checkout('local')
150        new_branch_revid = tree.commit('something local')
151        self.assertRevisionNotInRepository(
152            trunk_tree.branch.base, new_branch_revid)
153        self.assertRevisionInRepository('newbranch', new_branch_revid)
154
155    def test_sprout_stacked_from_smart_server(self):
156        # We have a mainline
157        trunk_tree = self.make_branch_and_tree('mainline')
158        trunk_revid = trunk_tree.commit('mainline')
159        # Make sure that we can make a stacked branch from it
160        try:
161            trunk_tree.controldir.sprout('testbranch', stacked=True)
162        except unstackable_format_errors as e:
163            raise TestNotApplicable(e)
164        # Now serve the original mainline from a smart server
165        remote_transport = self.make_smart_server('mainline')
166        remote_bzrdir = controldir.ControlDir.open_from_transport(
167            remote_transport)
168        # and make branch from the smart server which is stacked
169        new_dir = remote_bzrdir.sprout('newbranch', stacked=True)
170        # stacked repository
171        self.assertRevisionNotInRepository('newbranch', trunk_revid)
172        tree = new_dir.open_branch().create_checkout('local')
173        new_branch_revid = tree.commit('something local')
174        self.assertRevisionNotInRepository(trunk_tree.branch.user_url,
175                                           new_branch_revid)
176        self.assertRevisionInRepository('newbranch', new_branch_revid)
177
178    def test_unstack_fetches(self):
179        """Removing the stacked-on branch pulls across all data"""
180        try:
181            builder = self.make_branch_builder('trunk')
182        except errors.UninitializableFormat:
183            raise TestNotApplicable('uninitializeable format')
184        # We have a mainline
185        trunk, mainline_revid, rev2 = fixtures.build_branch_with_non_ancestral_rev(
186            builder)
187        # and make branch from it which is stacked (with no tags)
188        try:
189            new_dir = trunk.controldir.sprout(
190                self.get_url('newbranch'), stacked=True)
191        except unstackable_format_errors as e:
192            raise TestNotApplicable(e)
193        # stacked repository
194        self.assertRevisionNotInRepository('newbranch', mainline_revid)
195        # TODO: we'd like to commit in the stacked repository; that requires
196        # some care (maybe a BranchBuilder) if it's remote and has no
197        # workingtree
198        # newbranch_revid = new_dir.open_workingtree().commit('revision in '
199        # 'newbranch')
200        # now when we unstack that should implicitly fetch, to make sure that
201        # the branch will still work
202        new_branch = new_dir.open_branch()
203        try:
204            new_branch.tags.set_tag('tag-a', rev2)
205        except errors.TagsNotSupported:
206            tags_supported = False
207        else:
208            tags_supported = True
209        new_branch.set_stacked_on_url(None)
210        self.assertRevisionInRepository('newbranch', mainline_revid)
211        # of course it's still in the mainline
212        self.assertRevisionInRepository('trunk', mainline_revid)
213        if tags_supported:
214            # the tagged revision in trunk is now in newbranch too
215            self.assertRevisionInRepository('newbranch', rev2)
216        # and now we're no longer stacked
217        self.assertRaises(errors.NotStacked, new_branch.get_stacked_on_url)
218
219    def test_unstack_already_locked(self):
220        """Removing the stacked-on branch with an already write-locked branch
221        works.
222
223        This was bug 551525.
224        """
225        try:
226            stacked_bzrdir = self.make_stacked_bzrdir()
227        except unstackable_format_errors as e:
228            raise TestNotApplicable(e)
229        stacked_branch = stacked_bzrdir.open_branch()
230        stacked_branch.lock_write()
231        stacked_branch.set_stacked_on_url(None)
232        stacked_branch.unlock()
233
234    def test_unstack_already_multiple_locked(self):
235        """Unstacking a branch preserves the lock count (even though it
236        replaces the br.repository object).
237
238        This is a more extreme variation of test_unstack_already_locked.
239        """
240        try:
241            stacked_bzrdir = self.make_stacked_bzrdir()
242        except unstackable_format_errors as e:
243            raise TestNotApplicable(e)
244        stacked_branch = stacked_bzrdir.open_branch()
245        stacked_branch.lock_write()
246        stacked_branch.lock_write()
247        stacked_branch.lock_write()
248        stacked_branch.set_stacked_on_url(None)
249        stacked_branch.unlock()
250        stacked_branch.unlock()
251        stacked_branch.unlock()
252
253    def make_stacked_bzrdir(self, in_directory=None):
254        """Create a stacked branch and return its bzrdir.
255
256        :param in_directory: If not None, create a directory of this
257            name and create the stacking and stacked-on bzrdirs in
258            this directory.
259        """
260        if in_directory is not None:
261            self.get_transport().mkdir(in_directory)
262            prefix = in_directory + '/'
263        else:
264            prefix = ''
265        tree = self.make_branch_and_tree(prefix + 'stacked-on')
266        tree.commit('Added foo')
267        stacked_bzrdir = tree.branch.controldir.sprout(
268            self.get_url(prefix + 'stacked'), tree.branch.last_revision(),
269            stacked=True)
270        return stacked_bzrdir
271
272    def test_clone_from_stacked_branch_preserve_stacking(self):
273        # We can clone from the bzrdir of a stacked branch. If
274        # preserve_stacking is True, the cloned branch is stacked on the
275        # same branch as the original.
276        try:
277            stacked_bzrdir = self.make_stacked_bzrdir()
278        except unstackable_format_errors as e:
279            raise TestNotApplicable(e)
280        cloned_bzrdir = stacked_bzrdir.clone('cloned', preserve_stacking=True)
281        try:
282            self.assertEqual(
283                stacked_bzrdir.open_branch().get_stacked_on_url(),
284                cloned_bzrdir.open_branch().get_stacked_on_url())
285        except unstackable_format_errors as e:
286            pass
287
288    def test_clone_from_branch_stacked_on_relative_url_preserve_stacking(self):
289        # If a branch's stacked-on url is relative, we can still clone
290        # from it with preserve_stacking True and get a branch stacked
291        # on an appropriately adjusted relative url.
292        try:
293            stacked_bzrdir = self.make_stacked_bzrdir(in_directory='dir')
294        except unstackable_format_errors as e:
295            raise TestNotApplicable(e)
296        stacked_bzrdir.open_branch().set_stacked_on_url('../stacked-on')
297        cloned_bzrdir = stacked_bzrdir.clone(
298            self.get_url('cloned'), preserve_stacking=True)
299        self.assertEqual(
300            '../dir/stacked-on',
301            cloned_bzrdir.open_branch().get_stacked_on_url())
302
303    def test_clone_from_stacked_branch_no_preserve_stacking(self):
304        try:
305            stacked_bzrdir = self.make_stacked_bzrdir()
306        except unstackable_format_errors as e:
307            # not a testable combination.
308            raise TestNotApplicable(e)
309        cloned_unstacked_bzrdir = stacked_bzrdir.clone('cloned-unstacked',
310                                                       preserve_stacking=False)
311        unstacked_branch = cloned_unstacked_bzrdir.open_branch()
312        self.assertRaises((errors.NotStacked, _mod_branch.UnstackableBranchFormat),
313                          unstacked_branch.get_stacked_on_url)
314
315    def test_no_op_preserve_stacking(self):
316        """With no stacking, preserve_stacking should be a no-op."""
317        branch = self.make_branch('source')
318        cloned_bzrdir = branch.controldir.clone(
319            'cloned', preserve_stacking=True)
320        self.assertRaises((errors.NotStacked, _mod_branch.UnstackableBranchFormat),
321                          cloned_bzrdir.open_branch().get_stacked_on_url)
322
323    def make_stacked_on_matching(self, source):
324        if source.repository.supports_rich_root():
325            if source.repository._format.supports_chks:
326                format = "2a"
327            else:
328                format = "1.9-rich-root"
329        else:
330            format = "1.9"
331        return self.make_branch('stack-on', format)
332
333    def test_sprout_stacking_policy_handling(self):
334        """Obey policy where possible, ignore otherwise."""
335        if self.bzrdir_format.fixed_components:
336            raise TestNotApplicable('Branch format 4 does not autoupgrade.')
337        source = self.make_branch('source')
338        stack_on = self.make_stacked_on_matching(source)
339        parent_bzrdir = self.make_controldir('.', format='default')
340        parent_bzrdir.get_config().set_default_stack_on('stack-on')
341        target = source.controldir.sprout('target').open_branch()
342        # When we sprout we upgrade the branch when there is a default stack_on
343        # set by a config *and* the targeted branch supports stacking.
344        if stack_on._format.supports_stacking():
345            self.assertEqual('../stack-on', target.get_stacked_on_url())
346        else:
347            self.assertRaises(
348                branch.UnstackableBranchFormat, target.get_stacked_on_url)
349
350    def test_clone_stacking_policy_handling(self):
351        """Obey policy where possible, ignore otherwise."""
352        if self.bzrdir_format.fixed_components:
353            raise TestNotApplicable('Branch format 4 does not autoupgrade.')
354        source = self.make_branch('source')
355        stack_on = self.make_stacked_on_matching(source)
356        parent_bzrdir = self.make_controldir('.', format='default')
357        parent_bzrdir.get_config().set_default_stack_on('stack-on')
358        target = source.controldir.clone('target').open_branch()
359        # When we clone we upgrade the branch when there is a default stack_on
360        # set by a config *and* the targeted branch supports stacking.
361        if stack_on._format.supports_stacking():
362            self.assertEqual('../stack-on', target.get_stacked_on_url())
363        else:
364            self.assertRaises(
365                _mod_branch.UnstackableBranchFormat, target.get_stacked_on_url)
366
367    def test_sprout_to_smart_server_stacking_policy_handling(self):
368        """Obey policy where possible, ignore otherwise."""
369        if not self.branch_format.supports_leaving_lock():
370            raise TestNotApplicable('Branch format is not usable via HPSS.')
371        source = self.make_branch('source')
372        stack_on = self.make_stacked_on_matching(source)
373        parent_bzrdir = self.make_controldir('.', format='default')
374        parent_bzrdir.get_config().set_default_stack_on('stack-on')
375        url = self.make_smart_server('target').base
376        target = source.controldir.sprout(url).open_branch()
377        # When we sprout we upgrade the branch when there is a default stack_on
378        # set by a config *and* the targeted branch supports stacking.
379        if stack_on._format.supports_stacking():
380            self.assertEqual('../stack-on', target.get_stacked_on_url())
381        else:
382            self.assertRaises(
383                _mod_branch.UnstackableBranchFormat, target.get_stacked_on_url)
384
385    def prepare_stacked_on_fetch(self):
386        stack_on = self.make_branch_and_tree('stack-on')
387        rev1 = stack_on.commit('first commit')
388        try:
389            stacked_dir = stack_on.controldir.sprout('stacked', stacked=True)
390        except unstackable_format_errors as e:
391            raise TestNotApplicable('Format does not support stacking.')
392        unstacked = self.make_repository('unstacked')
393        return stacked_dir.open_workingtree(), unstacked, rev1
394
395    def test_fetch_copies_from_stacked_on(self):
396        stacked, unstacked, rev1 = self.prepare_stacked_on_fetch()
397        unstacked.fetch(stacked.branch.repository, rev1)
398        unstacked.get_revision(rev1)
399
400    def test_fetch_copies_from_stacked_on_and_stacked(self):
401        stacked, unstacked, rev1 = self.prepare_stacked_on_fetch()
402        tree = stacked.branch.create_checkout('local')
403        rev2 = tree.commit('second commit')
404        unstacked.fetch(stacked.branch.repository, rev2)
405        unstacked.get_revision(rev1)
406        unstacked.get_revision(rev2)
407        self.check_lines_added_or_present(stacked.branch, rev1)
408        self.check_lines_added_or_present(stacked.branch, rev2)
409
410    def test_autopack_when_stacked(self):
411        # in bzr.dev as of 20080730, autopack was reported to fail in stacked
412        # repositories because of problems with text deltas spanning physical
413        # repository boundaries.  however, i didn't actually get this test to
414        # fail on that code. -- mbp
415        # see https://bugs.launchpad.net/bzr/+bug/252821
416        stack_on = self.make_branch_and_tree('stack-on')
417        if not stack_on.branch._format.supports_stacking():
418            raise TestNotApplicable("%r does not support stacking"
419                                    % self.branch_format)
420        text_lines = [b'line %d blah blah blah\n' % i for i in range(20)]
421        self.build_tree_contents([('stack-on/a', b''.join(text_lines))])
422        stack_on.add('a')
423        stack_on.commit('base commit')
424        stacked_dir = stack_on.controldir.sprout('stacked', stacked=True)
425        stacked_branch = stacked_dir.open_branch()
426        local_tree = stack_on.controldir.sprout('local').open_workingtree()
427        for i in range(20):
428            text_lines[0] = b'changed in %d\n' % i
429            self.build_tree_contents([('local/a', b''.join(text_lines))])
430            local_tree.commit('commit %d' % i)
431            local_tree.branch.push(stacked_branch)
432        stacked_branch.repository.pack()
433        check.check_dwim(stacked_branch.base, False, True, True)
434
435    def test_pull_delta_when_stacked(self):
436        if not self.branch_format.supports_stacking():
437            raise TestNotApplicable("%r does not support stacking"
438                                    % self.branch_format)
439        stack_on = self.make_branch_and_tree('stack-on')
440        text_lines = [b'line %d blah blah blah\n' % i for i in range(20)]
441        self.build_tree_contents([('stack-on/a', b''.join(text_lines))])
442        stack_on.add('a')
443        stack_on.commit('base commit')
444        # make a stacked branch from the mainline
445        stacked_dir = stack_on.controldir.sprout('stacked', stacked=True)
446        stacked_tree = stacked_dir.open_workingtree()
447        # make a second non-stacked branch from the mainline
448        other_dir = stack_on.controldir.sprout('other')
449        other_tree = other_dir.open_workingtree()
450        text_lines[9] = b'changed in other\n'
451        self.build_tree_contents([('other/a', b''.join(text_lines))])
452        stacked_revid = other_tree.commit('commit in other')
453        # this should have generated a delta; try to pull that across
454        # bug 252821 caused a RevisionNotPresent here...
455        stacked_tree.pull(other_tree.branch)
456        stacked_tree.branch.repository.pack()
457        check.check_dwim(stacked_tree.branch.base, False, True, True)
458        self.check_lines_added_or_present(stacked_tree.branch, stacked_revid)
459
460    def test_fetch_revisions_with_file_changes(self):
461        # Fetching revisions including file changes into a stacked branch
462        # works without error.
463        # Make the source tree.
464        src_tree = self.make_branch_and_tree('src')
465        self.build_tree_contents([('src/a', b'content')])
466        src_tree.add('a')
467        src_tree.commit('first commit')
468
469        # Make the stacked-on branch.
470        src_tree.controldir.sprout('stacked-on')
471
472        # Make a branch stacked on it.
473        target = self.make_branch('target')
474        try:
475            target.set_stacked_on_url('../stacked-on')
476        except unstackable_format_errors as e:
477            raise TestNotApplicable('Format does not support stacking.')
478
479        # Change the source branch.
480        self.build_tree_contents([('src/a', b'new content')])
481        rev2 = src_tree.commit('second commit')
482
483        # Fetch changes to the target.
484        target.fetch(src_tree.branch)
485        rtree = target.repository.revision_tree(rev2)
486        rtree.lock_read()
487        self.addCleanup(rtree.unlock)
488        self.assertEqual(b'new content', rtree.get_file_text('a'))
489        self.check_lines_added_or_present(target, rev2)
490
491    def test_transform_fallback_location_hook(self):
492        # The 'transform_fallback_location' branch hook allows us to inspect
493        # and transform the URL of the fallback location for the branch.
494        stack_on = self.make_branch('stack-on')
495        stacked = self.make_branch('stacked')
496        try:
497            stacked.set_stacked_on_url('../stack-on')
498        except unstackable_format_errors as e:
499            raise TestNotApplicable('Format does not support stacking.')
500        self.get_transport().rename('stack-on', 'new-stack-on')
501        hook_calls = []
502
503        def hook(stacked_branch, url):
504            hook_calls.append(url)
505            return '../new-stack-on'
506        _mod_branch.Branch.hooks.install_named_hook(
507            'transform_fallback_location', hook, None)
508        _mod_branch.Branch.open('stacked')
509        self.assertEqual(['../stack-on'], hook_calls)
510
511    def test_stack_on_repository_branch(self):
512        # Stacking should work when the repo isn't co-located with the
513        # stack-on branch.
514        try:
515            repo = self.make_repository('repo', shared=True)
516        except errors.IncompatibleFormat:
517            raise TestNotApplicable()
518        if not repo._format.supports_nesting_repositories:
519            raise TestNotApplicable()
520        # Avoid make_branch, which produces standalone branches.
521        bzrdir = self.make_controldir('repo/stack-on')
522        try:
523            b = bzrdir.create_branch()
524        except errors.UninitializableFormat:
525            raise TestNotApplicable()
526        transport = self.get_transport('stacked')
527        b.controldir.clone_on_transport(transport, stacked_on=b.base)
528        # Ensure that opening the branch doesn't raise.
529        _mod_branch.Branch.open(transport.base)
530
531    def test_revision_history_of_stacked(self):
532        # See <https://launchpad.net/bugs/380314>.
533        stack_on = self.make_branch_and_tree('stack-on')
534        rev1 = stack_on.commit('first commit')
535        try:
536            stacked_dir = stack_on.controldir.sprout(
537                self.get_url('stacked'), stacked=True)
538        except unstackable_format_errors as e:
539            raise TestNotApplicable('Format does not support stacking.')
540        try:
541            stacked = stacked_dir.open_workingtree()
542        except errors.NoWorkingTree:
543            stacked = stacked_dir.open_branch().create_checkout(
544                'stacked-checkout', lightweight=True)
545        tree = stacked.branch.create_checkout('local')
546        rev2 = tree.commit('second commit')
547        # Sanity check: stacked's repo should not contain rev1, otherwise this
548        # test isn't testing what it's supposed to.
549        repo = stacked.branch.repository.controldir.open_repository()
550        repo.lock_read()
551        self.addCleanup(repo.unlock)
552        self.assertEqual({}, repo.get_parent_map([rev1]))
553        # revision_history should work, even though the history is spread over
554        # multiple repositories.
555        self.assertEqual((2, rev2), stacked.branch.last_revision_info())
556
557
558class TestStackingConnections(
559        transport_util.TestCaseWithConnectionHookedTransport):
560
561    def setUp(self):
562        super(TestStackingConnections, self).setUp()
563        try:
564            base_tree = self.make_branch_and_tree('base',
565                                                  format=self.bzrdir_format)
566        except errors.UninitializableFormat as e:
567            raise TestNotApplicable(e)
568        stacked = self.make_branch('stacked', format=self.bzrdir_format)
569        try:
570            stacked.set_stacked_on_url(base_tree.branch.base)
571        except unstackable_format_errors as e:
572            raise TestNotApplicable(e)
573        self.rev_base = base_tree.commit('first')
574        stacked.set_last_revision_info(1, self.rev_base)
575        stacked_relative = self.make_branch('stacked_relative',
576                                            format=self.bzrdir_format)
577        stacked_relative.set_stacked_on_url(base_tree.branch.user_url)
578        stacked.set_last_revision_info(1, self.rev_base)
579        self.start_logging_connections()
580
581    def test_open_stacked(self):
582        b = _mod_branch.Branch.open(self.get_url('stacked'))
583        rev = b.repository.get_revision(self.rev_base)
584        self.assertEqual(1, len(self.connections))
585
586    def test_open_stacked_relative(self):
587        b = _mod_branch.Branch.open(self.get_url('stacked_relative'))
588        rev = b.repository.get_revision(self.rev_base)
589        self.assertEqual(1, len(self.connections))
590