1# Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 17"""Tests for Branch.sprout()""" 18 19import os 20from breezy import ( 21 branch as _mod_branch, 22 errors, 23 osutils, 24 revision as _mod_revision, 25 tests, 26 urlutils, 27 ) 28from breezy.bzr import ( 29 branch as _mod_bzrbranch, 30 remote, 31 ) 32from breezy.tests import ( 33 features, 34 ) 35from breezy.tests.per_branch import TestCaseWithBranch 36 37 38class TestSprout(TestCaseWithBranch): 39 40 def test_sprout_branch_nickname(self): 41 # test the nick name is reset always 42 raise tests.TestSkipped('XXX branch sprouting is not yet tested.') 43 44 def test_sprout_branch_parent(self): 45 source = self.make_branch('source') 46 target = source.controldir.sprout(self.get_url('target')).open_branch() 47 self.assertEqual( 48 urlutils.strip_segment_parameters(source.user_url), 49 urlutils.strip_segment_parameters(target.get_parent())) 50 51 def test_sprout_uses_bzrdir_branch_format(self): 52 # branch.sprout(bzrdir) is defined as using the branch format selected 53 # by bzrdir; format preservation is achieved by parameterising the 54 # bzrdir during bzrdir.sprout, which is where stacking compatibility 55 # checks are done. So this test tests that each implementation of 56 # Branch.sprout delegates appropriately to the bzrdir which the 57 # branch is being created in, rather than testing that the result is 58 # in the format that we are testing (which is what would happen if 59 # the branch did not delegate appropriately). 60 if isinstance(self.branch_format, _mod_bzrbranch.BranchReferenceFormat): 61 raise tests.TestNotApplicable('cannot sprout to a reference') 62 # Start with a format that is unlikely to be the target format 63 # We call the super class to allow overriding the format of creation) 64 source = tests.TestCaseWithTransport.make_branch(self, 'old-branch', 65 format='knit') 66 target_bzrdir = self.make_controldir('target') 67 target_bzrdir.create_repository() 68 result_format = self.branch_format 69 if isinstance(target_bzrdir, remote.RemoteBzrDir): 70 # for a remote bzrdir, we need to parameterise it with a branch 71 # format, as, after creation, the newly opened remote objects 72 # do not have one unless a branch was created at the time. 73 # We use branch format 6 because its not the default, and its not 74 # metaweave either. 75 target_bzrdir._format.set_branch_format( 76 _mod_bzrbranch.BzrBranchFormat6()) 77 result_format = target_bzrdir._format.get_branch_format() 78 target = source.sprout(target_bzrdir) 79 if isinstance(target, remote.RemoteBranch): 80 # we have to look at the real branch to see whether RemoteBranch 81 # did the right thing. 82 target._ensure_real() 83 target = target._real_branch 84 if isinstance(result_format, remote.RemoteBranchFormat): 85 # Unwrap a parameterised RemoteBranchFormat for comparison. 86 result_format = result_format._custom_format 87 self.assertIs(result_format.__class__, target._format.__class__) 88 89 def test_sprout_partial(self): 90 # test sprouting with a prefix of the revision-history. 91 # also needs not-on-revision-history behaviour defined. 92 wt_a = self.make_branch_and_tree('a') 93 self.build_tree(['a/one']) 94 wt_a.add(['one']) 95 rev1 = wt_a.commit('commit one') 96 self.build_tree(['a/two']) 97 wt_a.add(['two']) 98 wt_a.commit('commit two') 99 repo_b = self.make_repository('b') 100 repo_a = wt_a.branch.repository 101 repo_a.copy_content_into(repo_b) 102 br_b = wt_a.branch.sprout(repo_b.controldir, revision_id=rev1) 103 self.assertEqual(rev1, br_b.last_revision()) 104 105 def test_sprout_partial_not_in_revision_history(self): 106 """We should be able to sprout from any revision in ancestry.""" 107 wt = self.make_branch_and_tree('source') 108 self.build_tree(['source/a']) 109 wt.add('a') 110 rev1 = wt.commit('rev1') 111 rev2_alt = wt.commit('rev2-alt') 112 wt.set_parent_ids([rev1]) 113 wt.branch.set_last_revision_info(1, rev1) 114 rev2 = wt.commit('rev2') 115 wt.set_parent_ids([rev2, rev2_alt]) 116 wt.commit('rev3') 117 118 repo = self.make_repository('target') 119 repo.fetch(wt.branch.repository) 120 branch2 = wt.branch.sprout(repo.controldir, revision_id=rev2_alt) 121 self.assertEqual((2, rev2_alt), branch2.last_revision_info()) 122 self.assertEqual(rev2_alt, branch2.last_revision()) 123 124 def test_sprout_preserves_tags(self): 125 """Sprout preserves tags, even tags of absent revisions.""" 126 try: 127 builder = self.make_branch_builder('source') 128 except errors.UninitializableFormat: 129 raise tests.TestSkipped('Uninitializable branch format') 130 builder.build_commit(message="Rev 1") 131 source = builder.get_branch() 132 try: 133 source.tags.set_tag('tag-a', b'missing-rev') 134 except (errors.TagsNotSupported, errors.GhostTagsNotSupported): 135 raise tests.TestNotApplicable( 136 'Branch format does not support tags or tags to ghosts.') 137 # Now source has a tag pointing to an absent revision. Sprout it. 138 target_bzrdir = self.make_repository('target').controldir 139 new_branch = source.sprout(target_bzrdir) 140 # The tag is present in the target 141 self.assertEqual(b'missing-rev', new_branch.tags.lookup_tag('tag-a')) 142 143 def test_sprout_from_any_repo_revision(self): 144 """We should be able to sprout from any revision.""" 145 wt = self.make_branch_and_tree('source') 146 self.build_tree(['source/a']) 147 wt.add('a') 148 rev1a = wt.commit('rev1a') 149 # simulated uncommit 150 wt.branch.set_last_revision_info(0, _mod_revision.NULL_REVISION) 151 wt.set_last_revision(_mod_revision.NULL_REVISION) 152 wt.revert() 153 wt.commit('rev1b') 154 wt2 = wt.controldir.sprout( 155 'target', revision_id=rev1a).open_workingtree() 156 self.assertEqual(rev1a, wt2.last_revision()) 157 self.assertPathExists('target/a') 158 159 def test_sprout_with_unicode_symlink(self): 160 # this tests bug #272444 161 # Since the trigger function seems to be set_parent_trees, there exists 162 # also a similar test, with name test_unicode_symlink, in class 163 # TestSetParents at file per_workingtree/test_parents.py 164 self.requireFeature(features.SymlinkFeature) 165 self.requireFeature(features.UnicodeFilenameFeature) 166 167 tree = self.make_branch_and_tree('tree1') 168 169 # The link points to a file whose name is an omega 170 # U+03A9 GREEK CAPITAL LETTER OMEGA 171 # UTF-8: ce a9 UTF-16BE: 03a9 Decimal: Ω 172 target = u'\u03a9' 173 link_name = u'\N{Euro Sign}link' 174 os.symlink(target, 'tree1/' + link_name) 175 tree.add([link_name]) 176 177 tree.commit('added a link to a Unicode target') 178 tree.controldir.sprout('dest') 179 self.assertEqual(target, osutils.readlink('dest/' + link_name)) 180 tree.lock_read() 181 self.addCleanup(tree.unlock) 182 # Check that the symlink target is safely round-tripped in the trees. 183 self.assertEqual(target, tree.get_symlink_target(link_name)) 184 self.assertEqual(target, 185 tree.basis_tree().get_symlink_target(link_name)) 186 187 def test_sprout_with_ghost_in_mainline(self): 188 tree = self.make_branch_and_tree('tree1') 189 if not tree.branch.repository._format.supports_ghosts: 190 raise tests.TestNotApplicable( 191 "repository format does not support ghosts in mainline") 192 tree.set_parent_ids([b"spooky"], allow_leftmost_as_ghost=True) 193 tree.add('') 194 rev1 = tree.commit('msg1') 195 tree.commit('msg2') 196 tree.controldir.sprout('target', revision_id=rev1) 197 198 def assertBranchHookBranchIsStacked(self, pre_change_params): 199 # Just calling will either succeed or fail. 200 pre_change_params.branch.get_stacked_on_url() 201 self.hook_calls.append(pre_change_params) 202 203 def test_sprout_stacked_hooks_get_stacked_branch(self): 204 tree = self.make_branch_and_tree('source') 205 tree.commit('a commit') 206 revid = tree.commit('a second commit') 207 source = tree.branch 208 target_transport = self.get_transport('target') 209 self.hook_calls = [] 210 _mod_branch.Branch.hooks.install_named_hook("pre_change_branch_tip", 211 self.assertBranchHookBranchIsStacked, None) 212 try: 213 dir = source.controldir.sprout(target_transport.base, 214 source.last_revision(), possible_transports=[target_transport], 215 source_branch=source, stacked=True) 216 except _mod_branch.UnstackableBranchFormat: 217 if not self.branch_format.supports_stacking(): 218 raise tests.TestNotApplicable( 219 "Format doesn't auto stack successfully.") 220 else: 221 raise 222 result = dir.open_branch() 223 self.assertEqual(revid, result.last_revision()) 224 self.assertEqual(source.base, result.get_stacked_on_url()) 225 # Smart servers invoke hooks on both sides 226 if isinstance(result, remote.RemoteBranch): 227 expected_calls = 2 228 else: 229 expected_calls = 1 230 self.assertEqual(expected_calls, len(self.hook_calls)) 231