Rev 4566: (mbp) add reconfigure --stacked-on and --unstacked in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Jul 24 05:22:21 BST 2009


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

------------------------------------------------------------
revno: 4566 [merge]
revision-id: pqm at pqm.ubuntu.com-20090724042219-cpfjr65il0k0tdet
parent: pqm at pqm.ubuntu.com-20090724000654-q6gwe0bpgk1buemd
parent: mbp at sourcefrog.net-20090724031556-5zyef6f1ixtn6r3z
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2009-07-24 05:22:19 +0100
message:
  (mbp) add reconfigure --stacked-on and --unstacked
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/fetch.py                fetch.py-20050818234941-26fea6105696365d
  bzrlib/help_topics/en/debug-flags.txt debugflags.txt-20090312050229-rdspqbqq4fzbjtpe-1
  bzrlib/lock.py                 lock.py-20050527050856-ec090bb51bc03349
  bzrlib/lockable_files.py       control_files.py-20051111201905-bb88546e799d669f
  bzrlib/lockdir.py              lockdir.py-20060220222025-98258adf27fbdda3
  bzrlib/reconfigure.py          reconfigure.py-20070908040425-6ykgo7escxhyrg9p-1
  bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
  bzrlib/repository.py           rev_storage.py-20051111201905-119e9401e46257e3
  bzrlib/tests/blackbox/test_reconfigure.py test_reconfigure.py-20070908173426-khfo5fi2rgzgtwj3-1
  bzrlib/tests/per_branch/test_stacking.py test_stacking.py-20080214020755-msjlkb7urobwly0f-1
  doc/en/user-guide/stacked.txt  stacked.txt-20080711023247-4uh9oovoka0sze8b-1
=== modified file 'NEWS'
--- a/NEWS	2009-07-23 17:00:27 +0000
+++ b/NEWS	2009-07-24 03:15:56 +0000
@@ -16,8 +16,12 @@
 New Features
 ************
 
