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."""
24
from bzrlib.revision import NULL_REVISION
25
from bzrlib.smart import server
26
from bzrlib.tests import TestNotApplicable, KnownFailure, transport_util
27
from bzrlib.tests.branch_implementations import TestCaseWithBranch
28
from bzrlib.transport import get_transport
31
unstackable_format_errors = (
32
errors.UnstackableBranchFormat,
33
errors.UnstackableRepositoryFormat,
37
class TestStacking(TestCaseWithBranch):
39
def check_lines_added_or_present(self, stacked_branch, revid):
40
# similar to a failure seen in bug 288751 by mbp 20081120
41
stacked_repo = stacked_branch.repository
42
stacked_repo.lock_read()
44
list(stacked_repo.inventories.iter_lines_added_or_present_in_keys(
49
def test_get_set_stacked_on_url(self):
50
# branches must either:
51
# raise UnstackableBranchFormat or
52
# raise UnstackableRepositoryFormat or
53
# permit stacking to be done and then return the stacked location.
54
branch = self.make_branch('branch')
55
target = self.make_branch('target')
57
branch.set_stacked_on_url(target.base)
58
except unstackable_format_errors:
59
# if the set failed, so must the get
60
self.assertRaises(unstackable_format_errors, branch.get_stacked_on_url)
61
self.assertFalse(branch._format.supports_stacking())
63
self.assertTrue(branch._format.supports_stacking())
64
# now we have a stacked branch:
65
self.assertEqual(target.base, branch.get_stacked_on_url())
66
branch.set_stacked_on_url(None)
67
self.assertRaises(errors.NotStacked, branch.get_stacked_on_url)
69
def test_get_set_stacked_on_relative(self):
70
# Branches can be stacked on other branches using relative paths.
71
branch = self.make_branch('branch')
72
target = self.make_branch('target')
74
branch.set_stacked_on_url('../target')
75
except unstackable_format_errors:
76
# if the set failed, so must the get
77
self.assertRaises(unstackable_format_errors, branch.get_stacked_on_url)
79
self.assertEqual('../target', branch.get_stacked_on_url())
81
def assertRevisionInRepository(self, repo_path, revid):
82
"""Check that a revision is in a repository, disregarding stacking."""
83
repo = bzrdir.BzrDir.open(repo_path).open_repository()
84
self.assertTrue(repo.has_revision(revid))
86
def assertRevisionNotInRepository(self, repo_path, revid):
87
"""Check that a revision is not in a repository, disregarding stacking."""
88
repo = bzrdir.BzrDir.open(repo_path).open_repository()
89
self.assertFalse(repo.has_revision(revid))
91
def test_get_graph_stacked(self):
92
"""A stacked repository shows the graph of its parent."""
93
trunk_tree = self.make_branch_and_tree('mainline')
94
trunk_revid = trunk_tree.commit('mainline')
95
# make a new branch, and stack on the existing one. we don't use
96
# sprout(stacked=True) here because if that is buggy and copies data
97
# it would cause a false pass of this test.
98
new_branch = self.make_branch('new_branch')
100
new_branch.set_stacked_on_url(trunk_tree.branch.base)
101
except unstackable_format_errors, e:
102
raise TestNotApplicable(e)
103
# reading the graph from the stacked branch's repository should see
104
# data from the stacked-on branch
105
new_repo = new_branch.repository
108
self.assertEqual(new_repo.get_parent_map([trunk_revid]),
109
{trunk_revid: (NULL_REVISION, )})
113
def test_sprout_stacked(self):
115
trunk_tree = self.make_branch_and_tree('mainline')
116
trunk_revid = trunk_tree.commit('mainline')
117
# and make branch from it which is stacked
119
new_dir = trunk_tree.bzrdir.sprout('newbranch', stacked=True)
120
except unstackable_format_errors, e:
121
raise TestNotApplicable(e)
123
self.assertRevisionNotInRepository('newbranch', trunk_revid)
124
new_tree = new_dir.open_workingtree()
125
new_branch_revid = new_tree.commit('something local')
126
self.assertRevisionNotInRepository('mainline', new_branch_revid)
127
self.assertRevisionInRepository('newbranch', new_branch_revid)
129
def test_sprout_stacked_from_smart_server(self):
130
if isinstance(self.branch_format, branch.BzrBranchFormat4):
131
raise TestNotApplicable('Branch format 4 is not usable via HPSS.')
133
trunk_tree = self.make_branch_and_tree('mainline')
134
trunk_revid = trunk_tree.commit('mainline')
135
# Make sure that we can make a stacked branch from it
137
trunk_tree.bzrdir.sprout('testbranch', stacked=True)
138
except unstackable_format_errors, e:
139
raise TestNotApplicable(e)
140
# Now serve the original mainline from a smart server
141
remote_transport = self.make_smart_server('mainline')
142
remote_bzrdir = bzrdir.BzrDir.open_from_transport(remote_transport)
143
# and make branch from the smart server which is stacked
144
new_dir = remote_bzrdir.sprout('newbranch', stacked=True)
146
self.assertRevisionNotInRepository('newbranch', trunk_revid)
147
new_tree = new_dir.open_workingtree()
148
new_branch_revid = new_tree.commit('something local')
149
self.assertRevisionNotInRepository('mainline', new_branch_revid)
150
self.assertRevisionInRepository('newbranch', new_branch_revid)
152
def test_unstack_fetches(self):
153
"""Removing the stacked-on branch pulls across all data"""
155
trunk_tree = self.make_branch_and_tree('mainline')
156
trunk_revid = trunk_tree.commit('revision on mainline')
157
# and make branch from it which is stacked
159
new_dir = trunk_tree.bzrdir.sprout('newbranch', stacked=True)
160
except unstackable_format_errors, e:
161
raise TestNotApplicable(e)
163
self.assertRevisionNotInRepository('newbranch', trunk_revid)
164
# now when we unstack that should implicitly fetch, to make sure that
165
# the branch will still work
166
new_branch = new_dir.open_branch()
167
new_branch.set_stacked_on_url(None)
168
self.assertRevisionInRepository('newbranch', trunk_revid)
169
# of course it's still in the mainline
170
self.assertRevisionInRepository('mainline', trunk_revid)
171
# and now we're no longer stacked
172
self.assertRaises(errors.NotStacked,
173
new_branch.get_stacked_on_url)
175
def make_stacked_bzrdir(self, in_directory=None):
176
"""Create a stacked branch and return its bzrdir.
178
:param in_directory: If not None, create a directory of this
179
name and create the stacking and stacked-on bzrdirs in
182
if in_directory is not None:
183
self.get_transport().mkdir(in_directory)
184
prefix = in_directory + '/'
187
tree = self.make_branch_and_tree(prefix + 'stacked-on')
188
tree.commit('Added foo')
189
stacked_bzrdir = tree.branch.bzrdir.sprout(
190
prefix + 'stacked', tree.branch.last_revision(), stacked=True)
191
return stacked_bzrdir
193
def test_clone_from_stacked_branch_preserve_stacking(self):
194
# We can clone from the bzrdir of a stacked branch. If
195
# preserve_stacking is True, the cloned branch is stacked on the
196
# same branch as the original.
198
stacked_bzrdir = self.make_stacked_bzrdir()
199
except unstackable_format_errors, e:
200
raise TestNotApplicable(e)
201
cloned_bzrdir = stacked_bzrdir.clone('cloned', preserve_stacking=True)
204
stacked_bzrdir.open_branch().get_stacked_on_url(),
205
cloned_bzrdir.open_branch().get_stacked_on_url())
206
except unstackable_format_errors, e:
209
def test_clone_from_branch_stacked_on_relative_url_preserve_stacking(self):
210
# If a branch's stacked-on url is relative, we can still clone
211
# from it with preserve_stacking True and get a branch stacked
212
# on an appropriately adjusted relative url.
214
stacked_bzrdir = self.make_stacked_bzrdir(in_directory='dir')
215
except unstackable_format_errors, e:
216
raise TestNotApplicable(e)
217
stacked_bzrdir.open_branch().set_stacked_on_url('../stacked-on')
218
cloned_bzrdir = stacked_bzrdir.clone('cloned', preserve_stacking=True)
221
cloned_bzrdir.open_branch().get_stacked_on_url())
223
def test_clone_from_stacked_branch_no_preserve_stacking(self):
225
stacked_bzrdir = self.make_stacked_bzrdir()
226
except unstackable_format_errors, e:
227
# not a testable combination.
228
raise TestNotApplicable(e)
229
cloned_unstacked_bzrdir = stacked_bzrdir.clone('cloned-unstacked',
230
preserve_stacking=False)
231
unstacked_branch = cloned_unstacked_bzrdir.open_branch()
232
self.assertRaises((errors.NotStacked, errors.UnstackableBranchFormat),
233
unstacked_branch.get_stacked_on_url)
235
def test_no_op_preserve_stacking(self):
236
"""With no stacking, preserve_stacking should be a no-op."""
237
branch = self.make_branch('source')
238
cloned_bzrdir = branch.bzrdir.clone('cloned', preserve_stacking=True)
239
self.assertRaises((errors.NotStacked, errors.UnstackableBranchFormat),
240
cloned_bzrdir.open_branch().get_stacked_on_url)
242
def test_sprout_stacking_policy_handling(self):
243
"""Obey policy where possible, ignore otherwise."""
244
stack_on = self.make_branch('stack-on')
245
parent_bzrdir = self.make_bzrdir('.', format='default')
246
parent_bzrdir.get_config().set_default_stack_on('stack-on')
247
source = self.make_branch('source')
248
target = source.bzrdir.sprout('target').open_branch()
249
if self.branch_format.supports_stacking():
250
self.assertEqual('../stack-on', target.get_stacked_on_url())
253
errors.UnstackableBranchFormat, target.get_stacked_on_url)
255
def test_clone_stacking_policy_handling(self):
256
"""Obey policy where possible, ignore otherwise."""
257
stack_on = self.make_branch('stack-on')
258
parent_bzrdir = self.make_bzrdir('.', format='default')
259
parent_bzrdir.get_config().set_default_stack_on('stack-on')
260
source = self.make_branch('source')
261
target = source.bzrdir.clone('target').open_branch()
262
if self.branch_format.supports_stacking():
263
self.assertEqual('../stack-on', target.get_stacked_on_url())
266
errors.UnstackableBranchFormat, target.get_stacked_on_url)
268
def test_sprout_to_smart_server_stacking_policy_handling(self):
269
"""Obey policy where possible, ignore otherwise."""
270
if isinstance(self.branch_format, branch.BzrBranchFormat4):
271
raise TestNotApplicable('Branch format 4 is not usable via HPSS.')
272
stack_on = self.make_branch('stack-on')
273
parent_bzrdir = self.make_bzrdir('.', format='default')
274
parent_bzrdir.get_config().set_default_stack_on('stack-on')
275
source = self.make_branch('source')
276
url = self.make_smart_server('target').base
277
target = source.bzrdir.sprout(url).open_branch()
278
if self.branch_format.supports_stacking():
279
self.assertEqual('../stack-on', target.get_stacked_on_url())
282
errors.UnstackableBranchFormat, target.get_stacked_on_url)
284
def prepare_stacked_on_fetch(self):
285
stack_on = self.make_branch_and_tree('stack-on')
286
stack_on.commit('first commit', rev_id='rev1')
288
stacked_dir = stack_on.bzrdir.sprout('stacked', stacked=True)
289
except unstackable_format_errors, e:
290
raise TestNotApplicable('Format does not support stacking.')
291
unstacked = self.make_repository('unstacked')
292
return stacked_dir.open_workingtree(), unstacked
294
def test_fetch_copies_from_stacked_on(self):
295
stacked, unstacked = self.prepare_stacked_on_fetch()
296
unstacked.fetch(stacked.branch.repository, 'rev1')
297
unstacked.get_revision('rev1')
299
def test_fetch_copies_from_stacked_on_and_stacked(self):
300
stacked, unstacked = self.prepare_stacked_on_fetch()
301
stacked.commit('second commit', rev_id='rev2')
302
unstacked.fetch(stacked.branch.repository, 'rev2')
303
unstacked.get_revision('rev1')
304
unstacked.get_revision('rev2')
305
self.check_lines_added_or_present(stacked.branch, 'rev1')
306
self.check_lines_added_or_present(stacked.branch, 'rev2')
308
def test_autopack_when_stacked(self):
309
# in bzr.dev as of 20080730, autopack was reported to fail in stacked
310
# repositories because of problems with text deltas spanning physical
311
# repository boundaries. however, i didn't actually get this test to
312
# fail on that code. -- mbp
313
# see https://bugs.launchpad.net/bzr/+bug/252821
314
if not self.branch_format.supports_stacking():
315
raise TestNotApplicable("%r does not support stacking"
316
% self.branch_format)
317
stack_on = self.make_branch_and_tree('stack-on')
318
text_lines = ['line %d blah blah blah\n' % i for i in range(20)]
319
self.build_tree_contents([('stack-on/a', ''.join(text_lines))])
321
stack_on.commit('base commit')
322
stacked_dir = stack_on.bzrdir.sprout('stacked', stacked=True)
323
stacked_tree = stacked_dir.open_workingtree()
325
text_lines[0] = 'changed in %d\n' % i
326
self.build_tree_contents([('stacked/a', ''.join(text_lines))])
327
stacked_tree.commit('commit %d' % i)
328
stacked_tree.branch.repository.pack()
329
stacked_tree.branch.check()
331
def test_pull_delta_when_stacked(self):
332
if not self.branch_format.supports_stacking():
333
raise TestNotApplicable("%r does not support stacking"
334
% self.branch_format)
335
stack_on = self.make_branch_and_tree('stack-on')
336
text_lines = ['line %d blah blah blah\n' % i for i in range(20)]
337
self.build_tree_contents([('stack-on/a', ''.join(text_lines))])
339
stack_on.commit('base commit')
340
# make a stacked branch from the mainline
341
stacked_dir = stack_on.bzrdir.sprout('stacked', stacked=True)
342
stacked_tree = stacked_dir.open_workingtree()
343
# make a second non-stacked branch from the mainline
344
other_dir = stack_on.bzrdir.sprout('other')
345
other_tree = other_dir.open_workingtree()
346
text_lines[9] = 'changed in other\n'
347
self.build_tree_contents([('other/a', ''.join(text_lines))])
348
stacked_revid = other_tree.commit('commit in other')
349
# this should have generated a delta; try to pull that across
350
# bug 252821 caused a RevisionNotPresent here...
351
stacked_tree.pull(other_tree.branch)
352
stacked_tree.branch.repository.pack()
353
stacked_tree.branch.check()
354
self.check_lines_added_or_present(stacked_tree.branch, stacked_revid)
356
def test_fetch_revisions_with_file_changes(self):
357
# Fetching revisions including file changes into a stacked branch
358
# works without error.
359
# Make the source tree.
360
src_tree = self.make_branch_and_tree('src')
361
self.build_tree_contents([('src/a', 'content')])
363
src_tree.commit('first commit')
365
# Make the stacked-on branch.
366
src_tree.bzrdir.sprout('stacked-on')
368
# Make a branch stacked on it.
369
target = self.make_branch('target')
371
target.set_stacked_on_url('../stacked-on')
372
except unstackable_format_errors, e:
373
raise TestNotApplicable('Format does not support stacking.')
375
# Change the source branch.
376
self.build_tree_contents([('src/a', 'new content')])
377
src_tree.commit('second commit', rev_id='rev2')
379
# Fetch changes to the target.
380
target.fetch(src_tree.branch)
381
rtree = target.repository.revision_tree('rev2')
383
self.addCleanup(rtree.unlock)
384
self.assertEqual('new content', rtree.get_file_by_path('a').read())
385
self.check_lines_added_or_present(target, 'rev2')
387
def test_transform_fallback_location_hook(self):
388
# The 'transform_fallback_location' branch hook allows us to inspect
389
# and transform the URL of the fallback location for the branch.
390
stack_on = self.make_branch('stack-on')
391
stacked = self.make_branch('stacked')
393
stacked.set_stacked_on_url('../stack-on')
394
except unstackable_format_errors, e:
395
raise TestNotApplicable('Format does not support stacking.')
396
self.get_transport().rename('stack-on', 'new-stack-on')
398
def hook(stacked_branch, url):
399
hook_calls.append(url)
400
return '../new-stack-on'
401
branch.Branch.hooks.install_named_hook(
402
'transform_fallback_location', hook, None)
403
branch.Branch.open('stacked')
404
self.assertEqual(['../stack-on'], hook_calls)
406
def test_stack_on_repository_branch(self):
407
# Stacking should work when the repo isn't co-located with the
410
repo = self.make_repository('repo', shared=True)
411
except errors.IncompatibleFormat:
412
raise TestNotApplicable()
413
# Avoid make_branch, which produces standalone branches.
414
bzrdir = self.make_bzrdir('repo/stack-on')
416
b = bzrdir.create_branch()
417
except errors.UninitializableFormat:
418
raise TestNotApplicable()
419
transport = self.get_transport('stacked')
420
b.bzrdir.clone_on_transport(transport, stacked_on=b.base)
421
# Ensure that opening the branch doesn't raise.
422
branch.Branch.open(transport.base)
425
class TestStackingConnections(
426
transport_util.TestCaseWithConnectionHookedTransport):
429
super(TestStackingConnections, self).setUp()
431
base_tree = self.make_branch_and_tree('base',
432
format=self.bzrdir_format)
433
except errors.UninitializableFormat, e:
434
raise TestNotApplicable(e)
435
stacked = self.make_branch('stacked', format=self.bzrdir_format)
437
stacked.set_stacked_on_url(base_tree.branch.base)
438
except unstackable_format_errors, e:
439
raise TestNotApplicable(e)
440
base_tree.commit('first', rev_id='rev-base')
441
stacked.set_last_revision_info(1, 'rev-base')
442
stacked_relative = self.make_branch('stacked_relative',
443
format=self.bzrdir_format)
444
stacked_relative.set_stacked_on_url('../base')
445
stacked.set_last_revision_info(1, 'rev-base')
446
self.start_logging_connections()
448
def test_open_stacked(self):
449
b = branch.Branch.open(self.get_url('stacked'))
450
rev = b.repository.get_revision('rev-base')
451
self.assertEqual(1, len(self.connections))
453
def test_open_stacked_relative(self):
454
b = branch.Branch.open(self.get_url('stacked_relative'))
455
rev = b.repository.get_revision('rev-base')
456
self.assertEqual(1, len(self.connections))