Rev 88: Merge Aaron test fixes, support for stacking, and disabling of down-thread-with-changes. in http://bazaar.launchpad.net/~bzr-loom-devs/bzr-loom/trunk/

Robert Collins robertc at robertcollins.net
Mon Nov 17 00:58:01 GMT 2008


At http://bazaar.launchpad.net/~bzr-loom-devs/bzr-loom/trunk/

------------------------------------------------------------
revno: 88
revision-id: robertc at robertcollins.net-20081117005800-284m5e4d2hunwpxd
parent: robertc at robertcollins.net-20081117004746-k9uygzdp9y2xsh1d
parent: aaron at aaronbentley.com-20081017192404-b0biybmo0q40rvcm
committer: Robert Collins <robertc at robertcollins.net>
branch nick: trunk
timestamp: Mon 2008-11-17 11:58:00 +1100
message:
  Merge Aaron test fixes, support for stacking, and disabling of down-thread-with-changes.
modified:
  CONTRIBUTORS                   CONTRIBUTORS-20060620084702-jnrwijq76kg45klj-2
  branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
  commands.py                    commands.py-20060620084702-jnrwijq76kg45klj-6
  tests/blackbox.py              blackbox.py-20060620084702-jnrwijq76kg45klj-7
  tests/test_branch.py           test_branch.py-20060620084702-jnrwijq76kg45klj-9
  tests/test_tree.py             test_tree.py-20060701081608-boqb8o8yz1a2bil2-1
  tree.py                        tree.py-20060701085538-3ajq87mglfa5ryqa-1
    ------------------------------------------------------------
    revno: 86.1.18
    revision-id: aaron at aaronbentley.com-20081017192404-b0biybmo0q40rvcm
    parent: aaron at aaronbentley.com-20081016162441-yavoowvwh7ggrc8j
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Fri 2008-10-17 15:24:04 -0400
    message:
      Prevent pre-stacking exception by using _synchronize_history.
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.17
    revision-id: aaron at aaronbentley.com-20081016162441-yavoowvwh7ggrc8j
    parent: aaron at aaronbentley.com-20081010195541-3eteqrbkjvnsitj7
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Thu 2008-10-16 12:24:41 -0400
    message:
      Ensure require_loom_branc accepts branch7
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
      tests/test_branch.py           test_branch.py-20060620084702-jnrwijq76kg45klj-9
    ------------------------------------------------------------
    revno: 86.1.16
    revision-id: aaron at aaronbentley.com-20081010195541-3eteqrbkjvnsitj7
    parent: aaron at aaronbentley.com-20081007003428-0doh0jafbhw3baq0
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Fri 2008-10-10 15:55:41 -0400
    message:
      Add loom format 7, for compatibility with Branch format 7.
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
      tests/test_branch.py           test_branch.py-20060620084702-jnrwijq76kg45klj-9
    ------------------------------------------------------------
    revno: 86.1.15
    revision-id: aaron at aaronbentley.com-20081007003428-0doh0jafbhw3baq0
    parent: aaron at aaronbentley.com-20081002012428-z88nhc4hqwv3et7x
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-10-06 20:34:28 -0400
    message:
      Fix transport reuse
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.14
    revision-id: aaron at aaronbentley.com-20081002012428-z88nhc4hqwv3et7x
    parent: aaron at aaronbentley.com-20081001135629-dcngj75q4o5qwdec
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Wed 2008-10-01 21:24:28 -0400
    message:
      Move up_many onto LoomTreeDecorator, add tests
    modified:
      commands.py                    commands.py-20060620084702-jnrwijq76kg45klj-6
      tests/blackbox.py              blackbox.py-20060620084702-jnrwijq76kg45klj-7
      tests/test_tree.py             test_tree.py-20060701081608-boqb8o8yz1a2bil2-1
      tree.py                        tree.py-20060701085538-3ajq87mglfa5ryqa-1
    ------------------------------------------------------------
    revno: 86.1.13
    revision-id: aaron at aaronbentley.com-20081001135629-dcngj75q4o5qwdec
    parent: aaron at aaronbentley.com-20080930125115-ylw0d200m4nj1ww2
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Wed 2008-10-01 09:56:29 -0400
    message:
      Implement up-thread --auto
    modified:
      commands.py                    commands.py-20060620084702-jnrwijq76kg45klj-6
    ------------------------------------------------------------
    revno: 86.1.12
    revision-id: aaron at aaronbentley.com-20080930125115-ylw0d200m4nj1ww2
    parent: aaron at aaronbentley.com-20080930035310-zrxdxbl7ikg5yj4m
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Tue 2008-09-30 08:51:15 -0400
    message:
      fix NULL_REVISION handling
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.11
    revision-id: aaron at aaronbentley.com-20080930035310-zrxdxbl7ikg5yj4m
    parent: aaron at aaronbentley.com-20080930034427-bcnu1gofbiw5kma6
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-09-29 23:53:10 -0400
    message:
      All tests matching loom pass
    modified:
      tests/blackbox.py              blackbox.py-20060620084702-jnrwijq76kg45klj-7
      tests/test_tree.py             test_tree.py-20060701081608-boqb8o8yz1a2bil2-1
    ------------------------------------------------------------
    revno: 86.1.10
    revision-id: aaron at aaronbentley.com-20080930034427-bcnu1gofbiw5kma6
    parent: aaron at aaronbentley.com-20080930032650-55izsid6lcaozz44
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-09-29 23:44:27 -0400
    message:
      Handle NULL_REVISION
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.9
    revision-id: aaron at aaronbentley.com-20080930032650-55izsid6lcaozz44
    parent: aaron at aaronbentley.com-20080930032157-0ds53wiuycl9vdqf
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-09-29 23:26:50 -0400
    message:
      Add get_file_with_stat
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.8
    revision-id: aaron at aaronbentley.com-20080930032157-0ds53wiuycl9vdqf
    parent: aaron at aaronbentley.com-20080930030721-sylbu6agrhijl4ir
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-09-29 23:21:57 -0400
    message:
      Fix failing tests
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.7
    revision-id: aaron at aaronbentley.com-20080930030721-sylbu6agrhijl4ir
    parent: aaron at aaronbentley.com-20080929195112-9yqt8kuq4zh3fdvx
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-09-29 23:07:21 -0400
    message:
      Get all but two Branch tests passing
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.6
    revision-id: aaron at aaronbentley.com-20080929195112-9yqt8kuq4zh3fdvx
    parent: aaron at aaronbentley.com-20080929194207-7fqh0f54oyz5lsjv
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-09-29 15:51:12 -0400
    message:
      Do not error when copy_content_into destination is not a loom
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.5
    revision-id: aaron at aaronbentley.com-20080929194207-7fqh0f54oyz5lsjv
    parent: aaron at aaronbentley.com-20080929193851-237ik1km7xek09wz
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-09-29 15:42:07 -0400
    message:
      Handle NULL_REVISION in copy_content_into
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.4
    revision-id: aaron at aaronbentley.com-20080929193851-237ik1km7xek09wz
    parent: aaron at aaronbentley.com-20080922173529-vwosyfd5e4qrw2wx
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-09-29 15:38:51 -0400
    message:
      Honour new _override_hook_target param
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
    ------------------------------------------------------------
    revno: 86.1.3
    revision-id: aaron at aaronbentley.com-20080922173529-vwosyfd5e4qrw2wx
    parent: aaron at aaronbentley.com-20080920160119-i35ovqtxbd4v9wcb
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Mon 2008-09-22 13:35:29 -0400
    message:
      Correctly populate trees produced by export-loom
    modified:
      branch.py                      branch.py-20060620084702-jnrwijq76kg45klj-5
      tests/test_branch.py           test_branch.py-20060620084702-jnrwijq76kg45klj-9
    ------------------------------------------------------------
    revno: 86.1.2
    revision-id: aaron at aaronbentley.com-20080920160119-i35ovqtxbd4v9wcb
    parent: aaron at aaronbentley.com-20080905140031-nyf5u3u2a30wu81e
    parent: jml at canonical.com-20080912014618-u132uws19xj9uamv
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Sat 2008-09-20 12:01:19 -0400
    message:
      Merge with trunk
    modified:
      CONTRIBUTORS                   CONTRIBUTORS-20060620084702-jnrwijq76kg45klj-2
        ------------------------------------------------------------
        revno: 86.3.1
        revision-id: jml at canonical.com-20080912014618-u132uws19xj9uamv
        parent: jml at canonical.com-20080725052406-r9rplrdtu8c3gyhm
        parent: jml at canonical.com-20080912014434-lw68tqey2k6vevzp
        committer: Jonathan Lange <jml at canonical.com>
        branch nick: trunk
        timestamp: Fri 2008-09-12 11:46:18 +1000
        message:
          Add myself to contributors.
        modified:
          CONTRIBUTORS                   CONTRIBUTORS-20060620084702-jnrwijq76kg45klj-2
        ------------------------------------------------------------
        revno: 86.2.1
        revision-id: jml at canonical.com-20080912014434-lw68tqey2k6vevzp
        parent: jml at canonical.com-20080725052406-r9rplrdtu8c3gyhm
        committer: Jonathan Lange <jml at canonical.com>
        branch nick: jml-contributed
        timestamp: Fri 2008-09-12 11:44:34 +1000
        message:
          Add myself to contributors.
        modified:
          CONTRIBUTORS                   CONTRIBUTORS-20060620084702-jnrwijq76kg45klj-2
    ------------------------------------------------------------
    revno: 86.1.1
    revision-id: aaron at aaronbentley.com-20080905140031-nyf5u3u2a30wu81e
    parent: jml at canonical.com-20080725052406-r9rplrdtu8c3gyhm
    committer: Aaron Bentley <aaron at aaronbentley.com>
    branch nick: loom
    timestamp: Fri 2008-09-05 10:00:31 -0400
    message:
      Make down-thread error on uncommitted changes
    modified:
      commands.py                    commands.py-20060620084702-jnrwijq76kg45klj-6
      tests/blackbox.py              blackbox.py-20060620084702-jnrwijq76kg45klj-7