-* ``merge --interactive`` applies a user-selected portion of the merge.  The UI
-  is similar to ``shelve``.  (Aaron Bentley)
+* ``bzr merge --interactive`` applies a user-selected portion of the
+  merge.  The UI is similar to ``shelve``.  (Aaron Bentley)
+
+* ``bzr reconfigure`` now takes options ``--stacked-on URL`` and
+  ``--unstacked`` to change stacking of a branch.
+  (Martin Pool, #391411)
 
 Bug Fixes
 *********
@@ -172,7 +176,7 @@
 
 * ``bzr push`` now aborts if uncommitted changes (including pending merges)
   are present in the working tree (if one is present) and no revision is
-  speified. The configuration option ``push_strict`` can be used to set the
+  specified. The configuration option ``push_strict`` can be used to set the
   default for this behavior.  (Vincent Ladeuil, #284038, #322808, #65286)
 
 * ``bzr revno`` and ``bzr revision-info`` now have a ``--tree`` option to

=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2009-07-01 10:30:41 +0000
+++ b/bzrlib/branch.py	2009-07-10 08:42:35 +0000
@@ -35,6 +35,7 @@
         symbol_versioning,
         transport,
         tsort,
+        ui,
         urlutils,
         )
 from bzrlib.config import BranchConfig, TransportConfig
@@ -662,6 +663,9 @@
         """
         if not self._format.supports_stacking():
             raise errors.UnstackableBranchFormat(self._format, self.base)
+        # XXX: Changing from one fallback repository to another does not check
+        # that all the data you need is present in the new fallback.
+        # Possibly it should.
         self._check_stackable_repo()
         if not url:
             try:
@@ -669,27 +673,67 @@
             except (errors.NotStacked, errors.UnstackableBranchFormat,
                 errors.UnstackableRepositoryFormat):
                 return
-            url = ''
-            # XXX: Lock correctness - should unlock our old repo if we were
-            # locked.
-            # repositories don't offer an interface to remove fallback
-            # repositories today; take the conceptually simpler option and just
-            # reopen it.
-            self.repository = self.bzrdir.find_repository()
-            self.repository.lock_write()
-            # for every revision reference the branch has, ensure it is pulled
-            # in.
-            source_repository = self._get_fallback_repository(old_url)
-            for revision_id in chain([self.last_revision()],
-                self.tags.get_reverse_tag_dict()):
-                self.repository.fetch(source_repository, revision_id,
-                    find_ghosts=True)
+            self._unstack()
         else:
             self._activate_fallback_location(url)
         # write this out after the repository is stacked to avoid setting a
         # stacked config that doesn't work.
         self._set_config_location('stacked_on_location', url)
 
+    def _unstack(self):
+        """Change a branch to be unstacked, copying data as needed.
+        
+        Don't call this directly, use set_stacked_on_url(None).
+        """
+        pb = ui.ui_factory.nested_progress_bar()
+        try:
+            pb.update("Unstacking")
+            # The basic approach here is to fetch the tip of the branch,
+            # including all available ghosts, from the existing stacked
+            # repository into a new repository object without the fallbacks. 
+            #
+            # XXX: See <https://launchpad.net/bugs/397286> - this may not be
+            # correct for CHKMap repostiories
+            old_repository = self.repository
+            if len(old_repository._fallback_repositories) != 1:
+                raise AssertionError("can't cope with fallback repositories "
+                    "of %r" % (self.repository,))
+            # unlock it, including unlocking the fallback
+            old_repository.unlock()
+            old_repository.lock_read()
+            try:
+                # Repositories don't offer an interface to remove fallback
+                # repositories today; take the conceptually simpler option and just
+                # reopen it.  We reopen it starting from the URL so that we
+                # get a separate connection for RemoteRepositories and can
+                # stream from one of them to the other.  This does mean doing
+                # separate SSH connection setup, but unstacking is not a
+                # common operation so it's tolerable.
+                new_bzrdir = bzrdir.BzrDir.open(self.bzrdir.root_transport.base)
+                new_repository = new_bzrdir.find_repository()
+                self.repository = new_repository
+                if self.repository._fallback_repositories:
+                    raise AssertionError("didn't expect %r to have "
+                        "fallback_repositories"
+                        % (self.repository,))
+                # this is not paired with an unlock because it's just restoring
+                # the previous state; the lock's released when set_stacked_on_url
+                # returns
+                self.repository.lock_write()
+                # XXX: If you unstack a branch while it has a working tree
+                # with a pending merge, the pending-merged revisions will no
+                # longer be present.  You can (probably) revert and remerge.
+                #
+                # XXX: This only fetches up to the tip of the repository; it
+                # doesn't bring across any tags.  That's fairly consistent
+                # with how branch works, but perhaps not ideal.
+                self.repository.fetch(old_repository,
+                    revision_id=self.last_revision(),
+                    find_ghosts=True)
+            finally:
+                old_repository.unlock()
+        finally:
+            pb.finished()
 
     def _set_tags_bytes(self, bytes):
         """Mirror method for _get_tags_bytes.

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2009-07-19 04:47:49 +0000
+++ b/bzrlib/builtins.py	2009-07-24 03:15:56 +0000
@@ -5259,14 +5259,37 @@
             ),
         Option('bind-to', help='Branch to bind checkout to.', type=str),
         Option('force',
-               help='Perform reconfiguration even if local changes'
-               ' will be lost.')
+            help='Perform reconfiguration even if local changes'
+            ' will be lost.'),
+        Option('stacked-on',
+            help='Reconfigure a branch to be stacked on another branch.',
+            type=unicode,
+            ),
+        Option('unstacked',
+            help='Reconfigure a branch to be unstacked.  This '
+                'may require copying substantial data into it.',
+            ),
         ]
 
-    def run(self, location=None, target_type=None, bind_to=None, force=False):
+    def run(self, location=None, target_type=None, bind_to=None, force=False,
+            stacked_on=None,
+            unstacked=None):
         directory = bzrdir.BzrDir.open(location)
