Rev 6540: (abentley) switch --store stores uncommitted changes in branch (Aaron in file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/
Patch Queue Manager
pqm at pqm.ubuntu.com
Mon Jul 23 17:56:46 UTC 2012
At file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/
------------------------------------------------------------
revno: 6540 [merge]
revision-id: pqm at pqm.ubuntu.com-20120723175645-92crzj8j7bfnuglm
parent: pqm at pqm.ubuntu.com-20120720132332-p37f488vbgwkktw5
parent: aaron at aaronbentley.com-20120723165758-wxqhfigzg6orby7l
committer: Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Mon 2012-07-23 17:56:45 +0000
message:
(abentley) switch --store stores uncommitted changes in branch (Aaron
Bentley)
added:
doc/en/user-guide/switch_store.txt switch_store.txt-20120723161427-ocgl4tcfz0kyzjcx-1
modified:
bzrlib/branch.py branch.py-20050309040759-e4baf4e0d046576e
bzrlib/builtins.py builtins.py-20050830033751-fc01482b9ca23183
bzrlib/errors.py errors.py-20050309040759-20512168c4e14fbd
bzrlib/lock.py lock.py-20050527050856-ec090bb51bc03349
bzrlib/remote.py remote.py-20060720103555-yeeg2x51vn0rbtdp-1
bzrlib/shelf.py prepare_shelf.py-20081005181341-n74qe6gu1e65ad4v-1
bzrlib/switch.py switch.py-20071116011000-v5lnw7d2wkng9eux-1
bzrlib/tests/blackbox/test_switch.py test_switch.py-20071122111948-0c5en6uz92bwl76h-1
bzrlib/tests/per_branch/test_branch.py testbranch.py-20050711070244-121d632bc37d7253
bzrlib/tests/test_switch.py test_switch.py-20071116011000-v5lnw7d2wkng9eux-2
bzrlib/tests/test_workingtree.py testworkingtree.py-20051004024258-b88d0fe8f101d468
bzrlib/workingtree.py workingtree.py-20050511021032-29b6ec0a681e02e3
doc/en/release-notes/bzr-2.6.txt bzr2.6.txt-20120116134316-8w1xxom1c7vcu1t5-1
doc/en/user-guide/index-plain.txt indexplain.txt-20090909123806-96yfsgrqwra8cwq7-2
doc/en/user-guide/index.txt indexfor2x.txt-20090722150335-qt9yh29f930m4v0r-1
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py 2012-07-06 11:27:16 +0000
+++ b/bzrlib/branch.py 2012-07-19 18:28:25 +0000
@@ -39,6 +39,7 @@
repository,
revision as _mod_revision,
rio,
+ shelf,
tag as _mod_tag,
transport,
ui,
@@ -251,6 +252,23 @@
"""
raise NotImplementedError(self._get_config)
+ def store_uncommitted(self, creator):
+ """Store uncommitted changes from a ShelfCreator.
+
+ :param creator: The ShelfCreator containing uncommitted changes, or
+ None to delete any stored changes.
+ :raises: ChangesAlreadyStored if the branch already has changes.
+ """
+ raise NotImplementedError(self.store_uncommitted)
+
+ def get_unshelver(self, tree):
+ """Return a shelf.Unshelver for this branch and tree.
+
+ :param tree: The tree to use to construct the Unshelver.
+ :return: an Unshelver or None if no changes are stored.
+ """
+ raise NotImplementedError(self.get_unshelver)
+
def _get_fallback_repository(self, url, possible_transports):
"""Get the repository we fallback to at url."""
url = urlutils.join(self.base, url)
@@ -2386,6 +2404,45 @@
self.conf_store = _mod_config.BranchStore(self)
return self.conf_store
+ def _uncommitted_branch(self):
+ """Return the branch that may contain uncommitted changes."""
+ master = self.get_master_branch()
+ if master is not None:
+ return master
+ else:
+ return self
+
+ def store_uncommitted(self, creator):
+ """Store uncommitted changes from a ShelfCreator.
+
+ :param creator: The ShelfCreator containing uncommitted changes, or
+ None to delete any stored changes.
+ :raises: ChangesAlreadyStored if the branch already has changes.
+ """
+ branch = self._uncommitted_branch()
+ if creator is None:
+ branch._transport.delete('stored-transform')
+ return
+ if branch._transport.has('stored-transform'):
+ raise errors.ChangesAlreadyStored
+ transform = StringIO()
+ creator.write_shelf(transform)
+ transform.seek(0)
+ branch._transport.put_file('stored-transform', transform)
+
+ def get_unshelver(self, tree):
+ """Return a shelf.Unshelver for this branch and tree.
+
+ :param tree: The tree to use to construct the Unshelver.
+ :return: an Unshelver or None if no changes are stored.
+ """
+ branch = self._uncommitted_branch()
+ try:
+ transform = branch._transport.get('stored-transform')
+ except errors.NoSuchFile:
+ return None
+ return shelf.Unshelver.from_tree_and_shelf(tree, transform)
+
def is_locked(self):
return self.control_files.is_locked()
=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py 2012-07-20 13:23:32 +0000
+++ b/bzrlib/builtins.py 2012-07-23 17:56:45 +0000
@@ -6233,10 +6233,13 @@
Option('create-branch', short_name='b',
help='Create the target branch from this one before'
' switching to it.'),
+ Option('store',
+ help='Store and restore uncommitted changes in the'
+ ' branch.'),
]
def run(self, to_location=None, force=False, create_branch=False,
- revision=None, directory=u'.'):
+ revision=None, directory=u'.', store=False):
from bzrlib import switch
tree_location = directory
revision = _get_one_revision('switch', revision)
@@ -6273,7 +6276,8 @@
possible_transports=possible_transports)
if revision is not None:
revision = revision.as_revision_id(to_branch)
- switch.switch(control_dir, to_branch, force, revision_id=revision)
+ switch.switch(control_dir, to_branch, force, revision_id=revision,
+ store_uncommitted=store)
if had_explicit_nick:
branch = control_dir.open_branch() #get the new branch!
branch.nick = to_branch.nick
=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py 2012-06-18 11:43:07 +0000
+++ b/bzrlib/errors.py 2012-07-19 19:27:22 +0000
@@ -2893,6 +2893,21 @@
BzrError.__init__(self, tree=tree, display_url=display_url, more=more)
+class StoringUncommittedNotSupported(BzrError):
+
+ _fmt = ('Branch "%(display_url)s" does not support storing uncommitted'
+ ' changes.')
+
+ def __init__(self, branch):
+ import bzrlib.urlutils as urlutils
+ user_url = getattr(branch, "user_url", None)
+ if user_url is None:
+ display_url = str(branch)
+ else:
+ display_url = urlutils.unescape_for_display(user_url, 'ascii')
+ BzrError.__init__(self, branch=branch, display_url=display_url)
+
+
class ShelvedChanges(UncommittedChanges):
_fmt = ('Working tree "%(display_url)s" has shelved changes'
@@ -3340,3 +3355,9 @@
def __init__(self, feature):
self.feature = feature
+
+
+class ChangesAlreadyStored(BzrCommandError):
+
+ _fmt = ('Cannot store uncommitted changes because this branch already'
+ ' stores uncommitted changes.')
=== modified file 'bzrlib/lock.py'
--- a/bzrlib/lock.py 2011-12-19 13:23:58 +0000
+++ b/bzrlib/lock.py 2012-07-13 19:32:33 +0000
@@ -35,6 +35,7 @@
from __future__ import absolute_import
+import contextlib
import errno
import os
import sys
@@ -548,3 +549,10 @@
trace.note(gettext('{0!r} was {1} locked again'), self, type_name)
self._prev_lock = lock_type
+ at contextlib.contextmanager
+def write_locked(lockable):
+ lockable.lock_write()
+ try:
+ yield lockable
+ finally:
+ lockable.unlock()
=== modified file 'bzrlib/remote.py'
--- a/bzrlib/remote.py 2012-06-28 16:17:34 +0000
+++ b/bzrlib/remote.py 2012-07-19 18:28:25 +0000
@@ -3393,6 +3393,14 @@
self.conf_store = RemoteBranchStore(self)
return self.conf_store
+ def store_uncommitted(self, creator):
+ self._ensure_real()
+ return self._real_branch.store_uncommitted(creator)
+
+ def get_unshelver(self, tree):
+ self._ensure_real()
+ return self._real_branch.get_unshelver(tree)
+
def _get_real_transport(self):
# if we try vfs access, return the real branch's vfs transport
self._ensure_real()
=== modified file 'bzrlib/shelf.py'
--- a/bzrlib/shelf.py 2011-12-18 15:28:38 +0000
+++ b/bzrlib/shelf.py 2012-07-13 17:23:12 +0000
@@ -125,9 +125,14 @@
raise ValueError('Unknown change kind: "%s"' % change[0])
def shelve_all(self):
- """Shelve all changes."""
+ """Shelve all changes.
+
+ :return: True if changes were shelved, False if there were no changes.
+ """
+ change = None
for change in self.iter_shelvable():
self.shelve_change(change)
+ return change is not None
def shelve_rename(self, file_id):
"""Shelve a file rename.
@@ -255,6 +260,14 @@
"""Shelve changes from working tree."""
self.work_transform.apply()
+ @staticmethod
+ def metadata_record(serializer, revision_id, message=None):
+ metadata = {'revision_id': revision_id}
+ if message is not None:
+ metadata['message'] = message.encode('utf-8')
+ return serializer.bytes_record(
+ bencode.bencode(metadata), (('metadata',),))
+
def write_shelf(self, shelf_file, message=None):
"""Serialize the shelved changes to a file.
@@ -263,16 +276,17 @@
:return: the filename of the written file.
"""
transform.resolve_conflicts(self.shelf_transform)
+ revision_id = self.target_tree.get_revision_id()
+ return self._write_shelf(shelf_file, self.shelf_transform, revision_id,
+ message)
+
+ @classmethod
+ def _write_shelf(cls, shelf_file, transform, revision_id, message=None):
serializer = pack.ContainerSerialiser()
shelf_file.write(serializer.begin())
- metadata = {
- 'revision_id': self.target_tree.get_revision_id(),
- }
- if message is not None:
- metadata['message'] = message.encode('utf-8')
- shelf_file.write(serializer.bytes_record(
- bencode.bencode(metadata), (('metadata',),)))
- for bytes in self.shelf_transform.serialize(serializer):
+ metadata = cls.metadata_record(serializer, revision_id, message)
+ shelf_file.write(metadata)
+ for bytes in transform.serialize(serializer):
shelf_file.write(bytes)
shelf_file.write(serializer.end())
=== modified file 'bzrlib/switch.py'
--- a/bzrlib/switch.py 2012-02-14 17:22:37 +0000
+++ b/bzrlib/switch.py 2012-07-19 16:57:16 +0000
@@ -18,7 +18,12 @@
# Original author: David Allouche
-from bzrlib import errors, merge, revision
+from bzrlib import (
+ errors,
+ lock,
+ merge,
+ revision
+ )
from bzrlib.branch import Branch
from bzrlib.i18n import gettext
from bzrlib.trace import note
@@ -32,26 +37,32 @@
for hook in hooks:
hook(params)
-def switch(control_dir, to_branch, force=False, quiet=False, revision_id=None):
+def switch(control_dir, to_branch, force=False, quiet=False, revision_id=None,
+ store_uncommitted=False):
"""Switch the branch associated with a checkout.
:param control_dir: ControlDir of the checkout to change
:param to_branch: branch that the checkout is to reference
:param force: skip the check for local commits in a heavy checkout
:param revision_id: revision ID to switch to.
+ :param store_uncommitted: If True, store uncommitted changes in the
+ branch.
"""
_check_pending_merges(control_dir, force)
try:
source_repository = control_dir.open_branch().repository
except errors.NotBranchError:
source_repository = to_branch.repository
+ if store_uncommitted:
+ with lock.write_locked(control_dir.open_workingtree()) as tree:
+ tree.store_uncommitted()
to_branch.lock_read()
try:
_set_branch_location(control_dir, to_branch, force)
finally:
to_branch.unlock()
tree = control_dir.open_workingtree()
- _update(tree, source_repository, quiet, revision_id)
+ _update(tree, source_repository, quiet, revision_id, store_uncommitted)
_run_post_switch_hooks(control_dir, to_branch, force, revision_id)
def _check_pending_merges(control, force=False):
@@ -151,13 +162,18 @@
return False
-def _update(tree, source_repository, quiet=False, revision_id=None):
+def _update(tree, source_repository, quiet=False, revision_id=None,
+ restore_uncommitted=False):
"""Update a working tree to the latest revision of its branch.
:param tree: the working tree
:param source_repository: repository holding the revisions
+ :param restore_uncommitted: restore any uncommitted changes in the branch.
"""
- tree.lock_tree_write()
+ if restore_uncommitted:
+ tree.lock_write()
+ else:
+ tree.lock_tree_write()
try:
to_branch = tree.branch
if revision_id is None:
@@ -165,11 +181,14 @@
if tree.last_revision() == revision_id:
if not quiet:
note(gettext("Tree is up to date at revision %d."), to_branch.revno())
- return
- base_tree = source_repository.revision_tree(tree.last_revision())
- merge.Merge3Merger(tree, tree, base_tree, to_branch.repository.revision_tree(revision_id))
- tree.set_last_revision(to_branch.last_revision())
- if not quiet:
- note(gettext('Updated to revision %d.') % to_branch.revno())
+ else:
+ base_tree = source_repository.revision_tree(tree.last_revision())
+ merge.Merge3Merger(tree, tree, base_tree,
+ to_branch.repository.revision_tree(revision_id))
+ tree.set_last_revision(to_branch.last_revision())
+ if not quiet:
+ note(gettext('Updated to revision %d.') % to_branch.revno())
+ if restore_uncommitted:
+ tree.restore_uncommitted()
finally:
tree.unlock()
=== modified file 'bzrlib/tests/blackbox/test_switch.py'
--- a/bzrlib/tests/blackbox/test_switch.py 2012-06-18 11:43:07 +0000
+++ b/bzrlib/tests/blackbox/test_switch.py 2012-07-23 16:57:58 +0000
@@ -304,7 +304,8 @@
def test_switch_lightweight_after_branch_moved_relative(self):
self.prepare_lightweight_switch()
- self.run_bzr('switch --force branch1', working_dir='tree')
+ self.run_bzr('switch --force branch1',
+ working_dir='tree')
branch_location = WorkingTree.open('tree').branch.base
self.assertEndsWith(branch_location, 'branch1/')
@@ -492,3 +493,39 @@
self.assertLength(24, self.hpss_calls)
self.assertLength(4, self.hpss_connections)
self.assertThat(self.hpss_calls, ContainsNoVfsCalls)
+
+
+class TestSwitchUncommitted(TestCaseWithTransport):
+
+ def prepare(self):
+ tree = self.make_branch_and_tree('orig')
+ tree.commit('')
+ tree.branch.bzrdir.sprout('new')
+ checkout = tree.branch.create_checkout('checkout', lightweight=True)
+ self.build_tree(['checkout/a'])
+ self.assertPathExists('checkout/a')
+ checkout.add('a')
+ return checkout
+
+ def test_store_and_restore_uncommitted(self):
+ checkout = self.prepare()
+ self.run_bzr(['switch', '--store', '-d', 'checkout', 'new'])
+ self.build_tree(['checkout/b'])
+ checkout.add('b')
+ self.assertPathDoesNotExist('checkout/a')
+ self.assertPathExists('checkout/b')
+ self.run_bzr(['switch', '--store', '-d', 'checkout', 'orig'])
+ self.assertPathExists('checkout/a')
+ self.assertPathDoesNotExist('checkout/b')
+
+ def test_does_not_store(self):
+ self.prepare()
+ self.run_bzr(['switch', '-d', 'checkout', 'new'])
+ self.assertPathExists('checkout/a')
+
+ def test_does_not_restore_changes(self):
+ self.prepare()
+ self.run_bzr(['switch', '--store', '-d', 'checkout', 'new'])
+ self.assertPathDoesNotExist('checkout/a')
+ self.run_bzr(['switch', '-d', 'checkout', 'orig'])
+ self.assertPathDoesNotExist('checkout/a')
=== modified file 'bzrlib/tests/per_branch/test_branch.py'
--- a/bzrlib/tests/per_branch/test_branch.py 2012-03-14 11:49:29 +0000
+++ b/bzrlib/tests/per_branch/test_branch.py 2012-07-19 19:27:22 +0000
@@ -16,6 +16,8 @@
"""Tests for branch implementations - tests a branch format."""
+import contextlib
+
from bzrlib import (
branch as _mod_branch,
controldir,
@@ -25,10 +27,12 @@
merge,
osutils,
urlutils,
+ transform,
transport,
remote,
repository,
revision,
+ shelf,
symbol_versioning,
tests,
)
@@ -1057,3 +1061,88 @@
# above the control dir but we might need to relax that?
self.assertEqual(br.control_url.find(br.user_url), 0)
self.assertEqual(br.control_url, br.control_transport.base)
+
+
+class FakeShelfCreator(object):
+
+ def __init__(self, branch):
+ self.branch = branch
+
+ def write_shelf(self, shelf_file, message=None):
+ tree = self.branch.repository.revision_tree(revision.NULL_REVISION)
+ with transform.TransformPreview(tree) as tt:
+ shelf.ShelfCreator._write_shelf(
+ shelf_file, tt, revision.NULL_REVISION)
+
+
+ at contextlib.contextmanager
+def skip_if_storing_uncommitted_unsupported():
+ try:
+ yield
+ except errors.StoringUncommittedNotSupported:
+ raise tests.TestNotApplicable('Cannot store uncommitted changes.')
+
+
+class TestUncommittedChanges(per_branch.TestCaseWithBranch):
+
+ def bind(self, branch, master):
+ try:
+ branch.bind(master)
+ except errors.UpgradeRequired:
+ raise tests.TestNotApplicable('Branch cannot be bound.')
+
+ def test_store_uncommitted(self):
+ tree = self.make_branch_and_tree('b')
+ branch = tree.branch
+ creator = FakeShelfCreator(branch)
+ with skip_if_storing_uncommitted_unsupported():
+ self.assertIs(None, branch.get_unshelver(tree))
+ branch.store_uncommitted(creator)
+ self.assertIsNot(None, branch.get_unshelver(tree))
+
+ def test_store_uncommitted_bound(self):
+ tree = self.make_branch_and_tree('b')
+ branch = tree.branch
+ master = self.make_branch('master')
+ self.bind(branch, master)
+ creator = FakeShelfCreator(tree.branch)
+ self.assertIs(None, tree.branch.get_unshelver(tree))
+ self.assertIs(None, master.get_unshelver(tree))
+ tree.branch.store_uncommitted(creator)
+ self.assertIsNot(None, master.get_unshelver(tree))
+
+ def test_store_uncommitted_already_stored(self):
+ branch = self.make_branch('b')
+ with skip_if_storing_uncommitted_unsupported():
+ branch.store_uncommitted(FakeShelfCreator(branch))
+ self.assertRaises(errors.ChangesAlreadyStored,
+ branch.store_uncommitted, FakeShelfCreator(branch))
+
+ def test_store_uncommitted_none(self):
+ branch = self.make_branch('b')
+ with skip_if_storing_uncommitted_unsupported():
+ branch.store_uncommitted(FakeShelfCreator(branch))
+ branch.store_uncommitted(None)
+ self.assertIs(None, branch.get_unshelver(None))
+
+ def test_get_unshelver(self):
+ tree = self.make_branch_and_tree('tree')
+ tree.commit('')
+ self.build_tree_contents([('tree/file', 'contents1')])
+ tree.add('file')
+ with skip_if_storing_uncommitted_unsupported():
+ tree.store_uncommitted()
+ unshelver = tree.branch.get_unshelver(tree)
+ self.assertIsNot(None, unshelver)
+
+ def test_get_unshelver_bound(self):
+ tree = self.make_branch_and_tree('tree')
+ tree.commit('')
+ self.build_tree_contents([('tree/file', 'contents1')])
+ tree.add('file')
+ with skip_if_storing_uncommitted_unsupported():
+ tree.store_uncommitted()
+ branch = self.make_branch('branch')
+ self.bind(branch, tree.branch)
+ unshelver = branch.get_unshelver(tree)
+ self.assertIsNot(None, unshelver)
=== modified file 'bzrlib/tests/test_switch.py'
--- a/bzrlib/tests/test_switch.py 2011-05-13 12:51:05 +0000
+++ b/bzrlib/tests/test_switch.py 2012-07-18 19:55:04 +0000
@@ -22,9 +22,11 @@
from bzrlib import (
branch,
errors,
+ lock,
merge as _mod_merge,
switch,
tests,
+ workingtree,
)
@@ -34,6 +36,14 @@
super(TestSwitch, self).setUp()
self.lightweight = True
+ @staticmethod
+ def _master_if_present(branch):
+ master = branch.get_master_branch()
+ if master:
+ return master
+ else:
+ return branch
+
def _setup_tree(self):
tree = self.make_branch_and_tree('branch-1')
self.build_tree(['branch-1/file-1'])
@@ -41,18 +51,56 @@
tree.commit('rev1')
return tree
+ def _setup_uncommitted(self, same_revision=False):
+ tree = self._setup_tree()
+ to_branch = tree.bzrdir.sprout('branch-2').open_branch()
+ self.build_tree(['branch-1/file-2'])
+ if not same_revision:
+ tree.add('file-2')
+ tree.remove('file-1')
+ tree.commit('rev2')
+ checkout = tree.branch.create_checkout('checkout',
+ lightweight=self.lightweight)
+ self.build_tree(['checkout/file-3'])
+ checkout.add('file-3')
+ return checkout, to_branch
+
+ def test_switch_store_uncommitted(self):
+ """Test switch updates tree and stores uncommitted changes."""
+ checkout, to_branch = self._setup_uncommitted()
+ self.assertPathDoesNotExist('checkout/file-1')
+ self.assertPathExists('checkout/file-2')
+ switch.switch(checkout.bzrdir, to_branch, store_uncommitted=True)
+ self.assertPathExists('checkout/file-1')
+ self.assertPathDoesNotExist('checkout/file-2')
+ self.assertPathDoesNotExist('checkout/file-3')
+
+ def test_switch_restore_uncommitted(self):
+ """Test switch updates tree and restores uncommitted changes."""
+ checkout, to_branch = self._setup_uncommitted()
+ old_branch = self._master_if_present(checkout.branch)
+ self.assertPathDoesNotExist('checkout/file-1')
+ self.assertPathExists('checkout/file-2')
+ self.assertPathExists('checkout/file-3')
+ switch.switch(checkout.bzrdir, to_branch, store_uncommitted=True)
+ checkout = workingtree.WorkingTree.open('checkout')
+ switch.switch(checkout.bzrdir, old_branch, store_uncommitted=True)
+ self.assertPathDoesNotExist('checkout/file-1')
+ self.assertPathExists('checkout/file-2')
+ self.assertPathExists('checkout/file-3')
+
+ def test_switch_restore_uncommitted_same_revision(self):
+ """Test switch updates tree and restores uncommitted changes."""
+ checkout, to_branch = self._setup_uncommitted(same_revision=True)
+ old_branch = self._master_if_present(checkout.branch)
+ switch.switch(checkout.bzrdir, to_branch, store_uncommitted=True)
+ checkout = workingtree.WorkingTree.open('checkout')
+ switch.switch(checkout.bzrdir, old_branch, store_uncommitted=True)
+ self.assertPathExists('checkout/file-3')
+
def test_switch_updates(self):
"""Test switch updates tree and keeps uncommitted changes."""
- tree = self._setup_tree()
- to_branch = tree.bzrdir.sprout('branch-2').open_branch()
- self.build_tree(['branch-1/file-2'])
- tree.add('file-2')
- tree.remove('file-1')
- tree.commit('rev2')
- checkout = tree.branch.create_checkout('checkout',
- lightweight=self.lightweight)
- self.build_tree(['checkout/file-3'])
- checkout.add('file-3')
+ checkout, to_branch = self._setup_uncommitted()
self.assertPathDoesNotExist('checkout/file-1')
self.assertPathExists('checkout/file-2')
switch.switch(checkout.bzrdir, to_branch)
=== modified file 'bzrlib/tests/test_workingtree.py'
--- a/bzrlib/tests/test_workingtree.py 2012-03-14 08:34:10 +0000
+++ b/bzrlib/tests/test_workingtree.py 2012-07-19 15:44:55 +0000
@@ -19,12 +19,12 @@
bzrdir,
conflicts,
errors,
- symbol_versioning,
transport,
workingtree,
workingtree_3,
workingtree_4,
)
+from bzrlib.lock import write_locked
from bzrlib.lockdir import LockDir
from bzrlib.mutabletree import needs_tree_write_lock
from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
@@ -495,3 +495,34 @@
self.make_branch('qux')
trees = workingtree.WorkingTree.find_trees('.')
self.assertEqual(2, len(list(trees)))
+
+
+class TestStoredUncommitted(TestCaseWithTransport):
+
+ def store_uncommitted(self):
+ tree = self.make_branch_and_tree('tree')
+ tree.commit('get root in there')
+ self.build_tree_contents([('tree/file', 'content')])
+ tree.add('file', 'file-id')
+ tree.store_uncommitted()
+ return tree
+
+ def test_store_uncommitted(self):
+ self.store_uncommitted()
+ self.assertPathDoesNotExist('tree/file')
+
+ def test_store_uncommitted_no_change(self):
+ tree = self.make_branch_and_tree('tree')
+ tree.commit('get root in there')
+ tree.store_uncommitted()
+ self.assertIs(None, tree.branch.get_unshelver(tree))
+
+ def test_restore_uncommitted(self):
+ with write_locked(self.store_uncommitted()) as tree:
+ tree.restore_uncommitted()
+ self.assertPathExists('tree/file')
+ self.assertIs(None, tree.branch.get_unshelver(tree))
+
+ def test_restore_uncommitted_none(self):
+ tree = self.make_branch_and_tree('tree')
+ tree.restore_uncommitted()
=== modified file 'bzrlib/workingtree.py'
--- a/bzrlib/workingtree.py 2012-06-18 11:43:07 +0000
+++ b/bzrlib/workingtree.py 2012-07-18 20:06:21 +0000
@@ -60,6 +60,7 @@
revision as _mod_revision,
revisiontree,
rio as _mod_rio,
+ shelf,
transform,
transport,
ui,
@@ -1351,6 +1352,34 @@
basis_tree.unlock()
return conflicts
+ @needs_write_lock
+ def store_uncommitted(self):
+ """Store uncommitted changes from the tree in the branch."""
+ target_tree = self.basis_tree()
+ shelf_creator = shelf.ShelfCreator(self, target_tree)
+ try:
+ if not shelf_creator.shelve_all():
+ return
+ self.branch.store_uncommitted(shelf_creator)
+ shelf_creator.transform()
+ finally:
+ shelf_creator.finalize()
+ note('Uncommitted changes stored in branch "%s".', self.branch.nick)
+
+ @needs_write_lock
+ def restore_uncommitted(self):
+ """Restore uncommitted changes from the branch into the tree."""
+ unshelver = self.branch.get_unshelver(self)
+ if unshelver is None:
+ return
+ try:
+ merger = unshelver.make_merger()
+ merger.ignore_zero = True
+ merger.do_merge()
+ self.branch.store_uncommitted(None)
+ finally:
+ unshelver.finalize()
+
def revision_tree(self, revision_id):
"""See Tree.revision_tree.
=== modified file 'doc/en/release-notes/bzr-2.6.txt'
--- a/doc/en/release-notes/bzr-2.6.txt 2012-07-10 12:19:23 +0000
+++ b/doc/en/release-notes/bzr-2.6.txt 2012-07-19 18:39:38 +0000
@@ -18,7 +18,8 @@
New Features
************
-.. New commands, options, etc that users may wish to try out.
+* ``bzr switch --store`` now stores uncommitted changes in the branch, and
+ restores them when switching back to the branch. (Aaron Bentley)
Improvements
************
=== modified file 'doc/en/user-guide/index-plain.txt'
--- a/doc/en/user-guide/index-plain.txt 2011-05-16 10:22:06 +0000
+++ b/doc/en/user-guide/index-plain.txt 2012-07-23 16:14:38 +0000
@@ -79,6 +79,7 @@
.. include:: part2_intro.txt
.. include:: adv_merging.txt
.. include:: shelving_changes.txt
+.. include:: switch_store.txt
.. include:: filtered_views.txt
.. include:: stacked.txt
.. include:: server.txt
=== modified file 'doc/en/user-guide/index.txt'
--- a/doc/en/user-guide/index.txt 2011-06-13 14:08:28 +0000
+++ b/doc/en/user-guide/index.txt 2012-07-23 16:14:38 +0000
@@ -97,6 +97,7 @@
part2_intro
adv_merging
+ switch_store
shelving_changes
filtered_views
stacked
=== added file 'doc/en/user-guide/switch_store.txt'
--- a/doc/en/user-guide/switch_store.txt 1970-01-01 00:00:00 +0000
+++ b/doc/en/user-guide/switch_store.txt 2012-07-23 16:47:33 +0000
@@ -0,0 +1,93 @@
+Switch --store
+==============
+
+In workflows that a single working tree, like co-located branches, sometimes
+you want to switch while you have uncommitted changes. By default, ``switch``
+will apply your uncommitted changes to the new branch that you switch to. But
+often you don't want that. You just want to do some work in the other branch,
+and eventually return to this branch and work some more.
+
+You could run ``bzr shelve --all`` before switching, to store the changes
+safely. So you have to know that there are uncommitted changes present, and
+you have to remember to run ``bzr shelve --all``. Then when you switch back to
+the branch, you need to remember to unshelve the changes, and you need to know
+what their shelf-id was.
+
+Using ``switch --store`` takes care of all of this for you. If there are any
+uncommitted changes in your tree, it stores them in your branch. It then
+restores any uncommitted changes that were stored in the branch of your target
+tree. It's almost like having two working trees and using ``cd`` to switch
+between them.
+
+To take an example, first we'd set up a co-located branch::
+
+ $ bzr init foo
+ Created a standalone tree (format: 2a)
+ $ cd foo
+ $ bzr switch -b foo
+
+Now create committed and uncommitted changes::
+
+ $ touch committed
+ $ bzr add
+ adding committed
+ $ bzr commit -m "Add committed"
+ Committing to: /home/abentley/sandbox/foo/
+ added committed
+ Committed revision 1.
+ $ touch uncommitted
+ $ bzr add
+ adding uncommitted
+ $ ls
+ committed uncommitted
+
+Now create a new branch using ``--store``. The uncommitted changes are stored
+in "foo", but the committed changes are retained.
+::
+
+ $ bzr switch -b --store bar
+ Uncommitted changes stored in branch "foo".
+ Tree is up to date at revision 1.
+ Switched to branch: /home/abentley/sandbox/foo/
+ abentley at speedy:~/sandbox/foo$ ls
+ committed
+
+Now, create uncommitted changes in "bar"::
+
+ $ touch uncommitted-bar
+ $ bzr add
+ adding uncommitted-bar
+
+Finally, switch back to "foo"::
+
+ $ bzr switch --store foo
+ Uncommitted changes stored in branch "bar".
+ Tree is up to date at revision 1.
+ Switched to branch: /home/abentley/sandbox/foo/
+ $ ls
+ committed uncommitted
+
+Each branch holds only one set of stored changes. If you try to store a second
+set, you get an error. If you use ``--store`` all the time, this can't happen.
+But if you use plain switch, then it won't restore the uncommitted changes
+already present::
+
+ $ bzr switch bar
+ Tree is up to date at revision 1.
+ Switched to branch: /home/abentley/sandbox/foo/
+ $ bzr switch --store foo
+ bzr: ERROR: Cannot store uncommitted changes because this branch already
+ stores uncommitted changes.
+
+If you're working in a branch that already has stored changes, you can restore
+them with ``bzr switch . --store``::
+
+ $ bzr shelve --all -m "Uncommitted changes from foo"
+ Selected changes:
+ -D uncommitted
+ Changes shelved with id "1".
+ $ bzr switch . --store
+ Tree is up to date at revision 1.
+ Switched to branch: /home/abentley/sandbox/foo/
+ $ ls
+ committed uncommitted-bar
More information about the bazaar-commits
mailing list