=== modified file 'CONTRIBUTORS'
--- a/CONTRIBUTORS	2008-02-28 11:15:18 +0000
+++ b/CONTRIBUTORS	2008-09-12 01:44:34 +0000
@@ -2,3 +2,4 @@
 Scott James Remnant <scott at canonical.com>
 Aaron Bentley <aaron.bentley at canonical.com>
 Rob Weir <rweir at ertius.org>
+Jonathan Lange <jml at mumak.net>

=== modified file 'branch.py'
--- a/branch.py	2008-05-23 03:04:33 +0000
+++ b/branch.py	2008-10-17 19:24:04 +0000
@@ -52,24 +52,22 @@
     """
     try:
         branch.lock_write()
-        if branch._format.__class__ == bzrlib.branch.BzrBranchFormat5:
-            format = BzrBranchLoomFormat1()
-            format.take_over(branch)
-        elif branch._format.__class__ == bzrlib.branch.BzrBranchFormat6:
-            format = BzrBranchLoomFormat6()
-            format.take_over(branch)
-        else:
+        try:
+            format = {
+                bzrlib.branch.BzrBranchFormat5: BzrBranchLoomFormat1,
+                bzrlib.branch.BzrBranchFormat6: BzrBranchLoomFormat6,
+                bzrlib.branch.BzrBranchFormat7: BzrBranchLoomFormat7,
+            }[branch._format.__class__]()
+        except KeyError:
             raise UnsupportedBranchFormat(branch._format)
+        format.take_over(branch)
     finally:
         branch.unlock()
 
 
 def require_loom_branch(branch):
     """Return None if branch is already loomified, or raise NotALoom."""
-    if not branch._format.__class__ in (
-        BzrBranchLoomFormat1,
-        BzrBranchLoomFormat6,
-        ):
+    if not branch._format.__class__ in LOOM_FORMATS:
         raise NotALoom(branch)
 
 
@@ -155,7 +153,10 @@
         As usual this must be for the single existing file 'loom'.
         """
         return self._loom_stream
