1# Copyright (C) 2007-2012, 2016 Canonical Ltd
2# -*- coding: utf-8 -*-
3#
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.
8#
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.
13#
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
17
18
19"""Tests for the switch command of bzr."""
20
21import os
22
23from breezy.controldir import ControlDir
24from breezy import (
25    osutils,
26    urlutils,
27    branch,
28    )
29from breezy.workingtree import WorkingTree
30from breezy.tests import (
31    TestCaseWithTransport,
32    script,
33    )
34from breezy.tests.features import UnicodeFilenameFeature
35from breezy.directory_service import directories
36
37
38class TestSwitch(TestCaseWithTransport):
39
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'])
43        tree.add('file-1')
44        tree.commit('rev1')
45        tree.add('file-2')
46        tree.commit('rev2')
47        return tree
48
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')
53        os.chdir('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)
58
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')
66        os.chdir('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)
72
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(),
82                         False)
83        self.run_bzr('switch branch2', working_dir='checkout')
84
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(),
89                         False)
90
91    def test_switch_nick(self):
92        self._test_switch_nick(lightweight=False)
93
94    def test_switch_nick_lightweight(self):
95        self._test_switch_nick(lightweight=True)
96
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(),
108                         "explicit_nick")
109        self.run_bzr('switch branch2', working_dir='checkout')
110
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(),
115                         tree2.branch.nick)
116
117    def test_switch_explicit_nick(self):
118        self._test_switch_explicit_nick(lightweight=False)
119
120    def test_switch_explicit_nick_lightweight(self):
121        self._test_switch_explicit_nick(lightweight=True)
122
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')
127        tree1.commit('foo')
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)
136
137    def test_switch_finds_relative_bound_branch(self):
138        """Using switch on a heavy checkout should find master sibling
139
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"""
144
145        self.build_tree(['repo/',
146                         'heavyco/'])
147        tree1 = self.make_branch_and_tree('repo/brancha')
148        tree1.commit('foo')
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())
159
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')
165        tree1.commit('foo')
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)
174
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')
180        tree1.commit('foo')
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)
189
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')
196
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'])
201        tree.add('file-1')
202        revid1 = tree.commit('rev1')
203        tree.add('file-2')
204        revid2 = tree.commit('rev2')
205        self.run_bzr(['switch', '-b', 'anotherbranch'])
206        self.assertEqual(
207            {'', 'anotherbranch'},
208            set(tree.branch.controldir.branch_names()))
209
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'])
214        tree.add('file-1')
215        revid1 = tree.commit('rev1')
216        tree.add('file-2')
217        revid2 = tree.commit('rev2')
218        tree.controldir.create_branch(name='foo')
219        self.run_bzr_error(['Cannot switch a branch, only a checkout.'],
220                           'switch foo')
221        self.run_bzr(['switch', '--force', 'foo'])
222
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'])
231        tree.add('file-1')
232        revid1 = tree.commit('rev1')
233        tree.add('file-2')
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)
241
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'])
250        tree.add('file-1')
251        revid1 = tree.commit('rev1')
252        self.run_bzr(['switch', '-b', 'anotherbranch'], working_dir='branch-1')
253        bzrdir = ControlDir.open("branch-1")
254        self.assertEqual(
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)
259
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'])
269        tree.add('file-1')
270        revid1 = tree.commit('rev1')
271        self.run_bzr(['switch', '-b', u'branch\xe9'], working_dir='branch-1')
272        bzrdir = ControlDir.open("branch-1")
273        self.assertEqual(
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)
278
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
288        self.run_bzr_error(
289            ['brz switch --revision takes exactly one revision identifier'],
290            ['switch', '-r0..2'], working_dir='checkout')
291
292    def prepare_lightweight_switch(self):
293        branch = self.make_branch('branch')
294        branch.create_checkout('tree', lightweight=True)
295        osutils.rename('branch', 'branch1')
296
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/')
302
303    def test_switch_lightweight_after_branch_moved_relative(self):
304        self.prepare_lightweight_switch()
305        self.run_bzr('switch --force branch1',
306                     working_dir='tree')
307        branch_location = WorkingTree.open('tree').branch.base
308        self.assertEndsWith(branch_location, 'branch1/')
309
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')
314
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/')
322
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)
332
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)
342
343    def test_create_branch_directory_services(self):
344        branch = self.make_branch('branch')
345        tree = branch.create_checkout('tree', lightweight=True)
346
347        class FooLookup(object):
348            def look_up(self, name, url, purpose=None):
349                return 'foo-' + name
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/')
355
356    def test_switch_with_post_switch_hook(self):
357        from breezy import branch as _mod_branch
358        calls = []
359        _mod_branch.Branch.hooks.install_named_hook('post_switch',
360                                                    calls.append, None)
361        self.make_branch_and_tree('branch')
362        self.run_bzr('branch branch branch2')
363        self.run_bzr('checkout branch checkout')
364        os.chdir('checkout')
365        self.assertLength(0, calls)
366        out, err = self.run_bzr('switch ../branch2')
367        self.assertLength(1, calls)
368
369    def test_switch_lightweight_co_with_post_switch_hook(self):
370        from breezy import branch as _mod_branch
371        calls = []
372        _mod_branch.Branch.hooks.install_named_hook('post_switch',
373                                                    calls.append, None)
374        self.make_branch_and_tree('branch')
375        self.run_bzr('branch branch branch2')
376        self.run_bzr('checkout --lightweight branch checkout')
377        os.chdir('checkout')
378        self.assertLength(0, calls)
379        out, err = self.run_bzr('switch ../branch2')
380        self.assertLength(1, calls)
381
382    def test_switch_lightweight_directory(self):
383        """Test --directory option"""
384
385        # create a source branch
386        a_tree = self.make_branch_and_tree('a')
387        self.build_tree_contents([('a/a', b'initial\n')])
388        a_tree.add('a')
389        a_tree.commit(message='initial')
390
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')
395
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')
399
400
401class TestSwitchParentLocationBase(TestCaseWithTransport):
402
403    def setUp(self):
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
409                Shared repository...
410                Location:
411                  shared repository: repo
412                $ brz init repo/trunk
413                Created a repository branch...
414                Using shared repository: ...
415                ''')
416
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),
421                                  branch.get_parent())
422
423
424class TestSwitchParentLocation(TestSwitchParentLocationBase):
425
426    def _checkout_and_switch(self, option=''):
427        self.script_runner.run_script(self, '''
428                $ brz checkout %(option)s repo/trunk checkout
429                $ cd checkout
430                $ brz switch --create-branch switched
431                2>Tree is up to date at revision 0.
432                2>Switched to branch at .../switched/
433                $ cd ..
434                ''' % locals())
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)
438
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)
444
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)
450
451
452class 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.
456
457    def test_switch_create_doesnt_open_master_branch(self):
458        master = self.make_branch_and_tree('master')
459        master.commit('one')
460        # Note: not a lightweight checkout
461        checkout = master.branch.create_checkout('checkout')
462        opened = []
463
464        def open_hook(branch):
465            # Just append the final directory of the branch
466            name = branch.base.rstrip('/').rsplit('/', 1)[1]
467            opened.append(name)
468        branch.Branch.hooks.install_named_hook('open', open_hook,
469                                               'open_hook_logger')
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'))
475
476
477class TestSwitchUncommitted(TestCaseWithTransport):
478
479    def prepare(self):
480        tree = self.make_branch_and_tree('orig')
481        tree.commit('')
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')
486        checkout.add('a')
487        return checkout
488
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'])
493        checkout.add('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')
499
500    def test_does_not_store(self):
501        self.prepare()
502        self.run_bzr(['switch', '-d', 'checkout', 'new'])
503        self.assertPathExists('checkout/a')
504
505    def test_does_not_restore_changes(self):
506        self.prepare()
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')
511
512
513class TestSwitchStandAloneCorruption(TestCaseWithTransport):
514
515    def test_empty_tree_switch(self):
516        """switch . on an empty tree gets infinite recursion
517
518        Inspired by: https://bugs.launchpad.net/bzr/+bug/1018628
519        """
520        self.script_runner = script.ScriptRunner()
521        self.script_runner.run_script(self, '''
522            $ brz init
523            Created a standalone tree (format: 2a)
524            $ brz switch .
525            2>brz: ERROR: switching would create a branch reference loop. Use the "bzr up" command to switch to a different revision.
526            ''')
527
528    def test_switch_on_previous_rev(self):
529        """switch to previous rev in a standalone directory
530
531        Inspired by: https://bugs.launchpad.net/brz/+bug/1018628
532        """
533        self.script_runner = script.ScriptRunner()
534        self.script_runner.run_script(self, '''
535           $ brz init
536           Created a standalone tree (format: 2a)
537           $ brz commit -m 1 --unchanged
538           $ brz commit -m 2 --unchanged
539           $ brz switch -r 1
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)
542
543    def test_switch_create_colo_locks_repo_path(self):
544        self.script_runner = script.ScriptRunner()
545        self.script_runner.run_script(self, '''
546            $ mkdir mywork
547            $ cd mywork
548            $ brz init
549            Created a standalone tree (format: 2a)
550            $ echo A > a && brz add a && brz commit -m A
551            $ brz switch -b br1
552            $ cd ..
553            $ mv mywork mywork1
554            $ cd mywork1
555            $ brz branches
556              br1
557            ''', null_output_matches_anything=True)
558
559    def test_switch_to_new_branch_on_old_rev(self):
560        """switch to previous rev in a standalone directory
561
562        Inspired by: https://bugs.launchpad.net/brz/+bug/933362
563        """
564        self.script_runner = script.ScriptRunner()
565        self.script_runner.run_script(self, '''
566           $ brz init
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
572           2>Committing to: ...
573           2>Committed revision 1.
574           $ brz commit -m 2 --unchanged
575           2>Committing to: ...
576           2>Committed revision 2.
577           $ brz switch -b blah -r1
578           2>Updated to revision 1.
579           2>Switched to branch blah
580           $ brz branches
581           * blah
582             trunk
583           $ brz st
584           ''')
585