1# Copyright (C) 2007-2010 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 fetch between repositories of the same type.""" 18 19from breezy import ( 20 controldir, 21 errors, 22 gpg, 23 repository, 24 ) 25from breezy.bzr import ( 26 remote, 27 ) 28from breezy.bzr.inventory import ROOT_ID 29from breezy.tests import ( 30 TestNotApplicable, 31 TestSkipped, 32 ) 33from breezy.tests.per_repository import TestCaseWithRepository 34 35 36class TestFetchSameRepository(TestCaseWithRepository): 37 38 def test_fetch(self): 39 # smoke test fetch to ensure that the convenience function works. 40 # it is defined as a convenience function with the underlying 41 # functionality provided by an InterRepository 42 tree_a = self.make_branch_and_tree('a') 43 self.build_tree(['a/foo']) 44 tree_a.add('foo') 45 rev1 = tree_a.commit('rev1') 46 # fetch with a default limit (grab everything) 47 repo = self.make_repository('b') 48 if (tree_a.branch.repository.supports_rich_root() and not 49 repo.supports_rich_root()): 50 raise TestSkipped('Cannot fetch from model2 to model1') 51 repo.fetch(tree_a.branch.repository, 52 revision_id=None) 53 54 def test_fetch_fails_in_write_group(self): 55 # fetch() manages a write group itself, fetching within one isn't safe. 56 repo = self.make_repository('a') 57 repo.lock_write() 58 self.addCleanup(repo.unlock) 59 repo.start_write_group() 60 self.addCleanup(repo.abort_write_group) 61 # Don't need a specific class - not expecting flow control based on 62 # this. 63 self.assertRaises(errors.BzrError, repo.fetch, repo) 64 65 def test_fetch_to_knit3(self): 66 # create a repository of the sort we are testing. 67 tree_a = self.make_branch_and_tree('a') 68 self.build_tree(['a/foo']) 69 tree_a.add('foo') 70 rev1 = tree_a.commit('rev1') 71 # create a knit-3 based format to fetch into 72 f = controldir.format_registry.make_controldir('development-subtree') 73 try: 74 format = tree_a.branch.repository._format 75 format.check_conversion_target(f.repository_format) 76 # if we cannot convert data to knit3, skip the test. 77 except errors.BadConversionTarget as e: 78 raise TestSkipped(str(e)) 79 self.get_transport().mkdir('b') 80 b_bzrdir = f.initialize(self.get_url('b')) 81 knit3_repo = b_bzrdir.create_repository() 82 # fetch with a default limit (grab everything) 83 knit3_repo.fetch(tree_a.branch.repository, revision_id=None) 84 # Reopen to avoid any in-memory caching - ensure its reading from 85 # disk. 86 knit3_repo = b_bzrdir.open_repository() 87 rev1_tree = knit3_repo.revision_tree(rev1) 88 with rev1_tree.lock_read(): 89 lines = rev1_tree.get_file_lines(u'') 90 self.assertEqual([], lines) 91 b_branch = b_bzrdir.create_branch() 92 b_branch.pull(tree_a.branch) 93 try: 94 tree_b = b_bzrdir.create_workingtree() 95 except errors.NotLocalUrl: 96 try: 97 tree_b = b_branch.create_checkout('b', lightweight=True) 98 except errors.NotLocalUrl: 99 raise TestSkipped("cannot make working tree with transport %r" 100 % b_bzrdir.transport) 101 rev2 = tree_b.commit('no change') 102 rev2_tree = knit3_repo.revision_tree(rev2) 103 self.assertEqual(rev1, rev2_tree.get_file_revision(u'')) 104 105 def do_test_fetch_to_rich_root_sets_parents_correctly(self, result, 106 snapshots, root_id=ROOT_ID, allow_lefthand_ghost=False): 107 """Assert that result is the parents of b'tip' after fetching snapshots. 108 109 This helper constructs a 1.9 format source, and a test-format target 110 and fetches the result of building snapshots in the source, then 111 asserts that the parents of tip are result. 112 113 :param result: A parents list for the inventories.get_parent_map call. 114 :param snapshots: An iterable of snapshot parameters for 115 BranchBuilder.build_snapshot. 116 '""" 117 # This overlaps slightly with the tests for commit builder about graph 118 # consistency. 119 # Cases: 120 repo = self.make_repository('target') 121 remote_format = isinstance(repo, remote.RemoteRepository) 122 if not repo._format.rich_root_data and not remote_format: 123 return # not relevant 124 if not repo._format.supports_full_versioned_files: 125 raise TestNotApplicable( 126 'format does not support full versioned files') 127 builder = self.make_branch_builder('source', format='1.9') 128 builder.start_series() 129 for revision_id, parent_ids, actions in snapshots: 130 builder.build_snapshot(parent_ids, actions, 131 allow_leftmost_as_ghost=allow_lefthand_ghost, 132 revision_id=revision_id) 133 builder.finish_series() 134 source = builder.get_branch() 135 if remote_format and not repo._format.rich_root_data: 136 # use a manual rich root format to ensure the code path is tested. 137 repo = self.make_repository('remote-target', 138 format='1.9-rich-root') 139 repo.lock_write() 140 self.addCleanup(repo.unlock) 141 repo.fetch(source.repository) 142 graph = repo.get_file_graph() 143 self.assertEqual(result, 144 graph.get_parent_map([(root_id, b'tip')])[(root_id, b'tip')]) 145 146 def test_fetch_to_rich_root_set_parent_no_parents(self): 147 # No parents rev -> No parents 148 self.do_test_fetch_to_rich_root_sets_parents_correctly((), 149 [(b'tip', None, [('add', ('', ROOT_ID, 'directory', ''))]), 150 ]) 151 152 def test_fetch_to_rich_root_set_parent_1_parent(self): 153 # 1 parent rev -> 1 parent 154 self.do_test_fetch_to_rich_root_sets_parents_correctly( 155 ((ROOT_ID, b'base'),), 156 [(b'base', None, [('add', ('', ROOT_ID, 'directory', ''))]), 157 (b'tip', None, []), 158 ]) 159 160 def test_fetch_to_rich_root_set_parent_1_ghost_parent(self): 161 # 1 ghost parent -> No parents 162 if not self.repository_format.supports_ghosts: 163 raise TestNotApplicable("repository format does not support " 164 "ghosts") 165 self.do_test_fetch_to_rich_root_sets_parents_correctly((), 166 [(b'tip', [b'ghost'], [('add', ('', ROOT_ID, 'directory', ''))]), 167 ], allow_lefthand_ghost=True) 168 169 def test_fetch_to_rich_root_set_parent_2_head_parents(self): 170 # 2 parents both heads -> 2 parents 171 self.do_test_fetch_to_rich_root_sets_parents_correctly( 172 ((ROOT_ID, b'left'), (ROOT_ID, b'right')), 173 [(b'base', None, [('add', ('', ROOT_ID, 'directory', ''))]), 174 (b'left', None, []), 175 (b'right', [b'base'], []), 176 (b'tip', [b'left', b'right'], []), 177 ]) 178 179 def test_fetch_to_rich_root_set_parent_2_parents_1_head(self): 180 # 2 parents one head -> 1 parent 181 self.do_test_fetch_to_rich_root_sets_parents_correctly( 182 ((ROOT_ID, b'right'),), 183 [(b'left', None, [('add', ('', ROOT_ID, 'directory', ''))]), 184 (b'right', None, []), 185 (b'tip', [b'left', b'right'], []), 186 ]) 187 188 def test_fetch_to_rich_root_set_parent_1_parent_different_id_gone(self): 189 # 1 parent different fileid, ours missing -> no parents 190 self.do_test_fetch_to_rich_root_sets_parents_correctly( 191 (), 192 [(b'base', None, [('add', ('', ROOT_ID, 'directory', ''))]), 193 (b'tip', None, [('unversion', ''), 194 ('add', ('', b'my-root', 'directory', '')), 195 ]), 196 ], root_id=b'my-root') 197 198 def test_fetch_to_rich_root_set_parent_1_parent_different_id_moved(self): 199 # 1 parent different fileid, ours moved -> 1 parent 200 # (and that parent honours the changing revid of the other location) 201 self.do_test_fetch_to_rich_root_sets_parents_correctly( 202 ((b'my-root', b'origin'),), 203 [(b'origin', None, [('add', ('', ROOT_ID, 'directory', '')), 204 ('add', ('child', b'my-root', 'directory', ''))]), 205 (b'base', None, []), 206 (b'tip', None, [('unversion', 'child'), 207 ('unversion', ''), 208 ('flush', None), 209 ('add', ('', b'my-root', 'directory', '')), 210 ]), 211 ], root_id=b'my-root') 212 213 def test_fetch_to_rich_root_set_parent_2_parent_1_different_id_gone(self): 214 # 2 parents, 1 different fileid, our second missing -> 1 parent 215 self.do_test_fetch_to_rich_root_sets_parents_correctly( 216 ((b'my-root', b'right'),), 217 [(b'base', None, [('add', ('', ROOT_ID, 'directory', ''))]), 218 (b'right', None, [('unversion', ''), 219 ('add', ('', b'my-root', 'directory', ''))]), 220 (b'tip', [b'base', b'right'], [('unversion', ''), 221 ('add', ('', b'my-root', 'directory', '')), 222 ]), 223 ], root_id=b'my-root') 224 225 def test_fetch_to_rich_root_set_parent_2_parent_2_different_id_moved(self): 226 # 2 parents, 1 different fileid, our second moved -> 2 parent 227 # (and that parent honours the changing revid of the other location) 228 self.do_test_fetch_to_rich_root_sets_parents_correctly( 229 ((b'my-root', b'right'),), 230 # b'my-root' at 'child'. 231 [(b'origin', None, [('add', ('', ROOT_ID, 'directory', '')), 232 ('add', ('child', b'my-root', 'directory', ''))]), 233 (b'base', None, []), 234 # b'my-root' at root 235 (b'right', None, [('unversion', 'child'), 236 ('unversion', ''), 237 ('flush', None), 238 ('add', ('', b'my-root', 'directory', ''))]), 239 (b'tip', [b'base', b'right'], [('unversion', ''), 240 ('unversion', 'child'), 241 ('flush', None), 242 ('add', ('', b'my-root', 'directory', '')), 243 ]), 244 ], root_id=b'my-root') 245 246 def test_fetch_all_from_self(self): 247 tree = self.make_branch_and_tree('.') 248 rev_id = tree.commit('one') 249 # This needs to be a new copy of the repository, if this changes, the 250 # test needs to be rewritten 251 repo = tree.branch.repository.controldir.open_repository() 252 # This fetch should be a no-op see bug #158333 253 tree.branch.repository.fetch(repo, None) 254 255 def test_fetch_from_self(self): 256 tree = self.make_branch_and_tree('.') 257 rev_id = tree.commit('one') 258 repo = tree.branch.repository.controldir.open_repository() 259 # This fetch should be a no-op see bug #158333 260 tree.branch.repository.fetch(repo, rev_id) 261 262 def test_fetch_missing_from_self(self): 263 tree = self.make_branch_and_tree('.') 264 rev_id = tree.commit('one') 265 # Even though the fetch() is a NO-OP it should assert the revision id 266 # is present 267 repo = tree.branch.repository.controldir.open_repository() 268 self.assertRaises(errors.NoSuchRevision, tree.branch.repository.fetch, 269 repo, b'no-such-revision') 270 271 def makeARepoWithSignatures(self): 272 wt = self.make_branch_and_tree('a-repo-with-sigs') 273 rev1 = wt.commit('rev1', allow_pointless=True) 274 repo = wt.branch.repository 275 repo.lock_write() 276 repo.start_write_group() 277 try: 278 repo.sign_revision(rev1, gpg.LoopbackGPGStrategy(None)) 279 except errors.UnsupportedOperation: 280 self.assertFalse(repo._format.supports_revision_signatures) 281 raise TestNotApplicable( 282 "repository format does not support signatures") 283 repo.commit_write_group() 284 repo.unlock() 285 return repo, rev1 286 287 def test_fetch_copies_signatures(self): 288 source_repo, rev1 = self.makeARepoWithSignatures() 289 target_repo = self.make_repository('target') 290 target_repo.fetch(source_repo, revision_id=None) 291 self.assertEqual( 292 source_repo.get_signature_text(rev1), 293 target_repo.get_signature_text(rev1)) 294 295 def make_repository_with_one_revision(self): 296 wt = self.make_branch_and_tree('source') 297 rev1 = wt.commit('rev1', allow_pointless=True) 298 return wt.branch.repository, rev1 299 300 def test_fetch_revision_already_exists(self): 301 # Make a repository with one revision. 302 source_repo, rev1 = self.make_repository_with_one_revision() 303 # Fetch that revision into a second repository. 304 target_repo = self.make_repository('target') 305 target_repo.fetch(source_repo, revision_id=rev1) 306 # Now fetch again; there will be nothing to do. This should work 307 # without causing any errors. 308 target_repo.fetch(source_repo, revision_id=rev1) 309 310 def test_fetch_all_same_revisions_twice(self): 311 # Blind-fetching all the same revisions twice should succeed and be a 312 # no-op the second time. 313 repo = self.make_repository('repo') 314 tree = self.make_branch_and_tree('tree') 315 revision_id = tree.commit('test') 316 repo.fetch(tree.branch.repository) 317 repo.fetch(tree.branch.repository) 318 319 def make_simple_branch_with_ghost(self): 320 if not self.repository_format.supports_ghosts: 321 raise TestNotApplicable("repository format does not support " 322 "ghosts") 323 builder = self.make_branch_builder('source') 324 builder.start_series() 325 a_revid = builder.build_snapshot(None, [ 326 ('add', ('', b'root-id', 'directory', None)), 327 ('add', ('file', b'file-id', 'file', b'content\n'))]) 328 b_revid = builder.build_snapshot([a_revid, b'ghost-id'], []) 329 builder.finish_series() 330 source_b = builder.get_branch() 331 source_b.lock_read() 332 self.addCleanup(source_b.unlock) 333 return source_b, b_revid 334 335 def test_fetch_with_ghost(self): 336 source_b, b_revid = self.make_simple_branch_with_ghost() 337 target = self.make_repository('target') 338 target.lock_write() 339 self.addCleanup(target.unlock) 340 target.fetch(source_b.repository, revision_id=b_revid) 341 342 def test_fetch_into_smart_with_ghost(self): 343 trans = self.make_smart_server('target') 344 source_b, b_revid = self.make_simple_branch_with_ghost() 345 if not source_b.controldir._format.supports_transport(trans): 346 raise TestNotApplicable("format does not support transport") 347 target = self.make_repository('target') 348 # Re-open the repository over the smart protocol 349 target = repository.Repository.open(trans.base) 350 target.lock_write() 351 self.addCleanup(target.unlock) 352 try: 353 target.fetch(source_b.repository, revision_id=b_revid) 354 except errors.TokenLockingNotSupported: 355 # The code inside fetch() that tries to lock and then fails, also 356 # causes weird problems with 'lock_not_held' later on... 357 target.lock_read() 358 self.knownFailure('some repositories fail to fetch' 359 ' via the smart server because of locking issues.') 360 361 def test_fetch_from_smart_with_ghost(self): 362 trans = self.make_smart_server('source') 363 source_b, b_revid = self.make_simple_branch_with_ghost() 364 if not source_b.controldir._format.supports_transport(trans): 365 raise TestNotApplicable("format does not support transport") 366 target = self.make_repository('target') 367 target.lock_write() 368 self.addCleanup(target.unlock) 369 # Re-open the repository over the smart protocol 370 source = repository.Repository.open(trans.base) 371 source.lock_read() 372 self.addCleanup(source.unlock) 373 target.fetch(source, revision_id=b_revid) 374