+        if stacked_on and unstacked:
+            raise BzrCommandError("Can't use both --stacked-on and --unstacked")
+        elif stacked_on is not None:
+            reconfigure.ReconfigureStackedOn().apply(directory, stacked_on)
+        elif unstacked:
+            reconfigure.ReconfigureUnstacked().apply(directory)
+        # At the moment you can use --stacked-on and a different
+        # reconfiguration shape at the same time; there seems no good reason
+        # to ban it.
         if target_type is None:
-            raise errors.BzrCommandError('No target configuration specified')
+            if stacked_on or unstacked:
+                return
+            else:
+                raise errors.BzrCommandError('No target configuration '
+                    'specified')
         elif target_type == 'branch':
             reconfiguration = reconfigure.Reconfigure.to_branch(directory)
         elif target_type == 'tree':

=== modified file 'bzrlib/fetch.py'
--- a/bzrlib/fetch.py	2009-06-17 17:57:15 +0000
+++ b/bzrlib/fetch.py	2009-07-09 08:59:51 +0000
@@ -59,11 +59,8 @@
                 symbol_versioning.deprecated_in((1, 14, 0))
                 % "pb parameter to RepoFetcher.__init__")
             # and for simplicity it is in fact ignored
-        if to_repository.has_same_location(from_repository):
-            # repository.fetch should be taking care of this case.
-            raise errors.BzrError('RepoFetcher run '
-                    'between two objects at the same location: '
-                    '%r and %r' % (to_repository, from_repository))
+        # repository.fetch has the responsibility for short-circuiting
+        # attempts to copy between a repository and itself.
         self.to_repository = to_repository
         self.from_repository = from_repository
         self.sink = to_repository._get_sink()

=== modified file 'bzrlib/help_topics/en/debug-flags.txt'
--- a/bzrlib/help_topics/en/debug-flags.txt	2009-07-16 00:01:17 +0000
+++ b/bzrlib/help_topics/en/debug-flags.txt	2009-07-24 03:15:56 +0000
@@ -23,5 +23,6 @@
 -Dknit            Trace knit operations.
 -Dlock            Trace when lockdir locks are taken or released.
 -Dmerge           Emit information for debugging merges.
+-Dunlock          Some errors during unlock are treated as warnings.
 -Dpack            Emit information about pack operations.
 -Dsftp            Trace SFTP internals.

=== modified file 'bzrlib/lock.py'
--- a/bzrlib/lock.py	2009-07-07 09:00:59 +0000
+++ b/bzrlib/lock.py	2009-07-20 08:47:58 +0000
@@ -37,8 +37,10 @@
 import errno
 import os
 import sys
+import warnings
 
 from bzrlib import (
+    debug,
     errors,
     osutils,
     trace,
@@ -86,6 +88,23 @@
                              self.lock_url, self.details)
 
 
+def cant_unlock_not_held(locked_object):
+    """An attempt to unlock failed because the object was not locked.
+
+    This provides a policy point from which we can generate either a warning 
+    or an exception.
+    """
+    # This is typically masking some other error and called from a finally
+    # block, so it's useful to have the option not to generate a new error
+    # here.  You can use -Werror to make it fatal.  It should possibly also
+    # raise LockNotHeld.
+    if 'unlock' in debug.debug_flags:
+        warnings.warn("%r is already unlocked" % (locked_object,),
+            stacklevel=3)
+    else:
+        raise errors.LockNotHeld(locked_object)
+
+
 try:
     import fcntl
     have_fcntl = True

