13
13
# You should have received a copy of the GNU General Public License
14
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""Tests for Branch.get_stacked_on_url and set_stacked_on_url."""
19
19
from bzrlib import (
25
23
from bzrlib.revision import NULL_REVISION
26
from bzrlib.tests import fixtures, TestNotApplicable, transport_util
27
from bzrlib.tests.per_branch import TestCaseWithBranch
30
unstackable_format_errors = (
31
errors.UnstackableBranchFormat,
32
errors.UnstackableRepositoryFormat,
24
from bzrlib.tests import TestNotApplicable, KnownFailure
25
from bzrlib.tests.branch_implementations import TestCaseWithBranch
36
28
class TestStacking(TestCaseWithBranch):
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
stacked_repo.lock_read()
43
list(stacked_repo.inventories.iter_lines_added_or_present_in_keys(
48
30
def test_get_set_stacked_on_url(self):
49
31
# branches must either:
50
32
# raise UnstackableBranchFormat or
69
53
# Branches can be stacked on other branches using relative paths.
70
54
branch = self.make_branch('branch')
71
55
target = self.make_branch('target')
73
branch.set_stacked_on_url('../target')
74
except unstackable_format_errors:
75
# if the set failed, so must the get
76
self.assertRaises(unstackable_format_errors, branch.get_stacked_on_url)
78
self.assertEqual('../target', branch.get_stacked_on_url())
80
def test_set_stacked_on_same_branch_raises(self):
81
# Stacking on the same branch silently raises and doesn't execute the
82
# change. Reported in bug 376243.
83
branch = self.make_branch('branch')
85
self.assertRaises(errors.UnstackableLocationError,
86
branch.set_stacked_on_url, '../branch')
87
except unstackable_format_errors:
88
# if the set failed, so must the get
89
self.assertRaises(unstackable_format_errors, branch.get_stacked_on_url)
91
self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
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
96
branch = self.make_branch('branch')
97
target = self.make_branch('target')
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, branch.get_stacked_on_url)
104
self.assertRaises(errors.UnstackableLocationError,
105
branch.set_stacked_on_url, '../branch')
57
errors.UnstackableBranchFormat,
58
errors.UnstackableRepositoryFormat,
61
branch.set_stacked_on_url('../target')
62
except old_format_errors:
63
# if the set failed, so must the get
64
self.assertRaises(old_format_errors, branch.get_stacked_on_url)
106
66
self.assertEqual('../target', branch.get_stacked_on_url())
108
68
def assertRevisionInRepository(self, repo_path, revid):
109
69
"""Check that a revision is in a repository, disregarding stacking."""
110
repo = controldir.ControlDir.open(repo_path).open_repository()
70
repo = bzrdir.BzrDir.open(repo_path).open_repository()
111
71
self.assertTrue(repo.has_revision(revid))
113
73
def assertRevisionNotInRepository(self, repo_path, revid):
114
74
"""Check that a revision is not in a repository, disregarding stacking."""
115
repo = controldir.ControlDir.open(repo_path).open_repository()
75
repo = bzrdir.BzrDir.open(repo_path).open_repository()
116
76
self.assertFalse(repo.has_revision(revid))
118
78
def test_get_graph_stacked(self):
144
105
# and make branch from it which is stacked
146
107
new_dir = trunk_tree.bzrdir.sprout('newbranch', stacked=True)
147
except unstackable_format_errors, e:
148
raise TestNotApplicable(e)
150
self.assertRevisionNotInRepository('newbranch', trunk_revid)
151
tree = new_dir.open_branch().create_checkout('local')
152
new_branch_revid = tree.commit('something local')
153
self.assertRevisionNotInRepository(
154
trunk_tree.branch.base, new_branch_revid)
155
self.assertRevisionInRepository('newbranch', new_branch_revid)
157
def test_sprout_stacked_from_smart_server(self):
159
trunk_tree = self.make_branch_and_tree('mainline')
160
trunk_revid = trunk_tree.commit('mainline')
161
# Make sure that we can make a stacked branch from it
163
trunk_tree.bzrdir.sprout('testbranch', stacked=True)
164
except unstackable_format_errors, e:
165
raise TestNotApplicable(e)
166
# Now serve the original mainline from a smart server
167
remote_transport = self.make_smart_server('mainline')
168
remote_bzrdir = controldir.ControlDir.open_from_transport(remote_transport)
169
# and make branch from the smart server which is stacked
170
new_dir = remote_bzrdir.sprout('newbranch', stacked=True)
172
self.assertRevisionNotInRepository('newbranch', trunk_revid)
173
tree = new_dir.open_branch().create_checkout('local')
174
new_branch_revid = tree.commit('something local')
175
self.assertRevisionNotInRepository(trunk_tree.branch.user_url,
108
except (errors.UnstackableBranchFormat,
109
errors.UnstackableRepositoryFormat), e:
110
raise TestNotApplicable(e)
112
self.assertRevisionNotInRepository('newbranch', trunk_revid)
113
new_tree = new_dir.open_workingtree()
114
new_branch_revid = new_tree.commit('something local')
115
self.assertRevisionNotInRepository('mainline', new_branch_revid)
177
116
self.assertRevisionInRepository('newbranch', new_branch_revid)
179
118
def test_unstack_fetches(self):
180
119
"""Removing the stacked-on branch pulls across all data"""
182
builder = self.make_branch_builder('trunk')
183
except errors.UninitializableFormat:
184
raise TestNotApplicable('uninitializeable format')
185
120
# We have a mainline
186
trunk = fixtures.build_branch_with_non_ancestral_rev(builder)
187
mainline_revid = 'rev-1'
188
# and make branch from it which is stacked (with no tags)
121
trunk_tree = self.make_branch_and_tree('mainline')
122
trunk_revid = trunk_tree.commit('revision on mainline')
123
# and make branch from it which is stacked
190
new_dir = trunk.bzrdir.sprout(self.get_url('newbranch'), stacked=True)
191
except unstackable_format_errors, e:
125
new_dir = trunk_tree.bzrdir.sprout('newbranch', stacked=True)
126
except (errors.UnstackableBranchFormat,
127
errors.UnstackableRepositoryFormat), e:
192
128
raise TestNotApplicable(e)
193
129
# 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
198
##newbranch_revid = new_dir.open_workingtree().commit('revision in '
130
self.assertRevisionNotInRepository('newbranch', trunk_revid)
200
131
# now when we unstack that should implicitly fetch, to make sure that
201
132
# the branch will still work
202
133
new_branch = new_dir.open_branch()
204
new_branch.tags.set_tag('tag-a', 'rev-2')
205
except errors.TagsNotSupported:
206
tags_supported = False
208
tags_supported = True
209
134
new_branch.set_stacked_on_url(None)
210
self.assertRevisionInRepository('newbranch', mainline_revid)
135
self.assertRevisionInRepository('newbranch', trunk_revid)
211
136
# of course it's still in the mainline
212
self.assertRevisionInRepository('trunk', mainline_revid)
214
# the tagged revision in trunk is now in newbranch too
215
self.assertRevisionInRepository('newbranch', 'rev-2')
137
self.assertRevisionInRepository('mainline', trunk_revid)
216
138
# and now we're no longer stacked
217
self.assertRaises(errors.NotStacked, new_branch.get_stacked_on_url)
219
def test_unstack_already_locked(self):
220
"""Removing the stacked-on branch with an already write-locked branch
226
stacked_bzrdir = self.make_stacked_bzrdir()
227
except unstackable_format_errors, 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()
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).
238
This is a more extreme variation of test_unstack_already_locked.
241
stacked_bzrdir = self.make_stacked_bzrdir()
242
except unstackable_format_errors, 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()
139
self.assertRaises(errors.NotStacked,
140
new_branch.get_stacked_on_url)
253
142
def make_stacked_bzrdir(self, in_directory=None):
254
143
"""Create a stacked branch and return its bzrdir.
319
212
self.assertRaises((errors.NotStacked, errors.UnstackableBranchFormat),
320
213
cloned_bzrdir.open_branch().get_stacked_on_url)
322
def make_stacked_on_matching(self, source):
323
if source.repository.supports_rich_root():
324
if source.repository._format.supports_chks:
327
format = "1.9-rich-root"
330
return self.make_branch('stack-on', format)
332
215
def test_sprout_stacking_policy_handling(self):
333
216
"""Obey policy where possible, ignore otherwise."""
334
if self.bzrdir_format.fixed_components:
335
raise TestNotApplicable('Branch format 4 does not autoupgrade.')
336
source = self.make_branch('source')
337
stack_on = self.make_stacked_on_matching(source)
217
stack_on = self.make_branch('stack-on')
338
218
parent_bzrdir = self.make_bzrdir('.', format='default')
339
219
parent_bzrdir.get_config().set_default_stack_on('stack-on')
220
source = self.make_branch('source')
340
221
target = source.bzrdir.sprout('target').open_branch()
341
# When we sprout we upgrade the branch when there is a default stack_on
342
# set by a config *and* the targeted branch supports stacking.
343
if stack_on._format.supports_stacking():
344
223
self.assertEqual('../stack-on', target.get_stacked_on_url())
347
errors.UnstackableBranchFormat, target.get_stacked_on_url)
224
except errors.UnstackableBranchFormat:
349
227
def test_clone_stacking_policy_handling(self):
350
228
"""Obey policy where possible, ignore otherwise."""
351
if self.bzrdir_format.fixed_components:
352
raise TestNotApplicable('Branch format 4 does not autoupgrade.')
353
source = self.make_branch('source')
354
stack_on = self.make_stacked_on_matching(source)
229
stack_on = self.make_branch('stack-on')
355
230
parent_bzrdir = self.make_bzrdir('.', format='default')
356
231
parent_bzrdir.get_config().set_default_stack_on('stack-on')
232
source = self.make_branch('source')
357
233
target = source.bzrdir.clone('target').open_branch()
358
# When we clone we upgrade the branch when there is a default stack_on
359
# set by a config *and* the targeted branch supports stacking.
360
if stack_on._format.supports_stacking():
361
self.assertEqual('../stack-on', target.get_stacked_on_url())
364
errors.UnstackableBranchFormat, target.get_stacked_on_url)
366
def test_sprout_to_smart_server_stacking_policy_handling(self):
367
"""Obey policy where possible, ignore otherwise."""
368
if not self.branch_format.supports_leaving_lock():
369
raise TestNotApplicable('Branch format is not usable via HPSS.')
370
source = self.make_branch('source')
371
stack_on = self.make_stacked_on_matching(source)
372
parent_bzrdir = self.make_bzrdir('.', format='default')
373
parent_bzrdir.get_config().set_default_stack_on('stack-on')
374
url = self.make_smart_server('target').base
375
target = source.bzrdir.sprout(url).open_branch()
376
# When we sprout we upgrade the branch when there is a default stack_on
377
# set by a config *and* the targeted branch supports stacking.
378
if stack_on._format.supports_stacking():
379
self.assertEqual('../stack-on', target.get_stacked_on_url())
382
errors.UnstackableBranchFormat, target.get_stacked_on_url)
235
self.assertEqual('../stack-on', target.get_stacked_on_url())
236
except errors.UnstackableBranchFormat:
384
239
def prepare_stacked_on_fetch(self):
385
240
stack_on = self.make_branch_and_tree('stack-on')
386
241
stack_on.commit('first commit', rev_id='rev1')
388
243
stacked_dir = stack_on.bzrdir.sprout('stacked', stacked=True)
389
except unstackable_format_errors, e:
244
except (errors.UnstackableRepositoryFormat,
245
errors.UnstackableBranchFormat):
390
246
raise TestNotApplicable('Format does not support stacking.')
391
247
unstacked = self.make_repository('unstacked')
392
248
return stacked_dir.open_workingtree(), unstacked
412
265
# repository boundaries. however, i didn't actually get this test to
413
266
# fail on that code. -- mbp
414
267
# see https://bugs.launchpad.net/bzr/+bug/252821
415
stack_on = self.make_branch_and_tree('stack-on')
416
if not stack_on.branch._format.supports_stacking():
268
if not self.branch_format.supports_stacking():
417
269
raise TestNotApplicable("%r does not support stacking"
418
270
% self.branch_format)
271
stack_on = self.make_branch_and_tree('stack-on')
419
272
text_lines = ['line %d blah blah blah\n' % i for i in range(20)]
420
273
self.build_tree_contents([('stack-on/a', ''.join(text_lines))])
421
274
stack_on.add('a')
422
275
stack_on.commit('base commit')
423
276
stacked_dir = stack_on.bzrdir.sprout('stacked', stacked=True)
424
stacked_branch = stacked_dir.open_branch()
425
local_tree = stack_on.bzrdir.sprout('local').open_workingtree()
277
stacked_tree = stacked_dir.open_workingtree()
426
278
for i in range(20):
427
279
text_lines[0] = 'changed in %d\n' % i
428
self.build_tree_contents([('local/a', ''.join(text_lines))])
429
local_tree.commit('commit %d' % i)
430
local_tree.branch.push(stacked_branch)
431
stacked_branch.repository.pack()
432
check.check_dwim(stacked_branch.base, False, True, True)
280
self.build_tree_contents([('stacked/a', ''.join(text_lines))])
281
stacked_tree.commit('commit %d' % i)
282
stacked_tree.branch.repository.pack()
283
stacked_tree.branch.check()
434
285
def test_pull_delta_when_stacked(self):
435
286
if not self.branch_format.supports_stacking():
484
335
rtree = target.repository.revision_tree('rev2')
485
336
rtree.lock_read()
486
337
self.addCleanup(rtree.unlock)
489
rtree.get_file_text(rtree.path2id('a'), 'a'))
490
self.check_lines_added_or_present(target, 'rev2')
492
def test_transform_fallback_location_hook(self):
493
# The 'transform_fallback_location' branch hook allows us to inspect
494
# and transform the URL of the fallback location for the branch.
495
stack_on = self.make_branch('stack-on')
496
stacked = self.make_branch('stacked')
498
stacked.set_stacked_on_url('../stack-on')
499
except unstackable_format_errors, e:
500
raise TestNotApplicable('Format does not support stacking.')
501
self.get_transport().rename('stack-on', 'new-stack-on')
503
def hook(stacked_branch, url):
504
hook_calls.append(url)
505
return '../new-stack-on'
506
branch.Branch.hooks.install_named_hook(
507
'transform_fallback_location', hook, None)
508
branch.Branch.open('stacked')
509
self.assertEqual(['../stack-on'], hook_calls)
511
def test_stack_on_repository_branch(self):
512
# Stacking should work when the repo isn't co-located with the
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_bzrdir('repo/stack-on')
523
b = bzrdir.create_branch()
524
except errors.UninitializableFormat:
525
raise TestNotApplicable()
526
transport = self.get_transport('stacked')
527
b.bzrdir.clone_on_transport(transport, stacked_on=b.base)
528
# Ensure that opening the branch doesn't raise.
529
branch.Branch.open(transport.base)
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
stack_on.commit('first commit', rev_id='rev1')
536
stacked_dir = stack_on.bzrdir.sprout(
537
self.get_url('stacked'), stacked=True)
538
except unstackable_format_errors, e:
539
raise TestNotApplicable('Format does not support stacking.')
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
tree.commit('second commit', rev_id='rev2')
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.bzrdir.open_repository()
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())
558
class TestStackingConnections(
559
transport_util.TestCaseWithConnectionHookedTransport):
562
super(TestStackingConnections, self).setUp()
564
base_tree = self.make_branch_and_tree('base',
565
format=self.bzrdir_format)
566
except errors.UninitializableFormat, e:
567
raise TestNotApplicable(e)
568
stacked = self.make_branch('stacked', format=self.bzrdir_format)
570
stacked.set_stacked_on_url(base_tree.branch.base)
571
except unstackable_format_errors, e:
572
raise TestNotApplicable(e)
573
base_tree.commit('first', rev_id='rev-base')
574
stacked.set_last_revision_info(1, '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, 'rev-base')
579
self.start_logging_connections()
581
def test_open_stacked(self):
582
b = branch.Branch.open(self.get_url('stacked'))
583
rev = b.repository.get_revision('rev-base')
584
self.assertEqual(1, len(self.connections))
586
def test_open_stacked_relative(self):
587
b = branch.Branch.open(self.get_url('stacked_relative'))
588
rev = b.repository.get_revision('rev-base')
589
self.assertEqual(1, len(self.connections))
338
self.assertEqual('new content', rtree.get_file_by_path('a').read())