1
# Copyright (C) 2008 Canonical Ltd
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.
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.
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
17
"""Tests for Branch.get_stacked_on_url and set_stacked_on_url."""
25
from bzrlib.revision import NULL_REVISION
26
from bzrlib.smart import server
27
from bzrlib.tests import TestNotApplicable, KnownFailure, transport_util
28
from bzrlib.tests.per_branch import TestCaseWithBranch
29
from bzrlib.transport import get_transport
32
unstackable_format_errors = (
33
errors.UnstackableBranchFormat,
34
errors.UnstackableRepositoryFormat,
38
class TestStacking(TestCaseWithBranch):
40
def check_lines_added_or_present(self, stacked_branch, revid):
41
# similar to a failure seen in bug 288751 by mbp 20081120
42
stacked_repo = stacked_branch.repository
43
stacked_repo.lock_read()
45
list(stacked_repo.inventories.iter_lines_added_or_present_in_keys(
50
def test_get_set_stacked_on_url(self):
51
# branches must either:
52
# raise UnstackableBranchFormat or
53
# raise UnstackableRepositoryFormat or
54
# permit stacking to be done and then return the stacked location.
55
branch = self.make_branch('branch')
56
target = self.make_branch('target')
58
branch.set_stacked_on_url(target.base)
59
except unstackable_format_errors:
60
# if the set failed, so must the get
61
self.assertRaises(unstackable_format_errors, branch.get_stacked_on_url)
62
self.assertFalse(branch._format.supports_stacking())
64
self.assertTrue(branch._format.supports_stacking())
65
# now we have a stacked branch:
66
self.assertEqual(target.base, branch.get_stacked_on_url())
67
branch.set_stacked_on_url(None)
68
self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
70
def test_get_set_stacked_on_relative(self):
71
# Branches can be stacked on other branches using relative paths.
72
branch = self.make_branch('branch')
73
target = self.make_branch('target')
75
branch.set_stacked_on_url('../target')
76
except unstackable_format_errors:
77
# if the set failed, so must the get
78
self.assertRaises(unstackable_format_errors, branch.get_stacked_on_url)
80
self.assertEqual('../target', branch.get_stacked_on_url())
82
def test_set_stacked_on_same_branch_raises(self):
83
# Stacking on the same branch silently raises and doesn't execute the
84
# change. Reported in bug 376243.
85
branch = self.make_branch('branch')
87
self.assertRaises(errors.UnstackableLocationError,
88
branch.set_stacked_on_url, '../branch')
89
except unstackable_format_errors:
90
# if the set failed, so must the get
91
self.assertRaises(unstackable_format_errors, branch.get_stacked_on_url)
93
self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
95
def test_set_stacked_on_same_branch_after_being_stacked_raises(self):
96
# Stacking on the same branch silently raises and doesn't execute the
98
branch = self.make_branch('branch')
99
target = self.make_branch('target')
101
branch.set_stacked_on_url('../target')
102
except unstackable_format_errors:
103
# if the set failed, so must the get
104
self.assertRaises(unstackable_format_errors, branch.get_stacked_on_url)
106
self.assertRaises(errors.UnstackableLocationError,
107
branch.set_stacked_on_url, '../branch')
108
self.assertEqual('../target', branch.get_stacked_on_url())
110
def assertRevisionInRepository(self, repo_path, revid):
111
"""Check that a revision is in a repository, disregarding stacking."""
112
repo = bzrdir.BzrDir.open(repo_path).open_repository()
113
self.assertTrue(repo.has_revision(revid))
115
def assertRevisionNotInRepository(self, repo_path, revid):
116
"""Check that a revision is not in a repository, disregarding stacking."""
117
repo = bzrdir.BzrDir.open(repo_path).open_repository()
118
self.assertFalse(repo.has_revision(revid))
120
def test_get_graph_stacked(self):
121
"""A stacked repository shows the graph of its parent."""
122
trunk_tree = self.make_branch_and_tree('mainline')
123
trunk_revid = trunk_tree.commit('mainline')
124
# make a new branch, and stack on the existing one. we don't use
125
# sprout(stacked=True) here because if that is buggy and copies data
126
# it would cause a false pass of this test.
127
new_branch = self.make_branch('new_branch')
129
new_branch.set_stacked_on_url(trunk_tree.branch.base)
130
except unstackable_format_errors, e:
131
raise TestNotApplicable(e)
132
# reading the graph from the stacked branch's repository should see
133
# data from the stacked-on branch
134
new_repo = new_branch.repository
137
self.assertEqual(new_repo.get_parent_map([trunk_revid]),
138
{trunk_revid: (NULL_REVISION, )})
142
def test_sprout_stacked(self):
144
trunk_tree = self.make_branch_and_tree('mainline')
145
trunk_revid = trunk_tree.commit('mainline')
146
# and make branch from it which is stacked
148
new_dir = trunk_tree.bzrdir.sprout('newbranch', stacked=True)
149
except unstackable_format_errors, e:
150
raise TestNotApplicable(e)
152
self.assertRevisionNotInRepository('newbranch', trunk_revid)
153
tree = new_dir.open_branch().create_checkout('local')
154
new_branch_revid = tree.commit('something local')
155
self.assertRevisionNotInRepository('mainline', new_branch_revid)
156
self.assertRevisionInRepository('newbranch', new_branch_revid)
158
def test_sprout_stacked_from_smart_server(self):
159
if isinstance(self.branch_format, branch.BzrBranchFormat4):
160
raise TestNotApplicable('Branch format 4 is not usable via HPSS.')
162
trunk_tree = self.make_branch_and_tree('mainline')
163
trunk_revid = trunk_tree.commit('mainline')
164
# Make sure that we can make a stacked branch from it
166
trunk_tree.bzrdir.sprout('testbranch', stacked=True)
167
except unstackable_format_errors, e:
168
raise TestNotApplicable(e)
169
# Now serve the original mainline from a smart server
170
remote_transport = self.make_smart_server('mainline')
171
remote_bzrdir = bzrdir.BzrDir.open_from_transport(remote_transport)
172
# and make branch from the smart server which is stacked
173
new_dir = remote_bzrdir.sprout('newbranch', stacked=True)
175
self.assertRevisionNotInRepository('newbranch', trunk_revid)
176
tree = new_dir.open_branch().create_checkout('local')
177
new_branch_revid = tree.commit('something local')
178
self.assertRevisionNotInRepository('mainline', new_branch_revid)
179
self.assertRevisionInRepository('newbranch', new_branch_revid)
181
def test_unstack_fetches(self):
182
"""Removing the stacked-on branch pulls across all data"""
184
trunk_tree = self.make_branch_and_tree('mainline')
185
trunk_revid = trunk_tree.commit('revision on mainline')
186
# and make branch from it which is stacked
188
new_dir = trunk_tree.bzrdir.sprout(self.get_url('newbranch'),
190
except unstackable_format_errors, e:
191
raise TestNotApplicable(e)
193
self.assertRevisionNotInRepository('newbranch', trunk_revid)
194
# TODO: we'd like to commit in the stacked repository; that requires
195
# some care (maybe a BranchBuilder) if it's remote and has no
197
##newbranch_revid = new_dir.open_workingtree().commit('revision in '
199
# now when we unstack that should implicitly fetch, to make sure that
200
# the branch will still work
201
new_branch = new_dir.open_branch()
202
new_branch.set_stacked_on_url(None)
203
self.assertRevisionInRepository('newbranch', trunk_revid)
204
# of course it's still in the mainline
205
self.assertRevisionInRepository('mainline', trunk_revid)
206
# and now we're no longer stacked
207
self.assertRaises(errors.NotStacked,
208
new_branch.get_stacked_on_url)
210
def make_stacked_bzrdir(self, in_directory=None):
211
"""Create a stacked branch and return its bzrdir.
213
:param in_directory: If not None, create a directory of this
214
name and create the stacking and stacked-on bzrdirs in
217
if in_directory is not None:
218
self.get_transport().mkdir(in_directory)
219
prefix = in_directory + '/'
222
tree = self.make_branch_and_tree(prefix + 'stacked-on')
223
tree.commit('Added foo')
224
stacked_bzrdir = tree.branch.bzrdir.sprout(
225
prefix + 'stacked', tree.branch.last_revision(), stacked=True)
226
return stacked_bzrdir
228
def test_clone_from_stacked_branch_preserve_stacking(self):
229
# We can clone from the bzrdir of a stacked branch. If
230
# preserve_stacking is True, the cloned branch is stacked on the
231
# same branch as the original.
233
stacked_bzrdir = self.make_stacked_bzrdir()
234
except unstackable_format_errors, e:
235
raise TestNotApplicable(e)
236
cloned_bzrdir = stacked_bzrdir.clone('cloned', preserve_stacking=True)
239
stacked_bzrdir.open_branch().get_stacked_on_url(),
240
cloned_bzrdir.open_branch().get_stacked_on_url())
241
except unstackable_format_errors, e:
244
def test_clone_from_branch_stacked_on_relative_url_preserve_stacking(self):
245
# If a branch's stacked-on url is relative, we can still clone
246
# from it with preserve_stacking True and get a branch stacked
247
# on an appropriately adjusted relative url.
249
stacked_bzrdir = self.make_stacked_bzrdir(in_directory='dir')
250
except unstackable_format_errors, e:
251
raise TestNotApplicable(e)
252
stacked_bzrdir.open_branch().set_stacked_on_url('../stacked-on')
253
cloned_bzrdir = stacked_bzrdir.clone('cloned', preserve_stacking=True)
256
cloned_bzrdir.open_branch().get_stacked_on_url())
258
def test_clone_from_stacked_branch_no_preserve_stacking(self):
260
stacked_bzrdir = self.make_stacked_bzrdir()
261
except unstackable_format_errors, e:
262
# not a testable combination.
263
raise TestNotApplicable(e)
264
cloned_unstacked_bzrdir = stacked_bzrdir.clone('cloned-unstacked',
265
preserve_stacking=False)
266
unstacked_branch = cloned_unstacked_bzrdir.open_branch()
267
self.assertRaises((errors.NotStacked, errors.UnstackableBranchFormat),
268
unstacked_branch.get_stacked_on_url)
270
def test_no_op_preserve_stacking(self):
271
"""With no stacking, preserve_stacking should be a no-op."""
272
branch = self.make_branch('source')
273
cloned_bzrdir = branch.bzrdir.clone('cloned', preserve_stacking=True)
274
self.assertRaises((errors.NotStacked, errors.UnstackableBranchFormat),
275
cloned_bzrdir.open_branch().get_stacked_on_url)
277
def make_stacked_on_matching(self, source):
278
if source.repository.supports_rich_root():
279
if source.repository._format.supports_chks:
282
format = "1.9-rich-root"
285
return self.make_branch('stack-on', format)
287
def test_sprout_stacking_policy_handling(self):
288
"""Obey policy where possible, ignore otherwise."""
289
if isinstance(self.branch_format, branch.BzrBranchFormat4):
290
raise TestNotApplicable('Branch format 4 does not autoupgrade.')
291
source = self.make_branch('source')
292
stack_on = self.make_stacked_on_matching(source)
293
parent_bzrdir = self.make_bzrdir('.', format='default')
294
parent_bzrdir.get_config().set_default_stack_on('stack-on')
295
target = source.bzrdir.sprout('target').open_branch()
296
# When we sprout we upgrade the branch when there is a default stack_on
297
# set by a config *and* the targeted branch supports stacking.
298
if stack_on._format.supports_stacking():
299
self.assertEqual('../stack-on', target.get_stacked_on_url())
302
errors.UnstackableBranchFormat, target.get_stacked_on_url)
304
def test_clone_stacking_policy_handling(self):
305
"""Obey policy where possible, ignore otherwise."""
306
if isinstance(self.branch_format, branch.BzrBranchFormat4):
307
raise TestNotApplicable('Branch format 4 does not autoupgrade.')
308
source = self.make_branch('source')
309
stack_on = self.make_stacked_on_matching(source)
310
parent_bzrdir = self.make_bzrdir('.', format='default')
311
parent_bzrdir.get_config().set_default_stack_on('stack-on')
312
target = source.bzrdir.clone('target').open_branch()
313
# When we clone we upgrade the branch when there is a default stack_on
314
# set by a config *and* the targeted branch supports stacking.
315
if stack_on._format.supports_stacking():
316
self.assertEqual('../stack-on', target.get_stacked_on_url())
319
errors.UnstackableBranchFormat, target.get_stacked_on_url)
321
def test_sprout_to_smart_server_stacking_policy_handling(self):
322
"""Obey policy where possible, ignore otherwise."""
323
if isinstance(self.branch_format, branch.BzrBranchFormat4):
324
raise TestNotApplicable('Branch format 4 is not usable via HPSS.')
325
source = self.make_branch('source')
326
stack_on = self.make_stacked_on_matching(source)
327
parent_bzrdir = self.make_bzrdir('.', format='default')
328
parent_bzrdir.get_config().set_default_stack_on('stack-on')
329
url = self.make_smart_server('target').base
330
target = source.bzrdir.sprout(url).open_branch()
331
# When we sprout we upgrade the branch when there is a default stack_on
332
# set by a config *and* the targeted branch supports stacking.
333
if stack_on._format.supports_stacking():
334
self.assertEqual('../stack-on', target.get_stacked_on_url())
337
errors.UnstackableBranchFormat, target.get_stacked_on_url)
339
def prepare_stacked_on_fetch(self):
340
stack_on = self.make_branch_and_tree('stack-on')
341
stack_on.commit('first commit', rev_id='rev1')
343
stacked_dir = stack_on.bzrdir.sprout('stacked', stacked=True)
344
except unstackable_format_errors, e:
345
raise TestNotApplicable('Format does not support stacking.')
346
unstacked = self.make_repository('unstacked')
347
return stacked_dir.open_workingtree(), unstacked
349
def test_fetch_copies_from_stacked_on(self):
350
stacked, unstacked = self.prepare_stacked_on_fetch()
351
unstacked.fetch(stacked.branch.repository, 'rev1')
352
unstacked.get_revision('rev1')
354
def test_fetch_copies_from_stacked_on_and_stacked(self):
355
stacked, unstacked = self.prepare_stacked_on_fetch()
356
tree = stacked.branch.create_checkout('local')
357
tree.commit('second commit', rev_id='rev2')
358
unstacked.fetch(stacked.branch.repository, 'rev2')
359
unstacked.get_revision('rev1')
360
unstacked.get_revision('rev2')
361
self.check_lines_added_or_present(stacked.branch, 'rev1')
362
self.check_lines_added_or_present(stacked.branch, 'rev2')
364
def test_autopack_when_stacked(self):
365
# in bzr.dev as of 20080730, autopack was reported to fail in stacked
366
# repositories because of problems with text deltas spanning physical
367
# repository boundaries. however, i didn't actually get this test to
368
# fail on that code. -- mbp
369
# see https://bugs.launchpad.net/bzr/+bug/252821
370
stack_on = self.make_branch_and_tree('stack-on')
371
if not stack_on.branch._format.supports_stacking():
372
raise TestNotApplicable("%r does not support stacking"
373
% self.branch_format)
374
text_lines = ['line %d blah blah blah\n' % i for i in range(20)]
375
self.build_tree_contents([('stack-on/a', ''.join(text_lines))])
377
stack_on.commit('base commit')
378
stacked_dir = stack_on.bzrdir.sprout('stacked', stacked=True)
379
stacked_branch = stacked_dir.open_branch()
380
local_tree = stack_on.bzrdir.sprout('local').open_workingtree()
382
text_lines[0] = 'changed in %d\n' % i
383
self.build_tree_contents([('local/a', ''.join(text_lines))])
384
local_tree.commit('commit %d' % i)
385
local_tree.branch.push(stacked_branch)
386
stacked_branch.repository.pack()
387
check.check_dwim(stacked_branch.base, False, True, True)
389
def test_pull_delta_when_stacked(self):
390
if not self.branch_format.supports_stacking():
391
raise TestNotApplicable("%r does not support stacking"
392
% self.branch_format)
393
stack_on = self.make_branch_and_tree('stack-on')
394
text_lines = ['line %d blah blah blah\n' % i for i in range(20)]
395
self.build_tree_contents([('stack-on/a', ''.join(text_lines))])
397
stack_on.commit('base commit')
398
# make a stacked branch from the mainline
399
stacked_dir = stack_on.bzrdir.sprout('stacked', stacked=True)
400
stacked_tree = stacked_dir.open_workingtree()
401
# make a second non-stacked branch from the mainline
402
other_dir = stack_on.bzrdir.sprout('other')
403
other_tree = other_dir.open_workingtree()
404
text_lines[9] = 'changed in other\n'
405
self.build_tree_contents([('other/a', ''.join(text_lines))])
406
stacked_revid = other_tree.commit('commit in other')
407
# this should have generated a delta; try to pull that across
408
# bug 252821 caused a RevisionNotPresent here...
409
stacked_tree.pull(other_tree.branch)
410
stacked_tree.branch.repository.pack()
411
check.check_dwim(stacked_tree.branch.base, False, True, True)
412
self.check_lines_added_or_present(stacked_tree.branch, stacked_revid)
414
def test_fetch_revisions_with_file_changes(self):
415
# Fetching revisions including file changes into a stacked branch
416
# works without error.
417
# Make the source tree.
418
src_tree = self.make_branch_and_tree('src')
419
self.build_tree_contents([('src/a', 'content')])
421
src_tree.commit('first commit')
423
# Make the stacked-on branch.
424
src_tree.bzrdir.sprout('stacked-on')
426
# Make a branch stacked on it.
427
target = self.make_branch('target')
429
target.set_stacked_on_url('../stacked-on')
430
except unstackable_format_errors, e:
431
raise TestNotApplicable('Format does not support stacking.')
433
# Change the source branch.
434
self.build_tree_contents([('src/a', 'new content')])
435
src_tree.commit('second commit', rev_id='rev2')
437
# Fetch changes to the target.
438
target.fetch(src_tree.branch)
439
rtree = target.repository.revision_tree('rev2')
441
self.addCleanup(rtree.unlock)
442
self.assertEqual('new content', rtree.get_file_by_path('a').read())
443
self.check_lines_added_or_present(target, 'rev2')
445
def test_transform_fallback_location_hook(self):
446
# The 'transform_fallback_location' branch hook allows us to inspect
447
# and transform the URL of the fallback location for the branch.
448
stack_on = self.make_branch('stack-on')
449
stacked = self.make_branch('stacked')
451
stacked.set_stacked_on_url('../stack-on')
452
except unstackable_format_errors, e:
453
raise TestNotApplicable('Format does not support stacking.')
454
self.get_transport().rename('stack-on', 'new-stack-on')
456
def hook(stacked_branch, url):
457
hook_calls.append(url)
458
return '../new-stack-on'
459
branch.Branch.hooks.install_named_hook(
460
'transform_fallback_location', hook, None)
461
branch.Branch.open('stacked')
462
self.assertEqual(['../stack-on'], hook_calls)
464
def test_stack_on_repository_branch(self):
465
# Stacking should work when the repo isn't co-located with the
468
repo = self.make_repository('repo', shared=True)
469
except errors.IncompatibleFormat:
470
raise TestNotApplicable()
471
# Avoid make_branch, which produces standalone branches.
472
bzrdir = self.make_bzrdir('repo/stack-on')
474
b = bzrdir.create_branch()
475
except errors.UninitializableFormat:
476
raise TestNotApplicable()
477
transport = self.get_transport('stacked')
478
b.bzrdir.clone_on_transport(transport, stacked_on=b.base)
479
# Ensure that opening the branch doesn't raise.
480
branch.Branch.open(transport.base)
482
def test_revision_history_of_stacked(self):
483
# See <https://launchpad.net/bugs/380314>.
484
stack_on = self.make_branch_and_tree('stack-on')
485
stack_on.commit('first commit', rev_id='rev1')
487
stacked_dir = stack_on.bzrdir.sprout(
488
self.get_url('stacked'), stacked=True)
489
except unstackable_format_errors, e:
490
raise TestNotApplicable('Format does not support stacking.')
492
stacked = stacked_dir.open_workingtree()
493
except errors.NoWorkingTree:
494
stacked = stacked_dir.open_branch().create_checkout(
495
'stacked-checkout', lightweight=True)
496
tree = stacked.branch.create_checkout('local')
497
tree.commit('second commit', rev_id='rev2')
498
# Sanity check: stacked's repo should not contain rev1, otherwise this
499
# test isn't testing what it's supposed to.
500
repo = stacked.branch.repository.bzrdir.open_repository()
502
self.addCleanup(repo.unlock)
503
self.assertEqual({}, repo.get_parent_map(['rev1']))
504
# revision_history should work, even though the history is spread over
505
# multiple repositories.
506
self.assertLength(2, stacked.branch.revision_history())
509
class TestStackingConnections(
510
transport_util.TestCaseWithConnectionHookedTransport):
513
super(TestStackingConnections, self).setUp()
515
base_tree = self.make_branch_and_tree('base',
516
format=self.bzrdir_format)
517
except errors.UninitializableFormat, e:
518
raise TestNotApplicable(e)
519
stacked = self.make_branch('stacked', format=self.bzrdir_format)
521
stacked.set_stacked_on_url(base_tree.branch.base)
522
except unstackable_format_errors, e:
523
raise TestNotApplicable(e)
524
base_tree.commit('first', rev_id='rev-base')
525
stacked.set_last_revision_info(1, 'rev-base')
526
stacked_relative = self.make_branch('stacked_relative',
527
format=self.bzrdir_format)
528
stacked_relative.set_stacked_on_url('../base')
529
stacked.set_last_revision_info(1, 'rev-base')
530
self.start_logging_connections()
532
def test_open_stacked(self):
533
b = branch.Branch.open(self.get_url('stacked'))
534
rev = b.repository.get_revision('rev-base')
535
self.assertEqual(1, len(self.connections))
537
def test_open_stacked_relative(self):
538
b = branch.Branch.open(self.get_url('stacked_relative'))
539
rev = b.repository.get_revision('rev-base')
540
self.assertEqual(1, len(self.connections))