-    
+
+    def get_file_with_stat(self, file_id, path=None):
+        return (self.get_file(file_id, path), None)
+
     def get_file_sha1(self, file_id, path):
         """Get the sha1 for a file. 
 
@@ -249,8 +250,7 @@
             else:
                 loom_tip = None
             threads = self.get_threads(state.get_basis_revision_id())
-            new_history = self.revision_history()
-            if revision_id is not None:
+            if revision_id not in (None, NULL_REVISION):
                 if threads:
                     # revision_id should be in the loom, or its an error 
                     found_threads = [thread for thread, rev in threads
@@ -260,14 +260,7 @@
                         # side has not been recorded yet, so its data is not
                         # present at this point.
                         raise UnrecordedRevision(self, revision_id)
-                else:
-                    # no threads yet, be a normal branch
-                    try:
-                        new_history = new_history[:new_history.index(revision_id) + 1]
-                    except ValueError:
-                        rev = self.repository.get_revision(revision_id)
-                        new_history = rev.get_history(self.repository)[1:]
-                    
+
                 # pull in the warp, which was skipped during the initial pull
                 # because the front end does not know what to pull.
                 # nb: this is mega huge hacky. THINK. RBC 2006062
@@ -284,19 +277,23 @@
                 finally:
                     nested.finished()
             state = loom_state.LoomState()
-            if threads:
-                last_rev = threads[-1][1]
-                if last_rev == EMPTY_REVISION:
-                    last_rev = bzrlib.revision.NULL_REVISION
-                destination.generate_revision_history(last_rev)
-                state.set_parents([loom_tip])
-                state.set_threads(
-                    (thread + ([thread[1]],) for thread in threads)
-                    )
-            else:
-                # no threads yet, be a normal branch.
-                destination.set_revision_history(new_history)
-            destination._set_last_loom(state)
+            try:
+                require_loom_branch(destination)
+                if threads:
+                    last_rev = threads[-1][1]
+                    if last_rev == EMPTY_REVISION:
+                        last_rev = bzrlib.revision.NULL_REVISION
+                    destination.generate_revision_history(last_rev)
+                    state.set_parents([loom_tip])
+                    state.set_threads(
+                        (thread + ([thread[1]],) for thread in threads)
+                        )
+                else:
+                    # no threads yet, be a normal branch.
+                    self._synchronize_history(destination, revision_id)
+                destination._set_last_loom(state)
+            except NotALoom:
+                self._synchronize_history(destination, revision_id)
             try:
                 parent = self.get_parent()
             except bzrlib.errors.InaccessibleParent, e:
@@ -358,13 +355,16 @@
             user_location = bzrlib.urlutils.unescape_for_display(
                 thread_transport.base, 'utf-8')
             try:
-                branch = bzrlib.branch.Branch.open_from_transport(
-                    thread_transport)
+                control_dir = bzrdir.BzrDir.open(thread_transport.base,
+                    possible_transports=[thread_transport])
+                tree, branch = control_dir._get_tree_branch()
             except bzrlib.errors.NotBranchError:
                 bzrlib.trace.note('Creating branch at %s' % user_location)
                 branch = bzrdir.BzrDir.create_branch_convenience(
                     thread_transport.base,
                     possible_transports=[thread_transport])
+                tree, branch = branch.bzrdir.open_tree_or_branch(
+                    thread_transport.base)
             else:
                 if thread_revision == branch.last_revision():
                     bzrlib.trace.note('Skipping up-to-date branch at %s'
@@ -372,7 +372,10 @@
                     continue
                 else:
                     bzrlib.trace.note('Updating branch at %s' % user_location)
-            branch.pull(self, stop_revision=thread_revision)
+            if tree is not None:
+                tree.pull(self, stop_revision=thread_revision)
+            else:
+                branch.pull(self, stop_revision=thread_revision)
 
     def _loom_content(self, rev_id):
         """Return the raw formatted content of a loom as a series of lines.
