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