=== modified file 'bzrlib/lockable_files.py'
--- a/bzrlib/lockable_files.py	2009-03-25 04:20:12 +0000
+++ b/bzrlib/lockable_files.py	2009-07-10 07:47:21 +0000
@@ -24,6 +24,7 @@
 from bzrlib import (
     counted_lock,
     errors,
+    lock,
     osutils,
     transactions,
     urlutils,
@@ -308,7 +309,7 @@
 
     def unlock(self):
         if not self._lock_mode:
-            raise errors.LockNotHeld(self)
+            return lock.cant_unlock_not_held(self)
         if self._lock_warner.lock_count > 1:
             self._lock_warner.lock_count -= 1
         else:

=== modified file 'bzrlib/lockdir.py'
--- a/bzrlib/lockdir.py	2009-05-08 15:39:11 +0000
+++ b/bzrlib/lockdir.py	2009-07-10 07:47:21 +0000
@@ -293,7 +293,7 @@
             self._fake_read_lock = False
             return
         if not self._lock_held:
-            raise LockNotHeld(self)
+            return lock.cant_unlock_not_held(self)
         if self._locked_via_token:
             self._locked_via_token = False
             self._lock_held = False

=== modified file 'bzrlib/reconfigure.py'
--- a/bzrlib/reconfigure.py	2009-07-10 08:33:11 +0000
+++ b/bzrlib/reconfigure.py	2009-07-24 03:15:56 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2007 Canonical Ltd
+# Copyright (C) 2007, 2009 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -14,14 +14,62 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
-"""Reconfigure a bzrdir into a new tree/branch/repository layout"""
+"""Reconfigure a bzrdir into a new tree/branch/repository layout.
+
+Various types of reconfiguration operation are available either by
+constructing a class or using a factory method on Reconfigure.
+"""
+
 
 from bzrlib import (
     branch,
     bzrdir,
     errors,
+    trace,
+    ui,
+    urlutils,
     )
 
+
+# TODO: common base class for all reconfigure operations, making no
+# assumptions about what kind of change will be done.
+
+
+class ReconfigureStackedOn(object):
+    """Reconfigures a branch to be stacked on another branch."""
+
+    def apply(self, bzrdir, stacked_on_url):
+        branch = bzrdir.open_branch()
+        # it may be a path relative to the cwd or a url; the branch wants
+        # a path relative to itself...
+        on_url = urlutils.relative_url(branch.base,
+            urlutils.normalize_url(stacked_on_url))
+        branch.lock_write()
+        try:
+            branch.set_stacked_on_url(on_url)
+            if not trace.is_quiet():
+                ui.ui_factory.note(
+                    "%s is now stacked on %s\n"
+                    % (branch.base, branch.get_stacked_on_url()))
+        finally:
+            branch.unlock()
+
+
+class ReconfigureUnstacked(object):
+
+    def apply(self, bzrdir):
+        branch = bzrdir.open_branch()
+        branch.lock_write()
+        try:
+            branch.set_stacked_on_url(None)
+            if not trace.is_quiet():
+                ui.ui_factory.note(
+                    "%s is now not stacked\n"
+                    % (branch.base,))
+        finally:
+            branch.unlock()
+
+
 class Reconfigure(object):
 
     def __init__(self, bzrdir, new_bound_location=None):

=== modified file 'bzrlib/remote.py'
--- a/bzrlib/remote.py	2009-07-06 09:47:35 +0000
+++ b/bzrlib/remote.py	2009-07-20 10:14:42 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006, 2007, 2008 Canonical Ltd
+# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -14,9 +14,6 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
-# TODO: At some point, handle upgrades by just passing the whole request
-# across to run on the server.
-
 import bz2
 
 from bzrlib import (
@@ -27,6 +24,7 @@
     debug,
     errors,
     graph,
+    lock,
     lockdir,
     repository,
     revision,
@@ -813,7 +811,23 @@
             result.add(_mod_revision.NULL_REVISION)
         return result
 
+    def _has_same_fallbacks(self, other_repo):
+        """Returns true if the repositories have the same fallbacks."""
+        # XXX: copied from Repository; it should be unified into a base class
+        # <https://bugs.edge.launchpad.net/bzr/+bug/401622>
+        my_fb = self._fallback_repositories
+        other_fb = other_repo._fallback_repositories
+        if len(my_fb) != len(other_fb):
+            return False
+        for f, g in zip(my_fb, other_fb):
+            if not f.has_same_location(g):
+                return False
+        return True
+
     def has_same_location(self, other):
+        # TODO: Move to RepositoryBase and unify with the regular Repository
+        # one; unfortunately the tests rely on slightly different behaviour at
+        # present -- mbp 20090710
         return (self.__class__ is other.__class__ and
                 self.bzrdir.transport.base == other.bzrdir.transport.base)
 
@@ -1025,7 +1039,7 @@
 
     def unlock(self):
         if not self._lock_count:
-            raise errors.LockNotHeld(self)
+            return lock.cant_unlock_not_held(self)
         self._lock_count -= 1
         if self._lock_count > 0:
             return
@@ -1230,7 +1244,9 @@
             raise errors.InternalBzrError(
                 "May not fetch while in a write group.")
         # fast path same-url fetch operations
-        if self.has_same_location(source) and fetch_spec is None:
+        if (self.has_same_location(source)
+            and fetch_spec is None
+            and self._has_same_fallbacks(source)):
             # check that last_revision is in 'from' and then return a
             # no-operation.
             if (revision_id is not None and

=== modified file 'bzrlib/repository.py'
--- a/bzrlib/repository.py	2009-07-02 23:10:53 +0000
+++ b/bzrlib/repository.py	2009-07-20 10:14:42 +0000
@@ -848,6 +848,7 @@
 ######################################################################
 # Repositories
 
+
 class Repository(object):
     """Repository holding history for one or more branches.
 
@@ -1183,8 +1184,25 @@
         self._inventory_entry_cache = fifo_cache.FIFOCache(10*1024)
 
     def __repr__(self):
-        return '%s(%r)' % (self.__class__.__name__,
-                           self.base)
+        if self._fallback_repositories:
+            return '%s(%r, fallback_repositories=%r)' % (
+                self.__class__.__name__,
+                self.base,
+                self._fallback_repositories)
+        else:
+            return '%s(%r)' % (self.__class__.__name__,
+                               self.base)
+
+    def _has_same_fallbacks(self, other_repo):
+        """Returns true if the repositories have the same fallbacks."""
+        my_fb = self._fallback_repositories
+        other_fb = other_repo._fallback_repositories
+        if len(my_fb) != len(other_fb):
+            return False
+        for f, g in zip(my_fb, other_fb):
+            if not f.has_same_location(g):
+                return False
+        return True
 
     def has_same_location(self, other):
         """Returns a boolean indicating if this repository is at the same
@@ -1529,7 +1547,11 @@
             raise errors.InternalBzrError(
                 "May not fetch while in a write group.")
         # fast path same-url fetch operations
-        if self.has_same_location(source) and fetch_spec is None:
+        # TODO: lift out to somewhere common with RemoteRepository
+        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
+        if (self.has_same_location(source)
+            and fetch_spec is None
+            and self._has_same_fallbacks(source)):
             # check that last_revision is in 'from' and then return a
             # no-operation.
             if (revision_id is not None and

=== modified file 'bzrlib/tests/blackbox/test_reconfigure.py'
--- a/bzrlib/tests/blackbox/test_reconfigure.py	2009-05-07 05:08:46 +0000
+++ b/bzrlib/tests/blackbox/test_reconfigure.py	2009-07-10 08:18:28 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2007 Canonical Ltd
+# Copyright (C) 2007, 2009 Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@
     tests,
     workingtree,
     )
+from bzrlib.branchbuilder import BranchBuilder
 
 
 class TestReconfigure(tests.TestCaseWithTransport):
@@ -182,3 +183,59 @@
 
     def test_lightweight_rich_root_pack_checkout_to_tree(self, format=None):
         self.test_lightweight_format_checkout_to_tree('rich-root-pack')
+
+
+class TestReconfigureStacking(tests.TestCaseWithTransport):
+
+    def test_reconfigure_stacking(self):
+        """Test a fairly realistic scenario for stacking:
+
+         * make a branch with some history
+         * branch it
+         * make the second branch stacked on the first
+         * commit in the second
+         * then make the second unstacked, so it has to fill in history from
+           the original fallback lying underneath its original content
+
+        See discussion in <https://bugs.edge.launchpad.net/bzr/+bug/391411>
+        """
+        # there are also per_branch tests that exercise remote operation etc
+        tree_1 = self.make_branch_and_tree('b1', format='2a')
+        self.build_tree(['b1/foo'])
+        tree_1.add(['foo'])
+        tree_1.commit('add foo')
+        branch_1 = tree_1.branch
+        # now branch and commit again
+        bzrdir_2 = tree_1.bzrdir.sprout('b2')
+        tree_2 = bzrdir_2.open_workingtree()
+        branch_2 = tree_2.branch
+        # now reconfigure to be stacked
+        out, err = self.run_bzr('reconfigure --stacked-on b1 b2')
+        self.assertContainsRe(out,
+            '^.*/b2/ is now stacked on ../b1\n$')
+        self.assertEquals('', err)
+        # can also give the absolute URL of the branch, and it gets stored 
+        # as a relative path if possible
+        out, err = self.run_bzr('reconfigure --stacked-on %s b2'
+            % (self.get_url('b1'),))
+        self.assertContainsRe(out,
+            '^.*/b2/ is now stacked on ../b1\n$')
+        self.assertEquals('', err)
+        # It should be given a relative URL to the destination, if possible,
+        # because that's most likely to work across different transports
+        self.assertEquals(branch_2.get_stacked_on_url(),
+            '../b1')
+        # commit, and it should be stored into b2's repo
+        self.build_tree_contents([('foo', 'new foo')])
+        tree_2.commit('update foo')
+        # Now turn it off again
+        out, err = self.run_bzr('reconfigure --unstacked b2')
+        self.assertContainsRe(out,
+            '^.*/b2/ is now not stacked\n$')
+        self.assertEquals('', err)
+        self.assertRaises(errors.NotStacked,
+            branch_2.get_stacked_on_url)
+
+    # XXX: Needs a test for reconfiguring stacking and shape at the same time;
+    # no branch at location; stacked-on is not a branch; quiet mode.
+    # -- mbp 20090706

=== modified file 'bzrlib/tests/per_branch/test_stacking.py'
--- a/bzrlib/tests/per_branch/test_stacking.py	2009-07-10 05:49:34 +0000
+++ b/bzrlib/tests/per_branch/test_stacking.py	2009-07-24 03:15:56 +0000
@@ -184,11 +184,17 @@
         trunk_revid = trunk_tree.commit('revision on mainline')
         # and make branch from it which is stacked
         try:
-            new_dir = trunk_tree.bzrdir.sprout('newbranch', stacked=True)
+            new_dir = trunk_tree.bzrdir.sprout(self.get_url('newbranch'),
+                stacked=True)
         except unstackable_format_errors, e:
             raise TestNotApplicable(e)
         # stacked repository
         self.assertRevisionNotInRepository('newbranch', trunk_revid)
+        # TODO: we'd like to commit in the stacked repository; that requires
+        # some care (maybe a BranchBuilder) if it's remote and has no
+        # workingtree
+        ##newbranch_revid = new_dir.open_workingtree().commit('revision in '
+            ##'newbranch')
         # now when we unstack that should implicitly fetch, to make sure that
         # the branch will still work
         new_branch = new_dir.open_branch()

=== modified file 'doc/en/user-guide/stacked.txt'
--- a/doc/en/user-guide/stacked.txt	2008-07-17 06:00:08 +0000
+++ b/doc/en/user-guide/stacked.txt	2009-07-20 08:47:58 +0000
@@ -83,3 +83,15 @@
 stacked-on branch needs to be available for almost all operations. This is
 not an issue when both branches are local or both branches are on the
 same server.
+
+
+Changing branch stacking
+------------------------
+
+Stacking of existing branches can be changed using the ``bzr reconfigure``
+command to either stack on an existing branch, or to turn off stacking.
+Be aware that when ``bzr reconfigure --unstacked`` is used, bzr will
+copy all the referenced data from the stacked-on repository into the
+previously stacked repository.  For large repositories this may take
+considerable time and may substantially increase the size of the
+repository.




More information about the bazaar-commits mailing list