@@ -432,7 +435,7 @@
 
     @needs_write_lock
     def pull(self, source, overwrite=False, stop_revision=None,
-        run_hooks=True, possible_transports=None):
+        run_hooks=True, possible_transports=None, _override_hook_target=None):
         """Pull from a branch into this loom.
 
         If the remote branch is a non-loom branch, the pull is done against the
@@ -442,110 +445,21 @@
         if not isinstance(source, LoomSupport):
             return super(LoomSupport, self).pull(source,
                 overwrite=overwrite, stop_revision=stop_revision,
-                possible_transports=possible_transports)
-        # pull the loom, and position our
-        pb = bzrlib.ui.ui_factory.nested_progress_bar()
-        result = bzrlib.branch.PullResult()
-        result.source_branch = source
-        result.target_branch = self
-        # cannot bind currently
-        result.local_branch = None
-        result.master_branch = self
-        try:
-            result.old_revno, result.old_revid = self.last_revision_info()
-            source.lock_read()
-            try:
-                source_state = source.get_loom_state()
-                source_parents = source_state.get_parents()
-                if not source_parents:
-                    # no thread commits ever
-                    # just pull the main branch.
-                    new_rev = source.last_revision()
-                    self.repository.fetch(source.repository,
-                        revision_id=new_rev)
-                    if not overwrite:
-                        new_rev_ancestry = source.repository.get_ancestry(
-                            new_rev)
-                        last_rev = self.last_revision()
-                        # get_ancestry returns None for NULL_REVISION currently.
-                        if last_rev == NULL_REVISION:
-                            last_rev = None
-                        if last_rev not in new_rev_ancestry:
-                            raise bzrlib.errors.DivergedBranches(self, source)
-                    old_count = len(self.revision_history())
-                    if new_rev == EMPTY_REVISION:
-                        new_rev = bzrlib.revision.NULL_REVISION
-                    self.generate_revision_history(new_rev)
-                    # get the final result object details
-                    result.tag_conflicts = None
-                    result.new_revno, result.new_revid = self.last_revision_info()
-                    if run_hooks:
-                        for hook in bzrlib.branch.Branch.hooks['post_pull']:
-                            hook(result)
-                    return result
-                # pulling a loom
-                # the first parent is the 'tip' revision.
-                my_state = self.get_loom_state()
-                source_loom_rev = source_state.get_parents()[0]
-                if not overwrite:
-                    # is the loom compatible?
-                    if len(my_state.get_parents()) > 0:
-                        source_ancestry = source.repository.get_ancestry(
-                            source_loom_rev)
-                        if my_state.get_parents()[0] not in source_ancestry:
-                            raise bzrlib.errors.DivergedBranches(self, source)
-                # fetch the loom content
-                self.repository.fetch(source.repository,
-                    revision_id=source_loom_rev)
-                # get the threads for the new basis
-                threads = self.get_threads(source_state.get_basis_revision_id())
-                # stopping at from our repository.
-                revisions = [rev for name,rev in threads]
-                # for each thread from top to bottom, retrieve its referenced
-                # content. XXX FIXME: a revision_ids parameter to fetch would be
-                # nice here.
-                # the order is reversed because the common case is for the top
-                # thread to include all content.
-                for rev_id in reversed(revisions):
-                    if rev_id not in (EMPTY_REVISION,
-                        bzrlib.revision.NULL_REVISION):
-                        # fetch the loom content for this revision
-                        self.repository.fetch(source.repository,
-                            revision_id=rev_id)
-                # set our work threads to match (this is where we lose data if
-                # there are local mods)
-                my_state.set_threads(
-                    (thread + ([thread[1]],) for thread in threads)
-                    )
-                # and the new parent data
-                my_state.set_parents([source_loom_rev])
-                # and save the state.
-                self._set_last_loom(my_state)
-                # set the branch nick.
-                self.nick = threads[-1][0]
-                # and position the branch on the top loom
-                new_rev = threads[-1][1]
-                if new_rev == EMPTY_REVISION:
-                    new_rev = bzrlib.revision.NULL_REVISION
-                self.generate_revision_history(new_rev)
-                # get the final result object details
-                result.tag_conflicts = None
-                result.new_revno, result.new_revid = self.last_revision_info()
-                if run_hooks:
-                    for hook in bzrlib.branch.Branch.hooks['post_pull']:
-                        hook(result)
-                return result
-            finally:
-                source.unlock()
-        finally:
-            pb.finished()
+                possible_transports=possible_transports,
+                _override_hook_target=_override_hook_target)
+        return _Puller(source, self).transfer(overwrite, stop_revision,
+            run_hooks, possible_transports, _override_hook_target)
 
     @needs_read_lock
     def push(self, target, overwrite=False, stop_revision=None,
         _override_hook_source_branch=None):
         # Not ideal, but see the issues raised on bazaar at lists.canonical.com
         # about the push api needing work.
-        return target.pull(self, overwrite=overwrite, stop_revision=stop_revision)
+        if not isinstance(target, LoomSupport):
+            return super(LoomSupport, self).push(target, overwrite,
+                stop_revision, _override_hook_source_branch=None)
+        return _Pusher(self, target).transfer(overwrite, stop_revision,
+                                              run_hooks=True)
 
     @needs_write_lock
     def record_loom(self, commit_message):
@@ -689,6 +603,151 @@
         super(LoomSupport, self).unlock()
 
 
+class _Puller(object):
+
+    def __init__(self, source, target):
+        self.target = target
+        self.source = source
+
+    def prepare_result(self, _override_hook_target):
+        result = self.make_result()
+        result.source_branch = self.source
+        result.target_branch = _override_hook_target
+        if result.target_branch is None:
+            result.target_branch = self.target
+        # cannot bind currently
+        result.local_branch = None
+        result.master_branch = self.target
+        result.old_revno, result.old_revid = self.target.last_revision_info()
+        return result
+
+    def finish_result(self, result):
+        result.tag_conflicts = None
+        result.new_revno, result.new_revid = self.target.last_revision_info()
+
+    def do_hooks(self, result, run_hooks):
+        self.finish_result(result)
+        # get the final result object details
+        if run_hooks:
+            for hook in self.post_hooks():
+                hook(result)
+        return result
+
+    @staticmethod
+    def make_result():
+        return bzrlib.branch.PullResult()
+
+    @staticmethod
+    def post_hooks():
+        return bzrlib.branch.Branch.hooks['post_pull']
+
+    def plain_transfer(self, result, run_hooks, stop_revision, overwrite):
+        # no thread commits ever
+        # just pull the main branch.
+        new_rev = stop_revision
+        if new_rev is None:
+            new_rev = self.source.last_revision()
+        if new_rev == EMPTY_REVISION:
+            new_rev = bzrlib.revision.NULL_REVISION
+        self.target.repository.fetch(self.source.repository,
+            revision_id=new_rev)
+        if not overwrite:
+            new_rev_ancestry = self.source.repository.get_ancestry(
+                new_rev)
+            last_rev = self.target.last_revision()
+            # get_ancestry returns None for NULL_REVISION currently.
+            if last_rev == NULL_REVISION:
+                last_rev = None
+            if last_rev not in new_rev_ancestry:
+                raise bzrlib.errors.DivergedBranches(
+                    self.target, self.source)
+        self.target.generate_revision_history(new_rev)
+        # get the final result object details
+        self.do_hooks(result, run_hooks)
+        return result
+
+    def transfer(self, overwrite, stop_revision, run_hooks=True,
+                 possible_transports=None, _override_hook_target=None):
+        """Implementation of push and pull"""
+        # pull the loom, and position our
+        pb = bzrlib.ui.ui_factory.nested_progress_bar()
+        try:
+            result = self.prepare_result(_override_hook_target)
+            self.target.lock_write()
+            self.source.lock_read()
+            try:
+                source_state = self.source.get_loom_state()
+                source_parents = source_state.get_parents()
+                if not source_parents:
+                    return self.plain_transfer(result, run_hooks,
+                                               stop_revision, overwrite)
+                # pulling a loom
+                # the first parent is the 'tip' revision.
+                my_state = self.target.get_loom_state()
+                source_loom_rev = source_state.get_parents()[0]
+                if not overwrite:
+                    # is the loom compatible?
+                    if len(my_state.get_parents()) > 0:
+                        source_ancestry = self.source.repository.get_ancestry(
+                            source_loom_rev)
+                        if my_state.get_parents()[0] not in source_ancestry:
+                            raise bzrlib.errors.DivergedBranches(
+                                self.target, self.source)
+                # fetch the loom content
+                self.target.repository.fetch(self.source.repository,
+                    revision_id=source_loom_rev)
+                # get the threads for the new basis
+                threads = self.target.get_threads(
+                    source_state.get_basis_revision_id())
+                # stopping at from our repository.
+                revisions = [rev for name,rev in threads]
+                # for each thread from top to bottom, retrieve its referenced
+                # content. XXX FIXME: a revision_ids parameter to fetch would be
+                # nice here.
+                # the order is reversed because the common case is for the top
+                # thread to include all content.
+                for rev_id in reversed(revisions):
+                    if rev_id not in (EMPTY_REVISION,
+                        bzrlib.revision.NULL_REVISION):
+                        # fetch the loom content for this revision
+                        self.target.repository.fetch(self.source.repository,
+                            revision_id=rev_id)
+                # set our work threads to match (this is where we lose data if
+                # there are local mods)
+                my_state.set_threads(
+                    (thread + ([thread[1]],) for thread in threads)
+                    )
+                # and the new parent data
+                my_state.set_parents([source_loom_rev])
+                # and save the state.
+                self.target._set_last_loom(my_state)
+                # set the branch nick.
+                self.target.nick = threads[-1][0]
+                # and position the branch on the top loom
+                new_rev = threads[-1][1]
+                if new_rev == EMPTY_REVISION:
+                    new_rev = bzrlib.revision.NULL_REVISION
+                self.target.generate_revision_history(new_rev)
+                self.do_hooks(result, run_hooks)
+                return result
+            finally:
+                self.source.unlock()
+                self.target.unlock()
+        finally:
+            pb.finished()
+
+
+class _Pusher(_Puller):
+
+    @staticmethod
+    def make_result():
+        return bzrlib.branch.PushResult()
+
+    @staticmethod
+    def post_hooks():
+        return bzrlib.branch.Branch.hooks['post_push']
+
+
 class LoomBranch(LoomSupport, bzrlib.branch.BzrBranch5):
     """The Loom branch.
     
