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