Rev 3093: switch for heavyweight checkouts in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Dec 7 06:46:59 GMT 2007


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 3093
revision-id:pqm at pqm.ubuntu.com-20071207064646-1nif1u2vsep2vqud
parent: pqm at pqm.ubuntu.com-20071207051359-iyupti9xt33jccin
parent: ian.clatworthy at internode.on.net-20071207055345-ek2m118pm79nowuf
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2007-12-07 06:46:46 +0000
message:
  switch for heavyweight checkouts
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/graph.py                graph_walker.py-20070525030359-y852guab65d4wtn0-1
  bzrlib/switch.py               switch.py-20071116011000-v5lnw7d2wkng9eux-1
  bzrlib/tests/branch_implementations/test_branch.py testbranch.py-20050711070244-121d632bc37d7253
  bzrlib/tests/test_switch.py    test_switch.py-20071116011000-v5lnw7d2wkng9eux-2
    ------------------------------------------------------------
    revno: 3092.1.1
    revision-id:ian.clatworthy at internode.on.net-20071207055345-ek2m118pm79nowuf
    parent: pqm at pqm.ubuntu.com-20071207051359-iyupti9xt33jccin
    parent: ian.clatworthy at internode.on.net-20071207054814-u8dzhvxnksy1dwj2
    committer: Ian Clatworthy <ian.clatworthy at internode.on.net>
    branch nick: ianc-integration
    timestamp: Fri 2007-12-07 15:53:45 +1000
    message:
      switch for heavyweight checkouts
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
      bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
      bzrlib/graph.py                graph_walker.py-20070525030359-y852guab65d4wtn0-1
      bzrlib/switch.py               switch.py-20071116011000-v5lnw7d2wkng9eux-1
      bzrlib/tests/branch_implementations/test_branch.py testbranch.py-20050711070244-121d632bc37d7253
      bzrlib/tests/test_switch.py    test_switch.py-20071116011000-v5lnw7d2wkng9eux-2
    ------------------------------------------------------------
    revno: 3078.2.8
    revision-id:ian.clatworthy at internode.on.net-20071207054814-u8dzhvxnksy1dwj2
    parent: ian.clatworthy at internode.on.net-20071207054651-p34puo2num38ig0j
    committer: Ian Clatworthy <ian.clatworthy at internode.on.net>
    branch nick: bzr.switch
    timestamp: Fri 2007-12-07 15:48:14 +1000
    message:
      tweak NEWS
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
    ------------------------------------------------------------
    revno: 3078.2.7
    revision-id:ian.clatworthy at internode.on.net-20071207054651-p34puo2num38ig0j
    parent: ian.clatworthy at internode.on.net-20071207053154-k9tmyczcf8niwonm
    committer: Ian Clatworthy <ian.clatworthy at internode.on.net>
    branch nick: bzr.switch
    timestamp: Fri 2007-12-07 15:46:51 +1000
    message:
      added smoke test for set_reference
    modified:
      bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
      bzrlib/tests/branch_implementations/test_branch.py testbranch.py-20050711070244-121d632bc37d7253
    ------------------------------------------------------------
    revno: 3078.2.6
    revision-id:ian.clatworthy at internode.on.net-20071207053154-k9tmyczcf8niwonm
    parent: ian.clatworthy at internode.on.net-20071207045158-y91slghnuoa7klfd
    committer: Ian Clatworthy <ian.clatworthy at internode.on.net>
    branch nick: bzr.switch
    timestamp: Fri 2007-12-07 15:31:54 +1000
    message:
      fix efficiency of local commit detection as recommended by jameinel's review
    modified:
      bzrlib/graph.py                graph_walker.py-20070525030359-y852guab65d4wtn0-1
      bzrlib/switch.py               switch.py-20071116011000-v5lnw7d2wkng9eux-1
      bzrlib/tests/test_switch.py    test_switch.py-20071116011000-v5lnw7d2wkng9eux-2
    ------------------------------------------------------------
    revno: 3078.2.5
    revision-id:ian.clatworthy at internode.on.net-20071207045158-y91slghnuoa7klfd
    parent: ian.clatworthy at internode.on.net-20071205125451-oe6p3gekbfhyxuzt
    committer: Ian Clatworthy <ian.clatworthy at internode.on.net>
    branch nick: bzr.switch
    timestamp: Fri 2007-12-07 14:51:58 +1000
    message:
      make switch fail without --force if branch missing
    modified:
      bzrlib/switch.py               switch.py-20071116011000-v5lnw7d2wkng9eux-1
      bzrlib/tests/test_switch.py    test_switch.py-20071116011000-v5lnw7d2wkng9eux-2
    ------------------------------------------------------------
    revno: 3078.2.4
    revision-id:ian.clatworthy at internode.on.net-20071205125451-oe6p3gekbfhyxuzt
    parent: ian.clatworthy at internode.on.net-20071205125414-pmgapgkgx7vpi33y
    committer: Ian Clatworthy <ian.clatworthy at internode.on.net>
    branch nick: bzr.switch
    timestamp: Wed 2007-12-05 22:54:51 +1000
    message:
      Add test for local commits handling
    modified:
      bzrlib/tests/test_switch.py    test_switch.py-20071116011000-v5lnw7d2wkng9eux-2
    ------------------------------------------------------------
    revno: 3078.2.3
    revision-id:ian.clatworthy at internode.on.net-20071205125414-pmgapgkgx7vpi33y
    parent: ian.clatworthy at internode.on.net-20071205074743-gsf610r9r0dhjmzs
    committer: Ian Clatworthy <ian.clatworthy at internode.on.net>
    branch nick: bzr.switch
    timestamp: Wed 2007-12-05 22:54:14 +1000
    message:
      Add NEWS
    modified:
      NEWS                           NEWS-20050323055033-4e00b5db738777ff
    ------------------------------------------------------------
    revno: 3078.2.2
    revision-id:ian.clatworthy at internode.on.net-20071205074743-gsf610r9r0dhjmzs
    parent: ian.clatworthy at internode.on.net-20071205064946-snjfrx883fc49osl
    committer: Ian Clatworthy <ian.clatworthy at internode.on.net>
    branch nick: bzr.switch
    timestamp: Wed 2007-12-05 17:47:43 +1000
    message:
      get switch tests passing on heavyweight checkouts
    modified:
      bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
      bzrlib/switch.py               switch.py-20071116011000-v5lnw7d2wkng9eux-1
      bzrlib/tests/test_switch.py    test_switch.py-20071116011000-v5lnw7d2wkng9eux-2
    ------------------------------------------------------------
    revno: 3078.2.1
    revision-id:ian.clatworthy at internode.on.net-20071205064946-snjfrx883fc49osl
    parent: pqm at pqm.ubuntu.com-20071205035041-vjo05rrhyrqqmgxf
    committer: Ian Clatworthy <ian.clatworthy at internode.on.net>
    branch nick: bzr.switch
    timestamp: Wed 2007-12-05 16:49:46 +1000
    message:
      Refactor switch to support heavyweight checkouts
    modified:
      bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
      bzrlib/switch.py               switch.py-20071116011000-v5lnw7d2wkng9eux-1