@@ -705,6 +764,15 @@
     """
 
 
+class LoomBranch7(LoomSupport, bzrlib.branch.BzrBranch7):
+    """Branch6 Loom branch.
+
+    A mixin is used as the easiest migration path to support branch7.
+    A rewrite would be preferable, but a stackable loom format is needed
+    quickly.
+    """
+
+
 class LoomFormatMixin(object):
     """Support code for Loom formats."""
     # A mixin is not ideal because it is tricky to test, but it seems to be the
@@ -821,5 +889,40 @@
         return "bzr loom format 6 (based on bzr branch format 6)\n"
 
 
+class BzrBranchLoomFormat7(LoomFormatMixin, bzrlib.branch.BzrBranchFormat7):
+    """Loom's second edition - based on bzr's Branch7.
+
+    This format is an extension to BzrBranchFormat7 with the following changes:
+     - a last-loom file.
+
+     The last-loom file has a revision id in it which points into the loom
+     data branch in the repository.
+
+    This format is new in the loom plugin.
+    """
+
+    _branch_class = LoomBranch7
+    _parent_classs = bzrlib.branch.BzrBranchFormat7
+
+    def get_format_string(self):
+        """See BranchFormat.get_format_string()."""
+        return "Bazaar-NG Loom branch format 7\n"
+
+    def get_format_description(self):
+        """See BranchFormat.get_format_description()."""
+        return "Loom branch format 7"
+
+    def __str__(self):
+        return "bzr loom format 7 (based on bzr branch format 7)\n"
+
+
 bzrlib.branch.BranchFormat.register_format(BzrBranchLoomFormat1())
 bzrlib.branch.BranchFormat.register_format(BzrBranchLoomFormat6())
+bzrlib.branch.BranchFormat.register_format(BzrBranchLoomFormat7())
+
+
+LOOM_FORMATS = [
+    BzrBranchLoomFormat1,
+    BzrBranchLoomFormat6,
+    BzrBranchLoomFormat7,
+]

=== modified file 'commands.py'
--- a/commands.py	2008-07-21 03:08:00 +0000
+++ b/commands.py	2008-11-17 00:58:00 +0000
@@ -275,18 +275,34 @@
 
 class cmd_down_thread(bzrlib.commands.Command):
     """Move the branch down a thread in the loom.
