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
38
class TestSwitch(TestCaseWithTransport):
40
def _create_sample_tree(self):
41
tree = self.make_branch_and_tree('branch-1')
42
self.build_tree(['branch-1/file-1', 'branch-1/file-2'])
49
def test_switch_up_to_date_light_checkout(self):
50
self.make_branch_and_tree('branch')
51
self.run_bzr('branch branch branch2')
52
self.run_bzr('checkout --lightweight branch checkout')
54
out, err = self.run_bzr('switch ../branch2')
55
self.assertContainsRe(err, 'Tree is up to date at revision 0.\n')
56
self.assertContainsRe(err, 'Switched to branch at .*/branch2.\n')
57
self.assertEqual('', out)
59
def test_switch_out_of_date_light_checkout(self):
60
self.make_branch_and_tree('branch')
61
self.run_bzr('branch branch branch2')
62
self.build_tree(['branch2/file'])
63
self.run_bzr('add branch2/file')
64
self.run_bzr('commit -m add-file branch2')
65
self.run_bzr('checkout --lightweight branch checkout')
67
out, err = self.run_bzr('switch ../branch2')
68
#self.assertContainsRe(err, '\+N file')
69
self.assertContainsRe(err, 'Updated to revision 1.\n')
70
self.assertContainsRe(err, 'Switched to branch at .*/branch2.\n')
71
self.assertEqual('', out)
73
def _test_switch_nick(self, lightweight):
74
"""Check that the nick gets switched too."""
75
tree1 = self.make_branch_and_tree('branch1')
76
tree2 = self.make_branch_and_tree('branch2')
77
tree2.pull(tree1.branch)
78
checkout = tree1.branch.create_checkout('checkout',
79
lightweight=lightweight)
80
self.assertEqual(checkout.branch.nick, tree1.branch.nick)
81
self.assertEqual(checkout.branch.get_config().has_explicit_nickname(),
83
self.run_bzr('switch branch2', working_dir='checkout')
85
# we need to get the tree again, otherwise we don't get the new branch
86
checkout = WorkingTree.open('checkout')
87
self.assertEqual(checkout.branch.nick, tree2.branch.nick)
88
self.assertEqual(checkout.branch.get_config().has_explicit_nickname(),
91
def test_switch_nick(self):
92
self._test_switch_nick(lightweight=False)
94
def test_switch_nick_lightweight(self):
95
self._test_switch_nick(lightweight=True)
97
def _test_switch_explicit_nick(self, lightweight):
98
"""Check that the nick gets switched too."""
99
tree1 = self.make_branch_and_tree('branch1')
100
tree2 = self.make_branch_and_tree('branch2')
101
tree2.pull(tree1.branch)
102
checkout = tree1.branch.create_checkout('checkout',
103
lightweight=lightweight)
104
self.assertEqual(checkout.branch.nick, tree1.branch.nick)
105
checkout.branch.nick = "explicit_nick"
106
self.assertEqual(checkout.branch.nick, "explicit_nick")
107
self.assertEqual(checkout.branch.get_config()._get_explicit_nickname(),
109
self.run_bzr('switch branch2', working_dir='checkout')
111
# we need to get the tree again, otherwise we don't get the new branch
112
checkout = WorkingTree.open('checkout')
113
self.assertEqual(checkout.branch.nick, tree2.branch.nick)
114
self.assertEqual(checkout.branch.get_config()._get_explicit_nickname(),
117
def test_switch_explicit_nick(self):
118
self._test_switch_explicit_nick(lightweight=False)
120
def test_switch_explicit_nick_lightweight(self):
121
self._test_switch_explicit_nick(lightweight=True)
123
def test_switch_finds_relative_branch(self):
124
"""Switch will find 'foo' relative to the branch the checkout is of."""
125
self.build_tree(['repo/'])
126
tree1 = self.make_branch_and_tree('repo/brancha')
128
tree2 = self.make_branch_and_tree('repo/branchb')
129
tree2.pull(tree1.branch)
130
branchb_id = tree2.commit('bar')
131
checkout = tree1.branch.create_checkout('checkout', lightweight=True)
132
self.run_bzr(['switch', 'branchb'], working_dir='checkout')
133
self.assertEqual(branchb_id, checkout.last_revision())
134
checkout = checkout.controldir.open_workingtree()
135
self.assertEqual(tree2.branch.base, checkout.branch.base)
137
def test_switch_finds_relative_bound_branch(self):
138
"""Using switch on a heavy checkout should find master sibling
140
The behaviour of lighweight and heavy checkouts should be
141
consistent when using the convenient "switch to sibling" feature
142
Both should switch to a sibling of the branch
143
they are bound to, and not a sibling of themself"""
145
self.build_tree(['repo/',
147
tree1 = self.make_branch_and_tree('repo/brancha')
149
tree2 = self.make_branch_and_tree('repo/branchb')
150
tree2.pull(tree1.branch)
151
branchb_id = tree2.commit('bar')
152
checkout = tree1.branch.create_checkout('heavyco/a', lightweight=False)
153
self.run_bzr(['switch', 'branchb'], working_dir='heavyco/a')
154
# Refresh checkout as 'switch' modified it
155
checkout = checkout.controldir.open_workingtree()
156
self.assertEqual(branchb_id, checkout.last_revision())
157
self.assertEqual(tree2.branch.base,
158
checkout.branch.get_bound_location())
160
def test_switch_finds_relative_unicode_branch(self):
161
"""Switch will find 'foo' relative to the branch the checkout is of."""
162
self.requireFeature(UnicodeFilenameFeature)
163
self.build_tree(['repo/'])
164
tree1 = self.make_branch_and_tree('repo/brancha')
166
tree2 = self.make_branch_and_tree(u'repo/branch\xe9')
167
tree2.pull(tree1.branch)
168
branchb_id = tree2.commit('bar')
169
checkout = tree1.branch.create_checkout('checkout', lightweight=True)
170
self.run_bzr(['switch', u'branch\xe9'], working_dir='checkout')
171
self.assertEqual(branchb_id, checkout.last_revision())
172
checkout = checkout.controldir.open_workingtree()
173
self.assertEqual(tree2.branch.base, checkout.branch.base)
175
def test_switch_finds_relative_unicode_branch(self):
176
"""Switch will find 'foo' relative to the branch the checkout is of."""
177
self.requireFeature(UnicodeFilenameFeature)
178
self.build_tree(['repo/'])
179
tree1 = self.make_branch_and_tree('repo/brancha')
181
tree2 = self.make_branch_and_tree(u'repo/branch\xe9')
182
tree2.pull(tree1.branch)
183
branchb_id = tree2.commit('bar')
184
checkout = tree1.branch.create_checkout('checkout', lightweight=True)
185
self.run_bzr(['switch', u'branch\xe9'], working_dir='checkout')
186
self.assertEqual(branchb_id, checkout.last_revision())
187
checkout = checkout.controldir.open_workingtree()
188
self.assertEqual(tree2.branch.base, checkout.branch.base)
190
def test_switch_revision(self):
191
tree = self._create_sample_tree()
192
checkout = tree.branch.create_checkout('checkout', lightweight=True)
193
self.run_bzr(['switch', 'branch-1', '-r1'], working_dir='checkout')
194
self.assertPathExists('checkout/file-1')
195
self.assertPathDoesNotExist('checkout/file-2')
197
def test_switch_into_colocated(self):
198
# Create a new colocated branch from an existing non-colocated branch.
199
tree = self.make_branch_and_tree('.', format='development-colo')
200
self.build_tree(['file-1', 'file-2'])
202
revid1 = tree.commit('rev1')
204
revid2 = tree.commit('rev2')
205
self.run_bzr(['switch', '-b', 'anotherbranch'])
207
{'', 'anotherbranch'},
208
set(tree.branch.controldir.branch_names()))
210
def test_switch_into_unrelated_colocated(self):
211
# Create a new colocated branch from an existing non-colocated branch.
212
tree = self.make_branch_and_tree('.', format='development-colo')
213
self.build_tree(['file-1', 'file-2'])
215
revid1 = tree.commit('rev1')
217
revid2 = tree.commit('rev2')
218
tree.controldir.create_branch(name='foo')
219
self.run_bzr_error(['Cannot switch a branch, only a checkout.'],
221
self.run_bzr(['switch', '--force', 'foo'])
223
def test_switch_existing_colocated(self):
224
# Create a branch branch-1 that initially is a checkout of 'foo'
225
# Use switch to change it to 'anotherbranch'
226
repo = self.make_repository('branch-1', format='development-colo')
227
target_branch = repo.controldir.create_branch(name='foo')
228
repo.controldir.set_branch_reference(target_branch)
229
tree = repo.controldir.create_workingtree()
230
self.build_tree(['branch-1/file-1', 'branch-1/file-2'])
232
revid1 = tree.commit('rev1')
234
revid2 = tree.commit('rev2')
235
otherbranch = tree.controldir.create_branch(name='anotherbranch')
236
otherbranch.generate_revision_history(revid1)
237
self.run_bzr(['switch', 'anotherbranch'], working_dir='branch-1')
238
tree = WorkingTree.open("branch-1")
239
self.assertEqual(tree.last_revision(), revid1)
240
self.assertEqual(tree.branch.control_url, otherbranch.control_url)
242
def test_switch_new_colocated(self):
243
# Create a branch branch-1 that initially is a checkout of 'foo'
244
# Use switch to create 'anotherbranch' which derives from that
245
repo = self.make_repository('branch-1', format='development-colo')
246
target_branch = repo.controldir.create_branch(name='foo')
247
repo.controldir.set_branch_reference(target_branch)
248
tree = repo.controldir.create_workingtree()
249
self.build_tree(['branch-1/file-1', 'branch-1/file-2'])
251
revid1 = tree.commit('rev1')
252
self.run_bzr(['switch', '-b', 'anotherbranch'], working_dir='branch-1')
253
bzrdir = ControlDir.open("branch-1")
255
{b.name for b in bzrdir.list_branches()},
256
{"foo", "anotherbranch"})
257
self.assertEqual(bzrdir.open_branch().name, "anotherbranch")
258
self.assertEqual(bzrdir.open_branch().last_revision(), revid1)
260
def test_switch_new_colocated_unicode(self):
261
# Create a branch branch-1 that initially is a checkout of 'foo'
262
# Use switch to create 'branch\xe9' which derives from that
263
self.requireFeature(UnicodeFilenameFeature)
264
repo = self.make_repository('branch-1', format='development-colo')
265
target_branch = repo.controldir.create_branch(name='foo')
266
repo.controldir.set_branch_reference(target_branch)
267
tree = repo.controldir.create_workingtree()
268
self.build_tree(['branch-1/file-1', 'branch-1/file-2'])
270
revid1 = tree.commit('rev1')
271
self.run_bzr(['switch', '-b', u'branch\xe9'], working_dir='branch-1')
272
bzrdir = ControlDir.open("branch-1")
274
{b.name for b in bzrdir.list_branches()},
275
{"foo", u"branch\xe9"})
276
self.assertEqual(bzrdir.open_branch().name, u"branch\xe9")
277
self.assertEqual(bzrdir.open_branch().last_revision(), revid1)
279
def test_switch_only_revision(self):
280
tree = self._create_sample_tree()
281
checkout = tree.branch.create_checkout('checkout', lightweight=True)
282
self.assertPathExists('checkout/file-1')
283
self.assertPathExists('checkout/file-2')
284
self.run_bzr(['switch', '-r1'], working_dir='checkout')
285
self.assertPathExists('checkout/file-1')
286
self.assertPathDoesNotExist('checkout/file-2')
287
# Check that we don't accept a range
289
['brz switch --revision takes exactly one revision identifier'],
290
['switch', '-r0..2'], working_dir='checkout')
292
def prepare_lightweight_switch(self):
293
branch = self.make_branch('branch')
294
branch.create_checkout('tree', lightweight=True)
295
osutils.rename('branch', 'branch1')
297
def test_switch_lightweight_after_branch_moved(self):
298
self.prepare_lightweight_switch()
299
self.run_bzr('switch --force ../branch1', working_dir='tree')
300
branch_location = WorkingTree.open('tree').branch.base
301
self.assertEndsWith(branch_location, 'branch1/')
303
def test_switch_lightweight_after_branch_moved_relative(self):
304
self.prepare_lightweight_switch()
305
self.run_bzr('switch --force branch1',
307
branch_location = WorkingTree.open('tree').branch.base
308
self.assertEndsWith(branch_location, 'branch1/')
310
def test_create_branch_no_branch(self):
311
self.prepare_lightweight_switch()
312
self.run_bzr_error(['cannot create branch without source branch'],
313
'switch --create-branch ../branch2', working_dir='tree')
315
def test_create_branch(self):
316
branch = self.make_branch('branch')
317
tree = branch.create_checkout('tree', lightweight=True)
318
tree.commit('one', rev_id=b'rev-1')
319
self.run_bzr('switch --create-branch ../branch2', working_dir='tree')
320
tree = WorkingTree.open('tree')
321
self.assertEndsWith(tree.branch.base, '/branch2/')
323
def test_create_branch_local(self):
324
branch = self.make_branch('branch')
325
tree = branch.create_checkout('tree', lightweight=True)
326
tree.commit('one', rev_id=b'rev-1')
327
self.run_bzr('switch --create-branch branch2', working_dir='tree')
328
tree = WorkingTree.open('tree')
329
# The new branch should have been created at the same level as
330
# 'branch', because we did not have a '/' segment
331
self.assertEqual(branch.base[:-1] + '2/', tree.branch.base)
333
def test_create_branch_short_name(self):
334
branch = self.make_branch('branch')
335
tree = branch.create_checkout('tree', lightweight=True)
336
tree.commit('one', rev_id=b'rev-1')
337
self.run_bzr('switch -b branch2', working_dir='tree')
338
tree = WorkingTree.open('tree')
339
# The new branch should have been created at the same level as
340
# 'branch', because we did not have a '/' segment
341
self.assertEqual(branch.base[:-1] + '2/', tree.branch.base)
343
def test_create_branch_directory_services(self):
344
branch = self.make_branch('branch')
345
tree = branch.create_checkout('tree', lightweight=True)
347
class FooLookup(object):
348
def look_up(self, name, url, purpose=None):
350
directories.register('foo:', FooLookup, 'Create branches named foo-')
351
self.addCleanup(directories.remove, 'foo:')
352
self.run_bzr('switch -b foo:branch2', working_dir='tree')
353
tree = WorkingTree.open('tree')
354
self.assertEndsWith(tree.branch.base, 'foo-branch2/')
356
def test_switch_with_post_switch_hook(self):
357
from breezy import branch as _mod_branch
359
_mod_branch.Branch.hooks.install_named_hook('post_switch',
361
self.make_branch_and_tree('branch')
362
self.run_bzr('branch branch branch2')
363
self.run_bzr('checkout branch checkout')
365
self.assertLength(0, calls)
366
out, err = self.run_bzr('switch ../branch2')
367
self.assertLength(1, calls)
369
def test_switch_lightweight_co_with_post_switch_hook(self):
370
from breezy import branch as _mod_branch
372
_mod_branch.Branch.hooks.install_named_hook('post_switch',
374
self.make_branch_and_tree('branch')
375
self.run_bzr('branch branch branch2')
376
self.run_bzr('checkout --lightweight branch checkout')
378
self.assertLength(0, calls)
379
out, err = self.run_bzr('switch ../branch2')
380
self.assertLength(1, calls)
382
def test_switch_lightweight_directory(self):
383
"""Test --directory option"""
385
# create a source branch
386
a_tree = self.make_branch_and_tree('a')
387
self.build_tree_contents([('a/a', b'initial\n')])
389
a_tree.commit(message='initial')
391
# clone and add a differing revision
392
b_tree = a_tree.controldir.sprout('b').open_workingtree()
393
self.build_tree_contents([('b/a', b'initial\nmore\n')])
394
b_tree.commit(message='more')
396
self.run_bzr('checkout --lightweight a checkout')
397
self.run_bzr('switch --directory checkout b')
398
self.assertFileEqual(b'initial\nmore\n', 'checkout/a')
401
class TestSwitchParentLocationBase(TestCaseWithTransport):
404
"""Set up a repository and branch ready for testing."""
405
super(TestSwitchParentLocationBase, self).setUp()
406
self.script_runner = script.ScriptRunner()
407
self.script_runner.run_script(self, '''
408
$ brz init-shared-repo --no-trees repo
411
shared repository: repo
412
$ brz init repo/trunk
413
Created a repository branch...
414
Using shared repository: ...
417
def assertParent(self, expected_parent, branch):
418
"""Verify that the parent is not None and is set correctly."""
419
actual_parent = branch.get_parent()
420
self.assertIsSameRealPath(urlutils.local_path_to_url(expected_parent),
424
class TestSwitchParentLocation(TestSwitchParentLocationBase):
426
def _checkout_and_switch(self, option=''):
427
self.script_runner.run_script(self, '''
428
$ brz checkout %(option)s repo/trunk checkout
430
$ brz switch --create-branch switched
431
2>Tree is up to date at revision 0.
432
2>Switched to branch at .../switched/
435
bound_branch = branch.Branch.open_containing('checkout')[0]
436
master_branch = branch.Branch.open_containing('repo/switched')[0]
437
return (bound_branch, master_branch)
439
def test_switch_parent_lightweight(self):
440
"""Lightweight checkout using brz switch."""
441
bb, mb = self._checkout_and_switch(option='--lightweight')
442
self.assertParent('repo/trunk', bb)
443
self.assertParent('repo/trunk', mb)
445
def test_switch_parent_heavyweight(self):
446
"""Heavyweight checkout using brz switch."""
447
bb, mb = self._checkout_and_switch()
448
self.assertParent('repo/trunk', bb)
449
self.assertParent('repo/trunk', mb)
452
class TestSwitchDoesntOpenMasterBranch(TestCaseWithTransport):
453
# See https://bugs.launchpad.net/bzr/+bug/812285
454
# "brz switch --create-branch" can point the new branch's parent to the
455
# master branch, but it doesn't have to open it to do so.
457
def test_switch_create_doesnt_open_master_branch(self):
458
master = self.make_branch_and_tree('master')
460
# Note: not a lightweight checkout
461
checkout = master.branch.create_checkout('checkout')
464
def open_hook(branch):
465
# Just append the final directory of the branch
466
name = branch.base.rstrip('/').rsplit('/', 1)[1]
468
branch.Branch.hooks.install_named_hook('open', open_hook,
470
self.run_bzr('switch --create-branch -d checkout feature')
471
# We only open the master branch 1 time.
472
# This test should be cleaner to write, but see bug:
473
# https://bugs.launchpad.net/bzr/+bug/812295
474
self.assertEqual(1, opened.count('master'))
477
class TestSwitchUncommitted(TestCaseWithTransport):
480
tree = self.make_branch_and_tree('orig')
482
tree.branch.controldir.sprout('new')
483
checkout = tree.branch.create_checkout('checkout', lightweight=True)
484
self.build_tree(['checkout/a'])
485
self.assertPathExists('checkout/a')
489
def test_store_and_restore_uncommitted(self):
490
checkout = self.prepare()
491
self.run_bzr(['switch', '--store', '-d', 'checkout', 'new'])
492
self.build_tree(['checkout/b'])
494
self.assertPathDoesNotExist('checkout/a')
495
self.assertPathExists('checkout/b')
496
self.run_bzr(['switch', '--store', '-d', 'checkout', 'orig'])
497
self.assertPathExists('checkout/a')
498
self.assertPathDoesNotExist('checkout/b')
500
def test_does_not_store(self):
502
self.run_bzr(['switch', '-d', 'checkout', 'new'])
503
self.assertPathExists('checkout/a')
505
def test_does_not_restore_changes(self):
507
self.run_bzr(['switch', '--store', '-d', 'checkout', 'new'])
508
self.assertPathDoesNotExist('checkout/a')
509
self.run_bzr(['switch', '-d', 'checkout', 'orig'])
510
self.assertPathDoesNotExist('checkout/a')
513
class TestSwitchStandAloneCorruption(TestCaseWithTransport):
515
def test_empty_tree_switch(self):
516
"""switch . on an empty tree gets infinite recursion
518
Inspired by: https://bugs.launchpad.net/bzr/+bug/1018628
520
self.script_runner = script.ScriptRunner()
521
self.script_runner.run_script(self, '''
523
Created a standalone tree (format: 2a)
525
2>brz: ERROR: switching would create a branch reference loop. Use the "bzr up" command to switch to a different revision.
528
def test_switch_on_previous_rev(self):
529
"""switch to previous rev in a standalone directory
531
Inspired by: https://bugs.launchpad.net/brz/+bug/1018628
533
self.script_runner = script.ScriptRunner()
534
self.script_runner.run_script(self, '''
536
Created a standalone tree (format: 2a)
537
$ brz commit -m 1 --unchanged
538
$ brz commit -m 2 --unchanged
540
2>brz: ERROR: switching would create a branch reference loop. Use the "bzr up" command to switch to a different revision.''',
541
null_output_matches_anything=True)
543
def test_switch_create_colo_locks_repo_path(self):
544
self.script_runner = script.ScriptRunner()
545
self.script_runner.run_script(self, '''
549
Created a standalone tree (format: 2a)
550
$ echo A > a && brz add a && brz commit -m A
557
''', null_output_matches_anything=True)
559
def test_switch_to_new_branch_on_old_rev(self):
560
"""switch to previous rev in a standalone directory
562
Inspired by: https://bugs.launchpad.net/brz/+bug/933362
564
self.script_runner = script.ScriptRunner()
565
self.script_runner.run_script(self, '''
567
Created a standalone tree (format: 2a)
568
$ brz switch -b trunk
569
2>Tree is up to date at revision 0.
570
2>Switched to branch trunk
571
$ brz commit -m 1 --unchanged
573
2>Committed revision 1.
574
$ brz commit -m 2 --unchanged
576
2>Committed revision 2.
577
$ brz switch -b blah -r1
578
2>Updated to revision 1.
579
2>Switched to branch blah