1
# Copyright (C) 2007-2012, 2016 Canonical Ltd
2
# -*- coding: utf-8 -*-
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
"""Tests for the switch command of bzr."""
23
from breezy.controldir import ControlDir
29
from breezy.workingtree import WorkingTree
30
from breezy.tests import (
31
TestCaseWithTransport,
34
from breezy.tests.features import UnicodeFilenameFeature
35
from breezy.directory_service import directories
37
from breezy.tests.matchers import ContainsNoVfsCalls
40
class TestSwitch(TestCaseWithTransport):
42
def _create_sample_tree(self):
43
tree = self.make_branch_and_tree('branch-1')
44
self.build_tree(['branch-1/file-1', 'branch-1/file-2'])
51
def test_switch_up_to_date_light_checkout(self):
52
self.make_branch_and_tree('branch')
53
self.run_bzr('branch branch branch2')
54
self.run_bzr('checkout --lightweight branch checkout')
56
out, err = self.run_bzr('switch ../branch2')
57
self.assertContainsRe(err, 'Tree is up to date at revision 0.\n')
58
self.assertContainsRe(err, 'Switched to branch at .*/branch2.\n')
59
self.assertEqual('', out)
61
def test_switch_out_of_date_light_checkout(self):
62
self.make_branch_and_tree('branch')
63
self.run_bzr('branch branch branch2')
64
self.build_tree(['branch2/file'])
65
self.run_bzr('add branch2/file')
66
self.run_bzr('commit -m add-file branch2')
67
self.run_bzr('checkout --lightweight branch checkout')
69
out, err = self.run_bzr('switch ../branch2')
70
#self.assertContainsRe(err, '\+N file')
71
self.assertContainsRe(err, 'Updated to revision 1.\n')
72
self.assertContainsRe(err, 'Switched to branch at .*/branch2.\n')
73
self.assertEqual('', out)
75
def _test_switch_nick(self, lightweight):
76
"""Check that the nick gets switched too."""
77
tree1 = self.make_branch_and_tree('branch1')
78
tree2 = self.make_branch_and_tree('branch2')
79
tree2.pull(tree1.branch)
80
checkout = tree1.branch.create_checkout('checkout',
81
lightweight=lightweight)
82
self.assertEqual(checkout.branch.nick, tree1.branch.nick)
83
self.assertEqual(checkout.branch.get_config().has_explicit_nickname(),
85
self.run_bzr('switch branch2', working_dir='checkout')
87
# we need to get the tree again, otherwise we don't get the new branch
88
checkout = WorkingTree.open('checkout')
89
self.assertEqual(checkout.branch.nick, tree2.branch.nick)
90
self.assertEqual(checkout.branch.get_config().has_explicit_nickname(),
93
def test_switch_nick(self):
94
self._test_switch_nick(lightweight=False)
96
def test_switch_nick_lightweight(self):
97
self._test_switch_nick(lightweight=True)
99
def _test_switch_explicit_nick(self, lightweight):
100
"""Check that the nick gets switched too."""
101
tree1 = self.make_branch_and_tree('branch1')
102
tree2 = self.make_branch_and_tree('branch2')
103
tree2.pull(tree1.branch)
104
checkout = tree1.branch.create_checkout('checkout',
105
lightweight=lightweight)
106
self.assertEqual(checkout.branch.nick, tree1.branch.nick)
107
checkout.branch.nick = "explicit_nick"
108
self.assertEqual(checkout.branch.nick, "explicit_nick")
109
self.assertEqual(checkout.branch.get_config()._get_explicit_nickname(),
111
self.run_bzr('switch branch2', working_dir='checkout')
113
# we need to get the tree again, otherwise we don't get the new branch
114
checkout = WorkingTree.open('checkout')
115
self.assertEqual(checkout.branch.nick, tree2.branch.nick)
116
self.assertEqual(checkout.branch.get_config()._get_explicit_nickname(),
119
def test_switch_explicit_nick(self):
120
self._test_switch_explicit_nick(lightweight=False)
122
def test_switch_explicit_nick_lightweight(self):
123
self._test_switch_explicit_nick(lightweight=True)
125
def test_switch_finds_relative_branch(self):
126
"""Switch will find 'foo' relative to the branch the checkout is of."""
127
self.build_tree(['repo/'])
128
tree1 = self.make_branch_and_tree('repo/brancha')
130
tree2 = self.make_branch_and_tree('repo/branchb')
131
tree2.pull(tree1.branch)
132
branchb_id = tree2.commit('bar')
133
checkout = tree1.branch.create_checkout('checkout', lightweight=True)
134
self.run_bzr(['switch', 'branchb'], working_dir='checkout')
135
self.assertEqual(branchb_id, checkout.last_revision())
136
checkout = checkout.controldir.open_workingtree()
137
self.assertEqual(tree2.branch.base, checkout.branch.base)
139
def test_switch_finds_relative_bound_branch(self):
140
"""Using switch on a heavy checkout should find master sibling
142
The behaviour of lighweight and heavy checkouts should be
143
consistent when using the convenient "switch to sibling" feature
144
Both should switch to a sibling of the branch
145
they are bound to, and not a sibling of themself"""
147
self.build_tree(['repo/',
149
tree1 = self.make_branch_and_tree('repo/brancha')
151
tree2 = self.make_branch_and_tree('repo/branchb')
152
tree2.pull(tree1.branch)
153
branchb_id = tree2.commit('bar')
154
checkout = tree1.branch.create_checkout('heavyco/a', lightweight=False)
155
self.run_bzr(['switch', 'branchb'], working_dir='heavyco/a')
156
# Refresh checkout as 'switch' modified it
157
checkout = checkout.controldir.open_workingtree()
158
self.assertEqual(branchb_id, checkout.last_revision())
159
self.assertEqual(tree2.branch.base,
160
checkout.branch.get_bound_location())
162
def test_switch_finds_relative_unicode_branch(self):
163
"""Switch will find 'foo' relative to the branch the checkout is of."""
164
self.requireFeature(UnicodeFilenameFeature)
165
self.build_tree(['repo/'])
166
tree1 = self.make_branch_and_tree('repo/brancha')
168
tree2 = self.make_branch_and_tree(u'repo/branch\xe9')
169
tree2.pull(tree1.branch)
170
branchb_id = tree2.commit('bar')
171
checkout = tree1.branch.create_checkout('checkout', lightweight=True)
172
self.run_bzr(['switch', u'branch\xe9'], working_dir='checkout')
173
self.assertEqual(branchb_id, checkout.last_revision())
174
checkout = checkout.controldir.open_workingtree()
175
self.assertEqual(tree2.branch.base, checkout.branch.base)
177
def test_switch_finds_relative_unicode_branch(self):
178
"""Switch will find 'foo' relative to the branch the checkout is of."""
179
self.requireFeature(UnicodeFilenameFeature)
180
self.build_tree(['repo/'])
181
tree1 = self.make_branch_and_tree('repo/brancha')
183
tree2 = self.make_branch_and_tree(u'repo/branch\xe9')
184
tree2.pull(tree1.branch)
185
branchb_id = tree2.commit('bar')
186
checkout = tree1.branch.create_checkout('checkout', lightweight=True)
187
self.run_bzr(['switch', u'branch\xe9'], working_dir='checkout')
188
self.assertEqual(branchb_id, checkout.last_revision())
189
checkout = checkout.controldir.open_workingtree()
190
self.assertEqual(tree2.branch.base, checkout.branch.base)
192
def test_switch_revision(self):
193
tree = self._create_sample_tree()
194
checkout = tree.branch.create_checkout('checkout', lightweight=True)
195
self.run_bzr(['switch', 'branch-1', '-r1'], working_dir='checkout')
196
self.assertPathExists('checkout/file-1')
197
self.assertPathDoesNotExist('checkout/file-2')
199
def test_switch_into_colocated(self):
200
# Create a new colocated branch from an existing non-colocated branch.
201
tree = self.make_branch_and_tree('.', format='development-colo')
202
self.build_tree(['file-1', 'file-2'])
204
revid1 = tree.commit('rev1')
206
revid2 = tree.commit('rev2')
207
self.run_bzr(['switch', '-b', 'anotherbranch'])
209
{'', 'anotherbranch'},
210
set(tree.branch.controldir.branch_names()))
212
def test_switch_into_unrelated_colocated(self):
213
# Create a new colocated branch from an existing non-colocated branch.
214
tree = self.make_branch_and_tree('.', format='development-colo')
215
self.build_tree(['file-1', 'file-2'])
217
revid1 = tree.commit('rev1')
219
revid2 = tree.commit('rev2')
220
tree.controldir.create_branch(name='foo')
221
self.run_bzr_error(['Cannot switch a branch, only a checkout.'],
223
self.run_bzr(['switch', '--force', 'foo'])
225
def test_switch_existing_colocated(self):
226
# Create a branch branch-1 that initially is a checkout of 'foo'
227
# Use switch to change it to 'anotherbranch'
228
repo = self.make_repository('branch-1', format='development-colo')
229
target_branch = repo.controldir.create_branch(name='foo')
230
repo.controldir.set_branch_reference(target_branch)
231
tree = repo.controldir.create_workingtree()
232
self.build_tree(['branch-1/file-1', 'branch-1/file-2'])
234
revid1 = tree.commit('rev1')
236
revid2 = tree.commit('rev2')
237
otherbranch = tree.controldir.create_branch(name='anotherbranch')
238
otherbranch.generate_revision_history(revid1)
239
self.run_bzr(['switch', 'anotherbranch'], working_dir='branch-1')
240
tree = WorkingTree.open("branch-1")
241
self.assertEqual(tree.last_revision(), revid1)
242
self.assertEqual(tree.branch.control_url, otherbranch.control_url)
244
def test_switch_new_colocated(self):
245
# Create a branch branch-1 that initially is a checkout of 'foo'
246
# Use switch to create 'anotherbranch' which derives from that
247
repo = self.make_repository('branch-1', format='development-colo')
248
target_branch = repo.controldir.create_branch(name='foo')
249
repo.controldir.set_branch_reference(target_branch)
250
tree = repo.controldir.create_workingtree()
251
self.build_tree(['branch-1/file-1', 'branch-1/file-2'])
253
revid1 = tree.commit('rev1')
254
self.run_bzr(['switch', '-b', 'anotherbranch'], working_dir='branch-1')
255
bzrdir = ControlDir.open("branch-1")
257
{b.name for b in bzrdir.list_branches()},
258
{"foo", "anotherbranch"})
259
self.assertEqual(bzrdir.open_branch().name, "anotherbranch")
260
self.assertEqual(bzrdir.open_branch().last_revision(), revid1)
262
def test_switch_new_colocated_unicode(self):
263
# Create a branch branch-1 that initially is a checkout of 'foo'
264
# Use switch to create 'branch\xe9' which derives from that
265
self.requireFeature(UnicodeFilenameFeature)
266
repo = self.make_repository('branch-1', format='development-colo')
267
target_branch = repo.controldir.create_branch(name='foo')
268
repo.controldir.set_branch_reference(target_branch)
269
tree = repo.controldir.create_workingtree()
270
self.build_tree(['branch-1/file-1', 'branch-1/file-2'])
272
revid1 = tree.commit('rev1')
273
self.run_bzr(['switch', '-b', u'branch\xe9'], working_dir='branch-1')
274
bzrdir = ControlDir.open("branch-1")
276
{b.name for b in bzrdir.list_branches()},
277
{"foo", u"branch\xe9"})
278
self.assertEqual(bzrdir.open_branch().name, u"branch\xe9")
279
self.assertEqual(bzrdir.open_branch().last_revision(), revid1)
281
def test_switch_only_revision(self):
282
tree = self._create_sample_tree()
283
checkout = tree.branch.create_checkout('checkout', lightweight=True)
284
self.assertPathExists('checkout/file-1')
285
self.assertPathExists('checkout/file-2')
286
self.run_bzr(['switch', '-r1'], working_dir='checkout')
287
self.assertPathExists('checkout/file-1')
288
self.assertPathDoesNotExist('checkout/file-2')
289
# Check that we don't accept a range
291
['brz switch --revision takes exactly one revision identifier'],
292
['switch', '-r0..2'], working_dir='checkout')
294
def prepare_lightweight_switch(self):
295
branch = self.make_branch('branch')
296
branch.create_checkout('tree', lightweight=True)
297
osutils.rename('branch', 'branch1')
299
def test_switch_lightweight_after_branch_moved(self):
300
self.prepare_lightweight_switch()
301
self.run_bzr('switch --force ../branch1', working_dir='tree')
302
branch_location = WorkingTree.open('tree').branch.base
303
self.assertEndsWith(branch_location, 'branch1/')
305
def test_switch_lightweight_after_branch_moved_relative(self):
306
self.prepare_lightweight_switch()
307
self.run_bzr('switch --force branch1',
309
branch_location = WorkingTree.open('tree').branch.base
310
self.assertEndsWith(branch_location, 'branch1/')
312
def test_create_branch_no_branch(self):
313
self.prepare_lightweight_switch()
314
self.run_bzr_error(['cannot create branch without source branch'],
315
'switch --create-branch ../branch2', working_dir='tree')
317
def test_create_branch(self):
318
branch = self.make_branch('branch')
319
tree = branch.create_checkout('tree', lightweight=True)
320
tree.commit('one', rev_id=b'rev-1')
321
self.run_bzr('switch --create-branch ../branch2', working_dir='tree')
322
tree = WorkingTree.open('tree')
323
self.assertEndsWith(tree.branch.base, '/branch2/')
325
def test_create_branch_local(self):
326
branch = self.make_branch('branch')
327
tree = branch.create_checkout('tree', lightweight=True)
328
tree.commit('one', rev_id=b'rev-1')
329
self.run_bzr('switch --create-branch branch2', working_dir='tree')
330
tree = WorkingTree.open('tree')
331
# The new branch should have been created at the same level as
332
# 'branch', because we did not have a '/' segment
333
self.assertEqual(branch.base[:-1] + '2/', tree.branch.base)
335
def test_create_branch_short_name(self):
336
branch = self.make_branch('branch')
337
tree = branch.create_checkout('tree', lightweight=True)
338
tree.commit('one', rev_id=b'rev-1')
339
self.run_bzr('switch -b branch2', working_dir='tree')
340
tree = WorkingTree.open('tree')
341
# The new branch should have been created at the same level as
342
# 'branch', because we did not have a '/' segment
343
self.assertEqual(branch.base[:-1] + '2/', tree.branch.base)
345
def test_create_branch_directory_services(self):
346
branch = self.make_branch('branch')
347
tree = branch.create_checkout('tree', lightweight=True)
349
class FooLookup(object):
350
def look_up(self, name, url, purpose=None):
352
directories.register('foo:', FooLookup, 'Create branches named foo-')
353
self.addCleanup(directories.remove, 'foo:')
354
self.run_bzr('switch -b foo:branch2', working_dir='tree')
355
tree = WorkingTree.open('tree')
356
self.assertEndsWith(tree.branch.base, 'foo-branch2/')
358
def test_switch_with_post_switch_hook(self):
359
from breezy import branch as _mod_branch
361
_mod_branch.Branch.hooks.install_named_hook('post_switch',
363
self.make_branch_and_tree('branch')
364
self.run_bzr('branch branch branch2')
365
self.run_bzr('checkout branch checkout')
367
self.assertLength(0, calls)
368
out, err = self.run_bzr('switch ../branch2')
369
self.assertLength(1, calls)
371
def test_switch_lightweight_co_with_post_switch_hook(self):
372
from breezy import branch as _mod_branch
374
_mod_branch.Branch.hooks.install_named_hook('post_switch',
376
self.make_branch_and_tree('branch')
377
self.run_bzr('branch branch branch2')
378
self.run_bzr('checkout --lightweight branch checkout')
380
self.assertLength(0, calls)
381
out, err = self.run_bzr('switch ../branch2')
382
self.assertLength(1, calls)
384
def test_switch_lightweight_directory(self):
385
"""Test --directory option"""
387
# create a source branch
388
a_tree = self.make_branch_and_tree('a')
389
self.build_tree_contents([('a/a', b'initial\n')])
391
a_tree.commit(message='initial')
393
# clone and add a differing revision
394
b_tree = a_tree.controldir.sprout('b').open_workingtree()
395
self.build_tree_contents([('b/a', b'initial\nmore\n')])
396
b_tree.commit(message='more')
398
self.run_bzr('checkout --lightweight a checkout')
399
self.run_bzr('switch --directory checkout b')
400
self.assertFileEqual(b'initial\nmore\n', 'checkout/a')
403
class TestSwitchParentLocationBase(TestCaseWithTransport):
406
"""Set up a repository and branch ready for testing."""
407
super(TestSwitchParentLocationBase, self).setUp()
408
self.script_runner = script.ScriptRunner()
409
self.script_runner.run_script(self, '''
410
$ brz init-shared-repo --no-trees repo
413
shared repository: repo
414
$ brz init repo/trunk
415
Created a repository branch...
416
Using shared repository: ...
419
def assertParent(self, expected_parent, branch):
420
"""Verify that the parent is not None and is set correctly."""
421
actual_parent = branch.get_parent()
422
self.assertIsSameRealPath(urlutils.local_path_to_url(expected_parent),
426
class TestSwitchParentLocation(TestSwitchParentLocationBase):
428
def _checkout_and_switch(self, option=''):
429
self.script_runner.run_script(self, '''
430
$ brz checkout %(option)s repo/trunk checkout
432
$ brz switch --create-branch switched
433
2>Tree is up to date at revision 0.
434
2>Switched to branch at .../switched/
437
bound_branch = branch.Branch.open_containing('checkout')[0]
438
master_branch = branch.Branch.open_containing('repo/switched')[0]
439
return (bound_branch, master_branch)
441
def test_switch_parent_lightweight(self):
442
"""Lightweight checkout using brz switch."""
443
bb, mb = self._checkout_and_switch(option='--lightweight')
444
self.assertParent('repo/trunk', bb)
445
self.assertParent('repo/trunk', mb)
447
def test_switch_parent_heavyweight(self):
448
"""Heavyweight checkout using brz switch."""
449
bb, mb = self._checkout_and_switch()
450
self.assertParent('repo/trunk', bb)
451
self.assertParent('repo/trunk', mb)
454
class TestSwitchDoesntOpenMasterBranch(TestCaseWithTransport):
455
# See https://bugs.launchpad.net/bzr/+bug/812285
456
# "brz switch --create-branch" can point the new branch's parent to the
457
# master branch, but it doesn't have to open it to do so.
459
def test_switch_create_doesnt_open_master_branch(self):
460
master = self.make_branch_and_tree('master')
462
# Note: not a lightweight checkout
463
checkout = master.branch.create_checkout('checkout')
466
def open_hook(branch):
467
# Just append the final directory of the branch
468
name = branch.base.rstrip('/').rsplit('/', 1)[1]
470
branch.Branch.hooks.install_named_hook('open', open_hook,
472
self.run_bzr('switch --create-branch -d checkout feature')
473
# We only open the master branch 1 time.
474
# This test should be cleaner to write, but see bug:
475
# https://bugs.launchpad.net/bzr/+bug/812295
476
self.assertEqual(1, opened.count('master'))
479
class TestSmartServerSwitch(TestCaseWithTransport):
481
def test_switch_lightweight(self):
482
self.setup_smart_server_with_call_log()
483
t = self.make_branch_and_tree('from')
484
for count in range(9):
485
t.commit(message='commit %d' % count)
486
out, err = self.run_bzr(['checkout', '--lightweight', self.get_url('from'),
488
self.reset_smart_call_log()
489
self.run_bzr(['switch', self.get_url('from')], working_dir='target')
490
# This figure represent the amount of work to perform this use case. It
491
# is entirely ok to reduce this number if a test fails due to rpc_count
492
# being too low. If rpc_count increases, more network roundtrips have
493
# become necessary for this use case. Please do not adjust this number
494
# upwards without agreement from bzr's network support maintainers.
495
self.assertLength(21, self.hpss_calls)
496
self.assertLength(3, self.hpss_connections)
497
self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
500
class TestSwitchUncommitted(TestCaseWithTransport):
503
tree = self.make_branch_and_tree('orig')
505
tree.branch.controldir.sprout('new')
506
checkout = tree.branch.create_checkout('checkout', lightweight=True)
507
self.build_tree(['checkout/a'])
508
self.assertPathExists('checkout/a')
512
def test_store_and_restore_uncommitted(self):
513
checkout = self.prepare()
514
self.run_bzr(['switch', '--store', '-d', 'checkout', 'new'])
515
self.build_tree(['checkout/b'])
517
self.assertPathDoesNotExist('checkout/a')
518
self.assertPathExists('checkout/b')
519
self.run_bzr(['switch', '--store', '-d', 'checkout', 'orig'])
520
self.assertPathExists('checkout/a')
521
self.assertPathDoesNotExist('checkout/b')
523
def test_does_not_store(self):
525
self.run_bzr(['switch', '-d', 'checkout', 'new'])
526
self.assertPathExists('checkout/a')
528
def test_does_not_restore_changes(self):
530
self.run_bzr(['switch', '--store', '-d', 'checkout', 'new'])
531
self.assertPathDoesNotExist('checkout/a')
532
self.run_bzr(['switch', '-d', 'checkout', 'orig'])
533
self.assertPathDoesNotExist('checkout/a')
536
class TestSwitchStandAloneCorruption(TestCaseWithTransport):
538
def test_empty_tree_switch(self):
539
"""switch . on an empty tree gets infinite recursion
541
Inspired by: https://bugs.launchpad.net/bzr/+bug/1018628
543
self.script_runner = script.ScriptRunner()
544
self.script_runner.run_script(self, '''
546
Created a standalone tree (format: 2a)
548
2>brz: ERROR: switching would create a branch reference loop. Use the "bzr up" command to switch to a different revision.
551
def test_switch_on_previous_rev(self):
552
"""switch to previous rev in a standalone directory
554
Inspired by: https://bugs.launchpad.net/brz/+bug/1018628
556
self.script_runner = script.ScriptRunner()
557
self.script_runner.run_script(self, '''
559
Created a standalone tree (format: 2a)
560
$ brz commit -m 1 --unchanged
561
$ brz commit -m 2 --unchanged
563
2>brz: ERROR: switching would create a branch reference loop. Use the "bzr up" command to switch to a different revision.''',
564
null_output_matches_anything=True)
566
def test_switch_create_colo_locks_repo_path(self):
567
self.script_runner = script.ScriptRunner()
568
self.script_runner.run_script(self, '''
572
Created a standalone tree (format: 2a)
573
$ echo A > a && brz add a && brz commit -m A
580
''', null_output_matches_anything=True)
582
def test_switch_to_new_branch_on_old_rev(self):
583
"""switch to previous rev in a standalone directory
585
Inspired by: https://bugs.launchpad.net/brz/+bug/933362
587
self.script_runner = script.ScriptRunner()
588
self.script_runner.run_script(self, '''
590
Created a standalone tree (format: 2a)
591
$ brz switch -b trunk
592
2>Tree is up to date at revision 0.
593
2>Switched to branch trunk
594
$ brz commit -m 1 --unchanged
596
2>Committed revision 1.
597
$ brz commit -m 2 --unchanged
599
2>Committed revision 2.
600
$ brz switch -b blah -r1
601
2>Updated to revision 1.
602
2>Switched to branch blah