-    
+
     This removes the changes introduced by the current thread from the branch
     and sets the branch to be the next thread down.
+
+    Down-thread refuses to operate if there are uncommitted changes, since
+    this is typically a mistake.  Switch can be used for this purpose, instead.
     """
 
     takes_args = ['thread?']
+    _see_also = ['switch']
 
     def run(self, thread=None):
-        (tree, path) = workingtree.WorkingTree.open_containing('.')
-        branch.require_loom_branch(tree.branch)
-        tree = LoomTreeDecorator(tree)
-        return tree.down_thread(thread)
+        (wt, path) = workingtree.WorkingTree.open_containing('.')
+        branch.require_loom_branch(wt.branch)
+        tree = LoomTreeDecorator(wt)
+        tree.lock_write()
+        try:
+            basis = wt.basis_tree()
+            basis.lock_read()
+            try:
+                for change in wt.iter_changes(basis):
+                    raise errors.BzrCommandError(
+                        'Working tree has uncommitted changes.')
+            finally:
+                basis.unlock()
+            return tree.down_thread(thread)
+        finally:
+            tree.unlock()
 
 
 class cmd_up_thread(bzrlib.commands.Command):
@@ -297,13 +313,17 @@
     that thread.
     """
 
-    takes_options = ['merge-type']
+    takes_options = ['merge-type', Option('auto',
+        help='Automatically commit and merge repeatedly.')]
 
-    def run(self, merge_type=None):
+    def run(self, merge_type=None, auto=False):
         (tree, path) = workingtree.WorkingTree.open_containing('.')
         branch.require_loom_branch(tree.branch)
         tree = LoomTreeDecorator(tree)
