1
# Copyright (C) 2007-2012 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 bzrlib.controldir import ControlDir
29
from bzrlib.workingtree import WorkingTree
30
from bzrlib.tests import (
31
TestCaseWithTransport,
34
from bzrlib.tests.features import UnicodeFilenameFeature
35
from bzrlib.directory_service import directories
37
from bzrlib.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: .*/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: .*/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.bzrdir.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.bzrdir.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.bzrdir.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.bzrdir.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
set(['', 'anotherbranch']),
210
set(tree.branch.bzrdir.get_branches().keys()))
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.bzrdir.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.bzrdir.create_branch(name='foo')
230
repo.bzrdir.set_branch_reference(target_branch)
231
tree = repo.bzrdir.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.bzrdir.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.assertEquals(tree.last_revision(), revid1)
242
self.assertEquals(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.bzrdir.create_branch(name='foo')
249
repo.bzrdir.set_branch_reference(target_branch)
250
tree = repo.bzrdir.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
set([b.name for b in bzrdir.list_branches()]),
258
set(["foo", "anotherbranch"]))
259
self.assertEquals(bzrdir.open_branch().name, "anotherbranch")
260
self.assertEquals(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.bzrdir.create_branch(name='foo')
268
repo.bzrdir.set_branch_reference(target_branch)
269
tree = repo.bzrdir.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
set([b.name for b in bzrdir.list_branches()]),
277
set(["foo", u"branch\xe9"]))
278
self.assertEquals(bzrdir.open_branch().name, u"branch\xe9")
279
self.assertEquals(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
['bzr 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='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='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='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)
348
class FooLookup(object):
349
def look_up(self, name, url):
351
directories.register('foo:', FooLookup, 'Create branches named foo-')
352
self.addCleanup(directories.remove, 'foo:')
353
self.run_bzr('switch -b foo:branch2', working_dir='tree')
354
tree = WorkingTree.open('tree')
355
self.assertEndsWith(tree.branch.base, 'foo-branch2/')
357
def test_switch_with_post_switch_hook(self):
358
from bzrlib import branch as _mod_branch
360
_mod_branch.Branch.hooks.install_named_hook('post_switch',
362
self.make_branch_and_tree('branch')
363
self.run_bzr('branch branch branch2')
364
self.run_bzr('checkout branch checkout')
366
self.assertLength(0, calls)
367
out, err = self.run_bzr('switch ../branch2')
368
self.assertLength(1, calls)
370
def test_switch_lightweight_co_with_post_switch_hook(self):
371
from bzrlib import branch as _mod_branch
373
_mod_branch.Branch.hooks.install_named_hook('post_switch',
375
self.make_branch_and_tree('branch')
376
self.run_bzr('branch branch branch2')
377
self.run_bzr('checkout --lightweight branch checkout')
379
self.assertLength(0, calls)
380
out, err = self.run_bzr('switch ../branch2')
381
self.assertLength(1, calls)
383
def test_switch_lightweight_directory(self):
384
"""Test --directory option"""
386
# create a source branch
387
a_tree = self.make_branch_and_tree('a')
388
self.build_tree_contents([('a/a', 'initial\n')])
390
a_tree.commit(message='initial')
392
# clone and add a differing revision
393
b_tree = a_tree.bzrdir.sprout('b').open_workingtree()
394
self.build_tree_contents([('b/a', 'initial\nmore\n')])
395
b_tree.commit(message='more')
397
self.run_bzr('checkout --lightweight a checkout')
398
self.run_bzr('switch --directory checkout b')
399
self.assertFileEqual('initial\nmore\n', 'checkout/a')
402
class TestSwitchParentLocationBase(TestCaseWithTransport):
405
"""Set up a repository and branch ready for testing."""
406
super(TestSwitchParentLocationBase, self).setUp()
407
self.script_runner = script.ScriptRunner()
408
self.script_runner.run_script(self, '''
409
$ bzr init-repo --no-trees repo
412
shared repository: repo
413
$ bzr init repo/trunk
414
Created a repository branch...
415
Using shared repository: ...
418
def assertParent(self, expected_parent, branch):
419
"""Verify that the parent is not None and is set correctly."""
420
actual_parent = branch.get_parent()
421
self.assertIsSameRealPath(urlutils.local_path_to_url(expected_parent),
425
class TestSwitchParentLocation(TestSwitchParentLocationBase):
427
def _checkout_and_switch(self, option=''):
428
self.script_runner.run_script(self, '''
429
$ bzr checkout %(option)s repo/trunk checkout
431
$ bzr switch --create-branch switched
432
2>Tree is up to date at revision 0.
433
2>Switched to branch:...switched...
436
bound_branch = branch.Branch.open_containing('checkout')[0]
437
master_branch = branch.Branch.open_containing('repo/switched')[0]
438
return (bound_branch, master_branch)
440
def test_switch_parent_lightweight(self):
441
"""Lightweight checkout using bzr switch."""
442
bb, mb = self._checkout_and_switch(option='--lightweight')
443
self.assertParent('repo/trunk', bb)
444
self.assertParent('repo/trunk', mb)
446
def test_switch_parent_heavyweight(self):
447
"""Heavyweight checkout using bzr switch."""
448
bb, mb = self._checkout_and_switch()
449
self.assertParent('repo/trunk', bb)
450
self.assertParent('repo/trunk', mb)
453
class TestSwitchDoesntOpenMasterBranch(TestCaseWithTransport):
454
# See https://bugs.launchpad.net/bzr/+bug/812285
455
# "bzr switch --create-branch" can point the new branch's parent to the
456
# master branch, but it doesn't have to open it to do so.
458
def test_switch_create_doesnt_open_master_branch(self):
459
master = self.make_branch_and_tree('master')
461
# Note: not a lightweight checkout
462
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 TestSmartServerSwitch(TestCaseWithTransport):
479
def test_switch_lightweight(self):
480
self.setup_smart_server_with_call_log()
481
t = self.make_branch_and_tree('from')
482
for count in range(9):
483
t.commit(message='commit %d' % count)
484
out, err = self.run_bzr(['checkout', '--lightweight', self.get_url('from'),
486
self.reset_smart_call_log()
487
self.run_bzr(['switch', self.get_url('from')], working_dir='target')
488
# This figure represent the amount of work to perform this use case. It
489
# is entirely ok to reduce this number if a test fails due to rpc_count
490
# being too low. If rpc_count increases, more network roundtrips have
491
# become necessary for this use case. Please do not adjust this number
492
# upwards without agreement from bzr's network support maintainers.
493
self.assertLength(24, self.hpss_calls)
494
self.assertLength(4, self.hpss_connections)
495
self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
498
class TestSwitchUncommitted(TestCaseWithTransport):
501
tree = self.make_branch_and_tree('orig')
503
tree.branch.bzrdir.sprout('new')
504
checkout = tree.branch.create_checkout('checkout', lightweight=True)
505
self.build_tree(['checkout/a'])
506
self.assertPathExists('checkout/a')
510
def test_store_and_restore_uncommitted(self):
511
checkout = self.prepare()
512
self.run_bzr(['switch', '--store', '-d', 'checkout', 'new'])
513
self.build_tree(['checkout/b'])
515
self.assertPathDoesNotExist('checkout/a')
516
self.assertPathExists('checkout/b')
517
self.run_bzr(['switch', '--store', '-d', 'checkout', 'orig'])
518
self.assertPathExists('checkout/a')
519
self.assertPathDoesNotExist('checkout/b')
521
def test_does_not_store(self):
523
self.run_bzr(['switch', '-d', 'checkout', 'new'])
524
self.assertPathExists('checkout/a')
526
def test_does_not_restore_changes(self):
528
self.run_bzr(['switch', '--store', '-d', 'checkout', 'new'])
529
self.assertPathDoesNotExist('checkout/a')
530
self.run_bzr(['switch', '-d', 'checkout', 'orig'])
531
self.assertPathDoesNotExist('checkout/a')