=== modified file 'NEWS'
--- a/NEWS	2007-12-07 01:45:23 +0000
+++ b/NEWS	2007-12-07 05:53:45 +0000
@@ -29,6 +29,16 @@
      the catalog, provided in pdf and png format and updated to refer
      to ``send`` instead of ``bundle``. (Ian Clatworthy, #165080)
 
+   * ``switch`` can now be used on heavyweight checkouts as well as
+     lightweight ones. After switching a heavyweight checkout, the
+     local branch is a mirror/cache of the new bound branch and
+     uncommitted changes in the working tree are merged. As a safety
+     check, if there are local commits in a checkout which have not
+     been committed to the previously bound branch, then ``switch``
+     fails unless the ``--force`` option is given. This option is
+     now also required if the branch a lightweight checkout is pointing
+     to has been moved. (Ian Clatworthy)
+
   INTERNALS:
 
     * New -Dhttp debug option reports http connections, requests and responses.

=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2007-12-03 22:21:35 +0000
+++ b/bzrlib/branch.py	2007-12-07 05:46:51 +0000
@@ -868,6 +868,19 @@
         """
         return None
 
+    @classmethod
+    def set_reference(self, a_bzrdir, to_branch):
+        """Set the target reference of the branch in a_bzrdir.
+
+        format probing must have been completed before calling
+        this method - it is assumed that the format of the branch
+        in a_bzrdir is correct.
+
+        :param a_bzrdir: The bzrdir to set the branch reference for.
+        :param to_branch: branch that the checkout is to reference
+        """
+        raise NotImplementedError(self.set_reference)
+
     def get_format_string(self):
         """Return the ASCII format string that identifies this format."""
         raise NotImplementedError(self.get_format_string)
@@ -1198,6 +1211,11 @@
         transport = a_bzrdir.get_branch_transport(None)
         return transport.get('location').read()
 
+    def set_reference(self, a_bzrdir, to_branch):
+        """See BranchFormat.set_reference()."""
+        transport = a_bzrdir.get_branch_transport(None)
+        location = transport.put_bytes('location', to_branch.base)
+
     def initialize(self, a_bzrdir, target_branch=None):
         """Create a branch of this format in a_bzrdir."""
         if target_branch is None:
@@ -1860,6 +1878,19 @@
         return None
 
     @classmethod
+    def set_reference(self, a_bzrdir, to_branch):
+        """Set the target reference of the branch in a_bzrdir.
+
+        format probing must have been completed before calling
+        this method - it is assumed that the format of the branch
+        in a_bzrdir is correct.
+
+        :param a_bzrdir: The bzrdir to set the branch reference for.
+        :param to_branch: branch that the checkout is to reference
+        """
+        raise NotImplementedError(self.set_reference)
+
+    @classmethod
     def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
             lock_class):
         branch_transport = a_bzrdir.get_branch_transport(cls)

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2007-12-06 12:03:35 +0000
+++ b/bzrlib/builtins.py	2007-12-07 05:53:45 +0000
@@ -4353,16 +4353,30 @@
 
 
 class cmd_switch(Command):
-    """Set the branch of a lightweight checkout and update."""
+    """Set the branch of a checkout and update.
+    
+    For lightweight checkouts, this changes the branch being referenced.
+    For heavyweight checkouts, this checks that there are no local commits
+    versus the current bound branch, then it makes the local branch a mirror
+    of the new location and binds to it.
+    
+    In both cases, the working tree is updated and uncommitted changes
+    are merged. The user can commit or revert these as they desire.
+
+    Pending merges need to be committed or reverted before using switch.
+    """
 
     takes_args = ['to_location']
+    takes_options = [Option('force',
+                        help='Switch even if local commits will be lost.')
+                     ]
 
-    def run(self, to_location):
+    def run(self, to_location, force=False):
         from bzrlib import switch
         to_branch = Branch.open(to_location)
         tree_location = '.'
         control_dir = bzrdir.BzrDir.open_containing(tree_location)[0]
-        switch.switch(control_dir, to_branch)
+        switch.switch(control_dir, to_branch, force)
         note('Switched to branch: %s',
             urlutils.unescape_for_display(to_branch.base, 'utf-8'))
 

=== modified file 'bzrlib/graph.py'
--- a/bzrlib/graph.py	2007-12-04 00:34:34 +0000
+++ b/bzrlib/graph.py	2007-12-07 05:31:54 +0000
@@ -361,7 +361,7 @@
         """Determine whether a revision is an ancestor of another.
 
         We answer this using heads() as heads() has the logic to perform the
-        smallest number of parent looksup to determine the ancestral
+        smallest number of parent lookups to determine the ancestral
         relationship between N revisions.
         """
         return set([candidate_descendant]) == self.heads(

=== modified file 'bzrlib/switch.py'
--- a/bzrlib/switch.py	2007-11-23 03:33:17 +0000
+++ b/bzrlib/switch.py	2007-12-07 05:31:54 +0000
@@ -16,59 +16,42 @@
 
 # Original author: David Allouche
 
-from bzrlib import errors, merge
+from bzrlib import errors, merge, revision
 from bzrlib.branch import Branch, BranchFormat, BranchReferenceFormat
 from bzrlib.bzrdir import BzrDir
 from bzrlib.trace import note
 
 
-def switch(control_dir, to_branch):
+def switch(control_dir, to_branch, force=False):
     """Switch the branch associated with a checkout.
 
     :param control_dir: BzrDir 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
     """
-    _check_switch_branch_format(control_dir)
-    _check_pending_merges(control_dir)
+    _check_pending_merges(control_dir, force)
     try:
         source_repository = control_dir.open_branch().repository
     except errors.NotBranchError:
         source_repository = to_branch.repository
-    _set_branch_location(control_dir, to_branch)
+    _set_branch_location(control_dir, to_branch, force)
     tree = control_dir.open_workingtree()
     _update(tree, source_repository)
 
 
-def _check_switch_branch_format(control):
-    """Check that the branch format supports the switch operation.
-
-    Note: Only lightweight checkouts are currently supported.
-    This may change in the future though.
-
-    :param control: BzrDir of the branch to check
-    """
-    branch_format = BranchFormat.find_format(control)
-    format_string = branch_format.get_format_string()
-    if not format_string.startswith("Bazaar-NG Branch Reference Format "):
-        raise errors.BzrCommandError(
-            'The switch command can only be used on a lightweight checkout.\n'
-            'Expected branch reference, found %s at %s' % (
-            format_string.strip(), control.root_transport.base))
-    if not format_string == BranchReferenceFormat().get_format_string():
-        raise errors.BzrCommandError(
-            'Unsupported: %r' % (format_string.strip(),))        
-
-
-def _check_pending_merges(control):
+def _check_pending_merges(control, force=False):
     """Check that there are no outstanding pending merges before switching.
 
     :param control: BzrDir of the branch to check
     """
     try:
         tree = control.open_workingtree()
-    except errors.NotBranchError:
-        # old branch is gone
-        return
+    except errors.NotBranchError, ex:
+        # Lightweight checkout and branch is no longer there
+        if force:
+            return
+        else:
+            raise ex
     # XXX: Should the tree be locked for get_parent_ids?
     existing_pending_merges = tree.get_parent_ids()[1:]
     if len(existing_pending_merges) > 0:
@@ -76,14 +59,57 @@
             'committed or reverted before using switch.')
 
 
-def _set_branch_location(control, to_branch):
+def _set_branch_location(control, to_branch, force=False):
     """Set location value of a branch reference.
 
     :param control: BzrDir 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
     """
-    transport = control.get_branch_transport(None)
-    location = transport.put_bytes('location', to_branch.base)
+    branch_format = control.find_branch_format()
+    if branch_format.get_reference(control) is not None:
+        # Lightweight checkout: update the branch reference
+        branch_format.set_reference(control, to_branch)
+    else:
+        b = control.open_branch()
+        bound_branch = b.get_bound_location()
+        if bound_branch is not None:
+            # Heavyweight checkout: check all local commits
+            # have been pushed to the current bound branch then
+            # synchronise the local branch with the new remote branch
+            # and bind to it
+            possible_transports = []
+            if not force and _any_local_commits(b, possible_transports):
+                raise errors.BzrCommandError(
+                    'Cannot switch as local commits found in the checkout. '
+                    'Commit these to the bound branch or use --force to '
+                    'throw them away.')
+            b.set_bound_location(None)
+            b.pull(to_branch, overwrite=True,
+                possible_transports=possible_transports)
+            b.set_bound_location(to_branch.base)
+        else:
+            raise errors.BzrCommandError('Cannot switch a branch, '
+                'only a checkout.')
+
+
+def _any_local_commits(this_branch, possible_transports):
+    """Does this branch have any commits not in the master branch?"""
+    last_rev = revision.ensure_null(this_branch.last_revision())
+    if last_rev != revision.NULL_REVISION:
+        other_branch = this_branch.get_master_branch(possible_transports)
+        this_branch.lock_read()
+        other_branch.lock_read()
+        try:
+            other_last_rev = other_branch.last_revision()
+            graph = this_branch.repository.get_graph(
+                other_branch.repository)
+            if not graph.is_ancestor(last_rev, other_last_rev):
+                return True
+        finally:
+            other_branch.unlock()
+            this_branch.unlock()
+    return False
 
 
 def _update(tree, source_repository):

=== modified file 'bzrlib/tests/branch_implementations/test_branch.py'
--- a/bzrlib/tests/branch_implementations/test_branch.py	2007-11-26 01:31:22 +0000
+++ b/bzrlib/tests/branch_implementations/test_branch.py	2007-12-07 05:46:51 +0000
@@ -577,6 +577,24 @@
         self.assertEqual(None,
             made_branch._format.get_reference(made_branch.bzrdir))
 
+    def test_set_reference(self):
+        """set_reference on all regular branches should be callable."""
+        if not self.branch_format.is_supported():
+            # unsupported formats are not loopback testable
+            # because the default open will not open them and
+            # they may not be initializable.
+            return
+        this_branch = self.make_branch('this')
+        other_branch = self.make_branch('other')
+        try:
+            this_branch._format.set_reference(this_branch.bzrdir, other_branch)
+        except NotImplementedError:
+            # that's ok
+            pass
+        else:
+            ref = this_branch._format.get_reference(this_branch.bzrdir)
+            self.assertEqual(ref, other_branch.base)
+
     def test_format_initialize_find_open(self):
         # loopback test to check the current format initializes to itself.
         if not self.branch_format.is_supported():

=== modified file 'bzrlib/tests/test_switch.py'
--- a/bzrlib/tests/test_switch.py	2007-11-30 00:34:19 +0000
+++ b/bzrlib/tests/test_switch.py	2007-12-07 05:31:54 +0000
@@ -24,6 +24,10 @@
 
 class TestSwitch(tests.TestCaseWithTransport):
 
+    def setUp(self):
+        super(TestSwitch, self).setUp()
+        self.lightweight = True
+
     def _setup_tree(self):
         tree = self.make_branch_and_tree('branch-1')
         self.build_tree(['branch-1/file-1'])
@@ -39,7 +43,8 @@
         tree.add('file-2')
         tree.remove('file-1')
         tree.commit('rev2')
-        checkout = tree.branch.create_checkout('checkout', lightweight=True)
+        checkout = tree.branch.create_checkout('checkout',
+            lightweight=self.lightweight)
         self.build_tree(['checkout/file-3'])
         checkout.add('file-3')
         self.failIfExists('checkout/file-1')
@@ -52,7 +57,8 @@
     def test_switch_after_branch_moved(self):
         """Test switch after the branch is moved."""
         tree = self._setup_tree()
-        checkout = tree.branch.create_checkout('checkout', lightweight=True)
+        checkout = tree.branch.create_checkout('checkout',
+            lightweight=self.lightweight)
         self.build_tree(['branch-1/file-2'])
         tree.add('file-2')
         tree.remove('file-1')
@@ -63,27 +69,22 @@
         # rename the branch on disk, the checkout object is now invalid.
         os.rename('branch-1', 'branch-2')
         to_branch = branch.Branch.open('branch-2')
-        switch.switch(checkout.bzrdir, to_branch)
+        # Check fails without --force
+        err = self.assertRaises((errors.NotBranchError,
+            errors.BoundBranchConnectionFailure),
+            switch.switch, checkout.bzrdir, to_branch)
+        switch.switch(checkout.bzrdir, to_branch, force=True)
         self.failIfExists('checkout/file-1')
         self.failUnlessExists('checkout/file-2')
         self.failUnlessExists('checkout/file-3')
 
-    def test_switch_on_heavy_checkout(self):
-        """Test graceful failure on heavyweight checkouts."""
-        tree = self._setup_tree()
-        checkout = tree.branch.create_checkout('checkout-1', lightweight=False)
-        branch2 = self.make_branch('branch-2')
-        err = self.assertRaises(errors.BzrCommandError,
-            switch.switch, checkout.bzrdir, branch2)
-        self.assertContainsRe(str(err),
-            "The switch command can only be used on a lightweight checkout")
-
     def test_switch_when_pending_merges(self):
         """Test graceful failure if pending merges are outstanding."""
         # Create 2 branches and a checkout
         tree = self._setup_tree()
         tree2 = tree.bzrdir.sprout('branch-2').open_workingtree()
-        checkout = tree.branch.create_checkout('checkout', lightweight=True)
+        checkout = tree.branch.create_checkout('checkout',
+            lightweight=self.lightweight)
         # Change tree2 and merge it into the checkout without committing
         self.build_tree(['branch-2/file-2'])
         tree2.add('file-2')
@@ -94,3 +95,42 @@
             switch.switch, checkout.bzrdir, tree2.branch)
         self.assertContainsRe(str(err),
             "Pending merges must be committed or reverted before using switch")
+
+
+class TestSwitchHeavyweight(TestSwitch):
+
+    def setUp(self):
+        super(TestSwitchHeavyweight, self).setUp()
+        self.lightweight = False
+
+    def test_switch_with_local_commits(self):
+        """Test switch complains about local commits unless --force given."""
+        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')
+        self.build_tree(['checkout/file-3'])
+        checkout.add('file-3')
+        checkout.commit(message='local only commit', local=True)
+        self.build_tree(['checkout/file-4'])
+        # Check the error reporting is as expected
+        err = self.assertRaises(errors.BzrCommandError,
+            switch.switch, checkout.bzrdir, to_branch)
+        self.assertContainsRe(str(err),
+            'Cannot switch as local commits found in the checkout.')
+        # Check all is ok when force is given
+        self.failIfExists('checkout/file-1')
+        self.failUnlessExists('checkout/file-2')
+        switch.switch(checkout.bzrdir, to_branch, force=True)
+        self.failUnlessExists('checkout/file-1')
+        self.failIfExists('checkout/file-2')
+        self.failIfExists('checkout/file-3')
+        self.failUnlessExists('checkout/file-4')
+        # Check that the checkout is a true mirror of the bound branch
+        missing_in_checkout = checkout.branch.missing_revisions(to_branch)
+        self.assertEqual([], missing_in_checkout)
+        missing_in_remote = to_branch.missing_revisions(checkout.branch)
+        self.assertEqual([], missing_in_remote)




More information about the bazaar-commits mailing list