-        return tree.up_thread(merge_type)
+        if not auto:
+            return tree.up_thread(merge_type)
+        else:
+            return tree.up_many(merge_type)
 
 
 class cmd_export_loom(bzrlib.commands.Command):

=== modified file 'tests/blackbox.py'
--- a/tests/blackbox.py	2008-07-17 09:36:54 +0000
+++ b/tests/blackbox.py	2008-10-02 01:24:28 +0000
@@ -21,6 +21,7 @@
 import os
 
 import bzrlib
+from bzrlib import branch as _mod_branch
 from bzrlib import workingtree
 from bzrlib.plugins.loom.branch import EMPTY_REVISION
 from bzrlib.plugins.loom.tree import LoomTreeDecorator
@@ -384,6 +385,17 @@
         """We should raise a user-friendly exception if the branch isn't loomed yet."""
         self.assert_exception_raised_on_non_loom_branch(['down-thread'])
 
+    def test_down_thread_with_changes(self):
+        """Trying to down-thread with changes causes an error."""
+        tree = self.get_vendor_loom()
+        tree.branch.new_thread('upper-thread')
+        tree.branch.nick = 'upper-thread'
+        self.build_tree(['new-file'])
+        tree.add('new-file')
+        out, err = self.run_bzr('down-thread', retcode=3)
+        self.assertEqual('bzr: ERROR: Working tree has uncommitted changes.\n',
+                         err)
+
 
 class TestUp(TestsWithLooms):
 
@@ -471,6 +483,14 @@
         self.run_bzr(['down-thread'])
         self.run_bzr(['up-thread', '--lca'])
 
+    def test_up_thread_auto(self):
+        tree = self.get_vendor_loom()
+        tree.branch.new_thread('middle')
+        tree.branch.new_thread('top')
+        self.run_bzr('up-thread --auto')
+        branch = _mod_branch.Branch.open('.')
+        self.assertEqual('top', branch.nick)
+
 
 class TestPush(TestsWithLooms):
 
@@ -522,7 +542,7 @@
             out, err = self.run_bzr(['pull'])
         finally:
             os.chdir('..')
-        self.assertStartsWith(out, 'Using saved location:')
+        self.assertStartsWith(out, 'Using saved parent location:')
         self.assertEndsWith(out, 'Now on revision 2.\n')
         self.assertEqual(
             'All changes applied successfully.\n',

=== modified file 'tests/test_branch.py'
--- a/tests/test_branch.py	2008-04-10 00:38:37 +0000
+++ b/tests/test_branch.py	2008-10-16 16:24:41 +0000
@@ -34,6 +34,8 @@
 import bzrlib.revision
 from bzrlib.revision import NULL_REVISION
 from bzrlib.tests import TestCaseWithTransport
+from bzrlib.transport import get_transport
+from bzrlib.workingtree import WorkingTree
 
 
 class TestFormat(TestCaseWithTransport):
@@ -65,13 +67,23 @@
         branch = self.make_branch('.')
         self.assertRaises(NotALoom, require_loom_branch, branch)
 
-    def test_on_loom(self):
-        branch = self.make_branch('.')
+
+    def works_on_loom(self, format):
+        branch = self.make_branch('.', format)
         loomify(branch)
         # reopen it
         branch = branch.bzrdir.open_branch()
         self.assertEqual(None, require_loom_branch(branch))
 
+    def test_works_on_loom1(self):
+        self.works_on_loom('knit')
+
+    def test_works_on_loom6(self):
+        self.works_on_loom('pack-0.92')
+
+    def test_works_on_loom7(self):
+        self.works_on_loom('1.6')
+
 
 class TestLoomify(TestCaseWithTransport):
 
@@ -113,6 +125,12 @@
             bzrlib.plugins.loom.branch.LoomBranch6,
             bzrlib.plugins.loom.branch.BzrBranchLoomFormat6)
 
+    def test_loomify_branch_format_7(self):
+        branch = self.make_branch('.', format='1.6')
+        loomify(branch)
+        self.assertConvertedBranchFormat(branch,
+            bzrlib.plugins.loom.branch.LoomBranch7,
+            bzrlib.plugins.loom.branch.BzrBranchLoomFormat7)
 
 class TestLoom(TestCaseWithLoom):
 
@@ -595,3 +613,25 @@
         self.assertEqual('thread1-id', thread1.last_revision())
         thread2 = Branch.open_from_transport(root_transport.clone('thread2'))
         self.assertEqual('thread2-id', thread2.last_revision())
+
+    def test_export_loom_as_tree(self):
+        tree = self.get_multi_threaded()
+        tree.branch.bzrdir.root_transport.mkdir('root')
+        root_transport = tree.branch.bzrdir.root_transport.clone('root')
+        tree.branch.export_threads(root_transport)
+        export_tree = WorkingTree.open(root_transport.local_abspath('thread1'))
+        self.assertEqual('thread1-id', export_tree.last_revision())
+
+    def test_export_loom_as_branch(self):
+        tree = self.get_multi_threaded()
+        tree.branch.bzrdir.root_transport.mkdir('root')
+        root_path = tree.branch.bzrdir.root_transport.local_abspath('root')
+        repo = self.make_repository('root', shared=True)
+        repo.set_make_working_trees(False)
+        root_transport = get_transport('root')
+        tree.branch.export_threads(root_transport)
+        self.assertRaises(errors.NoWorkingTree, WorkingTree.open,
+                          root_transport.local_abspath('thread1'))
+        export_branch = Branch.open_from_transport(
+            root_transport.clone('thread1'))
+        self.assertEqual('thread1-id', export_branch.last_revision())

=== modified file 'tests/test_tree.py'
--- a/tests/test_tree.py	2008-03-20 06:02:24 +0000
+++ b/tests/test_tree.py	2008-10-02 01:24:28 +0000
@@ -98,7 +98,7 @@
         tree_loom_tree.down_thread()
         # check the test will be valid
         self.assertEqual([None, bottom_rev1, top_rev1],
-            tree.branch.repository.get_ancestry([top_rev1]))
+            tree.branch.repository.get_ancestry(top_rev1))
         self.assertEqual([bottom_rev1], tree.get_parent_ids())
         tree_loom_tree.up_thread()
         self.assertEqual('top', tree.branch.nick)
@@ -117,7 +117,7 @@
         tree_loom_tree.down_thread()
         # check the test will be valid
         self.assertEqual([None, bottom_rev1, top_rev1],
-            tree.branch.repository.get_ancestry([top_rev1]))
+            tree.branch.repository.get_ancestry(top_rev1))
         self.assertEqual([bottom_rev1], tree.get_parent_ids())
         bottom_rev2 = tree.commit('bottom_two', allow_pointless=True)
         tree_loom_tree.up_thread()
@@ -146,6 +146,51 @@
         loom_tree.up_thread(_mod_merge.WeaveMerger)
         self.failIfExists('source/a.BASE')
 
+    def get_loom_with_three_threads(self):
+        tree = self.get_tree_with_loom('source')
+        tree.branch.new_thread('bottom')
+        tree.branch.new_thread('middle')
+        tree.branch.new_thread('top')
+        tree.branch.nick = 'bottom'
+        return bzrlib.plugins.loom.tree.LoomTreeDecorator(tree)
+
+    def test_up_many(self):
+        loom_tree = self.get_loom_with_three_threads()
+        loom_tree.up_many()
+        self.assertEqual('top', loom_tree.tree.branch.nick)
+        self.assertEqual([], loom_tree.tree.get_parent_ids())
+
+    def test_up_many_commits(self):
+        loom_tree = self.get_loom_with_two_threads()
+        loom_tree.tree.commit('bottom', rev_id='bottom-1')
+        loom_tree.up_thread()
+        loom_tree.tree.commit('top', rev_id='top-1')
+        loom_tree.down_thread()
+        loom_tree.tree.commit('bottom', rev_id='bottom-2')
+        loom_tree.up_many()
+        last_revision = loom_tree.tree.last_revision()
+        self.assertNotEqual(last_revision, 'top-1')
+        rev = loom_tree.tree.branch.repository.get_revision(last_revision)
+        self.assertEqual(['top-1', 'bottom-2'], rev.parent_ids)
+        self.assertEqual('Merge bottom into top', rev.message)
+
+    def test_up_many_halts_on_conflicts(self):
+        loom_tree = self.get_loom_with_three_threads()
+        tree = loom_tree.tree
+        self.build_tree_contents([('source/file', 'contents-a')])
+        tree.add('file')
+        tree.commit('bottom', rev_id='bottom-1')
+        loom_tree.up_thread()
+        self.build_tree_contents([('source/file', 'contents-b')])
+        tree.commit('middle', rev_id='middle-1')
+        loom_tree.down_thread()
+        self.build_tree_contents([('source/file', 'contents-c')])
+        tree.commit('bottom', rev_id='bottom-2')
+        loom_tree.up_many()
+        self.assertEqual('middle', tree.branch.nick)
+        self.assertEqual(['middle-1', 'bottom-2'], tree.get_parent_ids())
+        self.assertEqual(1, len(tree.conflicts()))
+
     def test_revert_loom(self):
         tree = self.get_tree_with_loom(',')
         # ensure we have some stuff to revert

=== modified file 'tree.py'
--- a/tree.py	2008-04-24 16:13:06 +0000
+++ b/tree.py	2008-10-02 01:24:28 +0000
@@ -133,6 +133,17 @@
         else:
             return 0
 
+    def up_many(self, merge_type=None):
+        threads = self.branch.get_loom_state().get_threads()
+        top_thread_name = threads[-1][0]
+        while self.branch.nick != top_thread_name:
+            old_nick = self.branch.nick
+            if self.up_thread(merge_type) != 0:
+                break
+            if len(self.tree.get_parent_ids()) > 1:
+                self.tree.commit('Merge %s into %s' % (old_nick,
+                                                       self.branch.nick))
+
     @needs_write_lock
     def down_thread(self, name=None):
         """Move to a thread down in the loom.




More information about the bazaar-commits mailing list