[patch] storage refactoring ready to merge?

Martin Pool mbp at sourcefrog.net
Thu Jan 12 08:59:58 GMT 2006


Here's an updated version of the "storage" branch, which many people
have had a hand in by now.  I've reviewed it today and made some small
changes and will merge it someone else would like to contribute a +1.

The changes (most from other people) include:

 * add LockableFiles grouping

 * store history in a Repository object rather than in the Branch itself

 * remove bzrlib.clone

 * split @needs_read_lock, @needs_write_lock into bzrlib.decorators

 * add IterableFile

 * remove many interfaces from Branch that don't (or no longer) 
   properly belong there

-- 
Martin
-------------- next part --------------
=== removed file 'bzrlib/clone.py'
--- bzrlib/clone.py	
+++ /dev/null	
@@ -1,162 +0,0 @@
-# Copyright (C) 2004, 2005 by 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
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-"""Make a copy of an entire branch and all its history.
-
-This is the underlying function for the branch/get/clone commands."""
-
-# TODO: This could be done *much* more efficiently by just copying
-# all the whole weaves and revisions, rather than getting one
-# revision at a time.
-
-# TODO: Optionally, after copying, discard any irrelevant information from
-# the destination, such as revisions committed after the last one we're interested 
-# in.  This needs to apply a weave prune operation (not written yet) to each
-# weave one by one.
-
-# Copying must be done in a way that supports http transports, where we
-# can't list a directory, and therefore have to rely on information
-# retrieved from top-level objects whose names we do know.
-#
-# In practice this means we first fetch the revision history and ancestry.
-# These give us a list of all the revisions that need to be fetched.  We 
-# also get the inventory weave.  We then just need to get a list of all 
-# file-ids ever referenced by this tree.  (It might be nice to keep a list
-# of them directly.)  This is done by walking over the inventories of all
-# copied revisions and accumulating a list of file ids.
-#
-# For local branches it is possible to optimize this considerably in two
-# ways.  One is to hardlink the files (if possible and requested), rather
-# than copying them.  Another is to simply list the directory rather than
-# walking through the inventories to find out what files are present -- but
-# there it may be better to just be consistent with remote branches.
-
-import os
-import sys
-
-import bzrlib
-from bzrlib.merge import build_working_dir
-from bzrlib.branch import Branch
-from bzrlib.trace import mutter, note
-from bzrlib.store import copy_all
-from bzrlib.errors import InvalidRevisionId
-
-def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
-    """Copy branch_from into the existing directory to_location.
-
-    Returns the newly created branch object.
-
-    revision
-        If not None, only revisions up to this point will be copied.
-        The head of the new branch will be that revision.  Must be a
-        revid or None.
-
-    to_location -- The destination directory; must either exist and be 
-        empty, or not exist, in which case it is created.
-
-    basis_branch
-        A local branch to copy revisions from, related to branch_from. 
-        This is used when branching from a remote (slow) branch, and we have
-        a local branch that might contain some relevant revisions.
-    """
-    assert isinstance(branch_from, Branch)
-    assert isinstance(to_location, basestring)
-    if basis_branch is not None:
-        note("basis_branch is not supported for fast weave copy yet.")
-    branch_from.lock_read()
-    try:
-        if not (branch_from.weave_store.listable()
-                and branch_from.revision_store.listable()):
-            return copy_branch_slower(branch_from, to_location, revision,
-                                      basis_branch)
-        history = _get_truncated_history(branch_from, revision)
-        if not bzrlib.osutils.lexists(to_location):
-            os.mkdir(to_location)
-        branch_to = Branch.initialize(to_location)
-        mutter("copy branch from %s to %s", branch_from, branch_to)
-        branch_to.working_tree().set_root_id(branch_from.get_root_id())
-        _copy_control_weaves(branch_from, branch_to)
-        _copy_text_weaves(branch_from, branch_to)
-        _copy_revision_store(branch_from, branch_to)
-        branch_to.set_parent(branch_from.base)
-        # must be done *after* history is copied across
-        branch_to.append_revision(*history)
-        build_working_dir(to_location)
-        mutter("copied")
-        return branch_to
-    finally:
-        branch_from.unlock()
-
-
-def _get_truncated_history(branch_from, revision_id):
-    history = branch_from.revision_history()
-    if revision_id is None:
-        return history
-    try:
-        idx = history.index(revision_id)
-    except ValueError:
-        raise InvalidRevisionId(revision_id=revision, branch=branch_from)
-    return history[:idx+1]
-
-def _copy_text_weaves(branch_from, branch_to):
-    copy_all(branch_from.weave_store, branch_to.weave_store)
-
-
-def _copy_revision_store(branch_from, branch_to):
-    copy_all(branch_from.revision_store, branch_to.revision_store)
-
-
-def _copy_control_weaves(branch_from, branch_to):
-    to_control = branch_to.control_weaves
-    from_control = branch_from.control_weaves
-    to_control.copy_multi(from_control, ['inventory'])
-
-    
-def copy_branch_slower(branch_from, to_location, revision=None, basis_branch=None):
-    """Copy branch_from into the existing directory to_location.
-
-    revision
-        If not None, only revisions up to this point will be copied.
-        The head of the new branch will be that revision.  Must be a
-        revid or None.
-
-    to_location -- The destination directory; must either exist and be 
-        empty, or not exist, in which case it is created.
-
-    revno
-        The revision to copy up to
-
-    basis_branch
-        A local branch to copy revisions from, related to branch_from. 
-        This is used when branching from a remote (slow) branch, and we have
-        a local branch that might contain some relevant revisions.
-    """
-    assert isinstance(branch_from, Branch)
-    assert isinstance(to_location, basestring)
-    if not bzrlib.osutils.lexists(to_location):
-        os.mkdir(to_location)
-    br_to = Branch.initialize(to_location)
-    mutter("copy branch from %s to %s", branch_from, br_to)
-    if basis_branch is not None:
-        basis_branch.push_stores(br_to)
-    br_to.working_tree().set_root_id(branch_from.get_root_id())
-    if revision is None:
-        revision = branch_from.last_revision()
-    br_to.update_revisions(branch_from, stop_revision=revision)
-    build_working_dir(to_location)
-    br_to.set_parent(branch_from.base)
-    mutter("copied")
-    return br_to

=== added file 'bzrlib/decorators.py'
--- /dev/null	
+++ bzrlib/decorators.py	
@@ -0,0 +1,49 @@
+# Copyright (C) 2005 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+def needs_read_lock(unbound):
+    """Decorate unbound to take out and release a read lock.
+
+    This decorator can be applied to methods of any class with lock_read() and
+    unlock() methods.
+    
+    Typical usage:
+        
+    class Branch(...):
+        @needs_read_lock
+        def branch_method(self, ...):
+            stuff
+    """
+    def decorated(self, *args, **kwargs):
+        self.lock_read()
+        try:
+            return unbound(self, *args, **kwargs)
+        finally:
+            self.unlock()
+    return decorated
+
+
+def needs_write_lock(unbound):
+    """Decorate unbound to take out and release a write lock."""
+    def decorated(self, *args, **kwargs):
+        self.lock_write()
+        try:
+            return unbound(self, *args, **kwargs)
+        finally:
+            self.unlock()
+    return decorated
+

=== added file 'bzrlib/iterablefile.py'
--- /dev/null	
+++ bzrlib/iterablefile.py	
@@ -0,0 +1,256 @@
+# Copyright (C) 2005 Aaron Bentley
+# <aaron.bentley at utoronto.ca>
+#
+#    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
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import doctest
+
+class IterableFileBase(object):
+    """Create a file-like object from any iterable"""
+    def __init__(self, iterable):
+        object.__init__(self)
+        self._iter = iterable.__iter__()
+        self._buffer = ""
+        self.done = False
+
+    def read_n(self, length):
+        """
+        >>> IterableFileBase(['This ', 'is ', 'a ', 'test.']).read_n(8)
+        'This is '
+        """
+        def test_length(result):
+            if len(result) >= length:
+                return length
+            else:
+                return None
+        return self._read(test_length)
+
+    def read_to(self, sequence, length=None):
+        """
+        >>> f = IterableFileBase(['Th\\nis ', 'is \\n', 'a ', 'te\\nst.'])
+        >>> f.read_to('\\n')
+        'Th\\n'
+        >>> f.read_to('\\n')
+        'is is \\n'
+        """
+        def test_contents(result):
+            if length is not None:
+                if len(result) >= length:
+                    return length
+            try:
+                return result.index(sequence)+len(sequence)
+            except ValueError:
+                return None
+        return self._read(test_contents)
+
+    def _read(self, result_length):
+        """
+        Read data until result satisfies the condition result_length.
+        result_length is a callable that returns None until the condition
+        is satisfied, and returns the length of the result to use when
+        the condition is satisfied.  (i.e. it returns the length of the
+        subset of the first condition match.)
+        """
+        result = self._buffer
+        while result_length(result) is None:
+            try:
+                result += self._iter.next()
+            except StopIteration:
+                self.done = True
+                self._buffer = ""
+                return result
+        output_length = result_length(result)
+        self._buffer = result[output_length:]
+        return result[:output_length]
+
+    def read_all(self):
+        """
+        >>> IterableFileBase(['This ', 'is ', 'a ', 'test.']).read_all()
+        'This is a test.'
+        """
+        def no_stop(result):
+            return None
+        return self._read(no_stop)
+
+
+    def push_back(self, contents):
+        """
+        >>> f = IterableFileBase(['Th\\nis ', 'is \\n', 'a ', 'te\\nst.'])
+        >>> f.read_to('\\n')
+        'Th\\n'
+        >>> f.push_back("Sh")
+        >>> f.read_all()
+        'Shis is \\na te\\nst.'
+        """
+        self._buffer = contents + self._buffer
+
+
+class IterableFile(object):
+    """This class supplies all File methods that can be implemented cheaply."""
+    def __init__(self, iterable):
+        object.__init__(self)
+        self._file_base = IterableFileBase(iterable)
+        self._iter = self._make_iterator()
+        self._closed = False
+        self.softspace = 0
+
+    def _make_iterator(self):
+        while not self._file_base.done:
+            self._check_closed()
+            result = self._file_base.read_to('\n')
+            if result != '':
+                yield result
+
+    def _check_closed(self):
+        if self.closed:
+            raise ValueError("File is closed.")
+
+    def close(self):
+        """
+        >>> f = IterableFile(['This ', 'is ', 'a ', 'test.'])
+        >>> f.closed
+        False
+        >>> f.close()
+        >>> f.closed
+        True
+        """
+        self._file_base.done = True
+        self._closed = True
+
+    closed = property(lambda x: x._closed)
+
+    def flush(self):
+        """No-op for standard compliance.
+        >>> f = IterableFile([])
+        >>> f.close()
+        >>> f.flush()
+        Traceback (most recent call last):
+        ValueError: File is closed.
+        """
+        self._check_closed()
+
+    def next(self):
+        """Implementation of the iterator protocol's next()
+
+        >>> f = IterableFile(['This \\n', 'is ', 'a ', 'test.'])
+        >>> f.next()
+        'This \\n'
+        >>> f.close()
+        >>> f.next()
+        Traceback (most recent call last):
+        ValueError: File is closed.
+        >>> f = IterableFile(['This \\n', 'is ', 'a ', 'test.\\n'])
+        >>> f.next()
+        'This \\n'
+        >>> f.next()
+        'is a test.\\n'
+        >>> f.next()
+        Traceback (most recent call last):
+        StopIteration
+        """
+        self._check_closed()
+        return self._iter.next()
+
+    def __iter__(self):
+        """
+        >>> list(IterableFile(['Th\\nis ', 'is \\n', 'a ', 'te\\nst.']))
+        ['Th\\n', 'is is \\n', 'a te\\n', 'st.']
+        >>> f = IterableFile(['Th\\nis ', 'is \\n', 'a ', 'te\\nst.'])
+        >>> f.close()
+        >>> list(f)
+        Traceback (most recent call last):
+        ValueError: File is closed.
+        """
+        return self
+
+    def read(self, length=None):
+        """
+        >>> IterableFile(['This ', 'is ', 'a ', 'test.']).read()
+        'This is a test.'
+        >>> f = IterableFile(['This ', 'is ', 'a ', 'test.'])
+        >>> f.read(10)
+        'This is a '
+        >>> f = IterableFile(['This ', 'is ', 'a ', 'test.'])
+        >>> f.close()
+        >>> f.read(10)
+        Traceback (most recent call last):
+        ValueError: File is closed.
+        """
+        self._check_closed()
+        if length is None:
+            return self._file_base.read_all()
+        else:
+            return self._file_base.read_n(length)
+
+    def read_to(self, sequence, size=None):
+        """
+        Read characters until a sequence is found, with optional max size.
+        The specified sequence, if found, will be included in the result
+
+        >>> f = IterableFile(['Th\\nis ', 'is \\n', 'a ', 'te\\nst.'])
+        >>> f.read_to('i')
+        'Th\\ni'
+        >>> f.read_to('i')
+        's i'
+        >>> f.close()
+        >>> f.read_to('i')
+        Traceback (most recent call last):
+        ValueError: File is closed.
+        """
+        self._check_closed()
+        return self._file_base.read_to(sequence, size)
+
+    def readline(self, size=None):
+        """
+        >>> f = IterableFile(['Th\\nis ', 'is \\n', 'a ', 'te\\nst.'])
+        >>> f.readline()
+        'Th\\n'
+        >>> f.readline(4)
+        'is i'
+        >>> f.close()
+        >>> f.readline()
+        Traceback (most recent call last):
+        ValueError: File is closed.
+        """
+        return self.read_to('\n', size)
+
+    def readlines(self, sizehint=None):
+        """
+        >>> f = IterableFile(['Th\\nis ', 'is \\n', 'a ', 'te\\nst.'])
+        >>> f.readlines()
+        ['Th\\n', 'is is \\n', 'a te\\n', 'st.']
+        >>> f = IterableFile(['Th\\nis ', 'is \\n', 'a ', 'te\\nst.'])
+        >>> f.close()
+        >>> f.readlines()
+        Traceback (most recent call last):
+        ValueError: File is closed.
+        """
+        lines = []
+        while True:
+            line = self.readline()
+            if line == "":
+                return lines
+            if sizehint is None:
+                lines.append(line)
+            elif len(line) < sizehint:
+                lines.append(line)
+                sizehint -= len(line)
+            else:
+                self._file_base.push_back(line)
+                return lines
+
+        
+if __name__ == "__main__":
+    doctest.testmod()

=== added file 'bzrlib/lockable_files.py'
--- /dev/null	
+++ bzrlib/lockable_files.py	
@@ -0,0 +1,233 @@
+# Copyright (C) 2005 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+from osutils import file_iterator
+
+import bzrlib
+import bzrlib.errors as errors
+from bzrlib.errors import LockError, ReadOnlyError
+from bzrlib.trace import mutter
+import bzrlib.transactions as transactions
+
+class LockableFiles(object):
+    """Object representing a set of files locked within the same scope
+
+    _lock_mode
+        None, or 'r' or 'w'
+
+    _lock_count
+        If _lock_mode is true, a positive count of the number of times the
+        lock has been taken *by this process*.  Others may have compatible 
+        read locks.
+
+    _lock
+        Lock object from bzrlib.lock.
+    """
+
+    _lock_mode = None
+    _lock_count = None
+    _lock = None
+    # If set to False (by a plugin, etc) BzrBranch will not set the
+    # mode on created files or directories
+    _set_file_mode = True
+    _set_dir_mode = True
+
+    def __init__(self, transport, lock_name):
+        object.__init__(self)
+        self._transport = transport
+        self.lock_name = lock_name
+        self._transaction = None
+        self._find_modes()
+
+    def __del__(self):
+        if self._lock_mode or self._lock:
+            # XXX: This should show something every time, and be suitable for
+            # headless operation and embedding
+            from warnings import warn
+            warn("file group %r was not explicitly unlocked" % self)
+            self._lock.unlock()
+
+    def _escape(self, file_or_path):
+        if not isinstance(file_or_path, basestring):
+            file_or_path = '/'.join(file_or_path)
+        if file_or_path == '':
+            return u''
+        return bzrlib.transport.urlescape(unicode(file_or_path))
+
+    def _find_modes(self):
+        """Determine the appropriate modes for files and directories."""
+        try:
+            try:
+                st = self._transport.stat(u'.')
+            except errors.NoSuchFile:
+                # The .bzr/ directory doesn't exist, try to
+                # inherit the permissions from the parent directory
+                # but only try 1 level up
+                temp_transport = self._transport.clone('..')
+                st = temp_transport.stat(u'.')
+        except (errors.TransportNotPossible, errors.NoSuchFile):
+            self._dir_mode = 0755
+            self._file_mode = 0644
+        else:
+            self._dir_mode = st.st_mode & 07777
+            # Remove the sticky and execute bits for files
+            self._file_mode = self._dir_mode & ~07111
+        if not self._set_dir_mode:
+            self._dir_mode = None
+        if not self._set_file_mode:
+            self._file_mode = None
+
+    def controlfilename(self, file_or_path):
+        """Return location relative to branch."""
+        return self._transport.abspath(self._escape(file_or_path))
+
+    def controlfile(self, file_or_path, mode='r'):
+        """Open a control file for this branch.
+
+        There are two classes of file in the control directory: text
+        and binary.  binary files are untranslated byte streams.  Text
+        control files are stored with Unix newlines and in UTF-8, even
+        if the platform or locale defaults are different.
+
+        Controlfiles should almost never be opened in write mode but
+        rather should be atomically copied and replaced using atomicfile.
+        """
+        import codecs
+
+        relpath = self._escape(file_or_path)
+        #TODO: codecs.open() buffers linewise, so it was overloaded with
+        # a much larger buffer, do we need to do the same for getreader/getwriter?
+        if mode == 'rb': 
+            return self._transport.get(relpath)
+        elif mode == 'wb':
+            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
+        elif mode == 'r':
+            # XXX: Do we really want errors='replace'?   Perhaps it should be
+            # an error, or at least reported, if there's incorrectly-encoded
+            # data inside a file.
+            # <https://launchpad.net/products/bzr/+bug/3823>
+            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
+        elif mode == 'w':
+            raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
+        else:
+            raise BzrError("invalid controlfile mode %r" % mode)
+
+    def put(self, path, file):
+        """Write a file.
+        
+        :param path: The path to put the file, relative to the .bzr control
+                     directory
+        :param f: A file-like or string object whose contents should be copied.
+        """
+        if not self._lock_mode == 'w':
+            raise ReadOnlyError()
+        self._transport.put(self._escape(path), file, mode=self._file_mode)
+
+    def put_utf8(self, path, file, mode=None):
+        """Write a file, encoding as utf-8.
+
+        :param path: The path to put the file, relative to the .bzr control
+                     directory
+        :param f: A file-like or string object whose contents should be copied.
+        """
+        import codecs
+        from iterablefile import IterableFile
+        ctrl_files = []
+        if hasattr(file, 'read'):
+            iterator = file_iterator(file)
+        else:
+            iterator = file
+        # IterableFile would not be needed if Transport.put took iterables
+        # instead of files.  ADHB 2005-12-25
+        # RBC 20060103 surely its not needed anyway, with codecs transcode
+        # file support ?
+        # JAM 20060103 We definitely don't want encode(..., 'replace')
+        # these are valuable files which should have exact contents.
+        encoded_file = IterableFile(b.encode('utf-8') for b in 
+                                    iterator)
+        self.put(path, encoded_file)
+
+    def lock_write(self):
+        mutter("lock write: %s (%s)", self, self._lock_count)
+        # TODO: Upgrade locking to support using a Transport,
+        # and potentially a remote locking protocol
+        if self._lock_mode:
+            if self._lock_mode != 'w':
+                raise LockError("can't upgrade to a write lock from %r" %
+                                self._lock_mode)
+            self._lock_count += 1
+        else:
+            self._lock = self._transport.lock_write(
+                    self._escape(self.lock_name))
+            self._lock_mode = 'w'
+            self._lock_count = 1
+            self._set_transaction(transactions.PassThroughTransaction())
+
+    def lock_read(self):
+        mutter("lock read: %s (%s)", self, self._lock_count)
+        if self._lock_mode:
+            assert self._lock_mode in ('r', 'w'), \
+                   "invalid lock mode %r" % self._lock_mode
+            self._lock_count += 1
+        else:
+            self._lock = self._transport.lock_read(
+                    self._escape(self.lock_name))
+            self._lock_mode = 'r'
+            self._lock_count = 1
+            self._set_transaction(transactions.ReadOnlyTransaction())
+            # 5K may be excessive, but hey, its a knob.
+            self.get_transaction().set_cache_size(5000)
+                        
+    def unlock(self):
+        mutter("unlock: %s (%s)", self, self._lock_count)
+        if not self._lock_mode:
+            raise LockError('branch %r is not locked' % (self))
+
+        if self._lock_count > 1:
+            self._lock_count -= 1
+        else:
+            self._finish_transaction()
+            self._lock.unlock()
+            self._lock = None
+            self._lock_mode = self._lock_count = None
+
+    def get_transaction(self):
+        """Return the current active transaction.
+
+        If no transaction is active, this returns a passthrough object
+        for which all data is immediately flushed and no caching happens.
+        """
+        if self._transaction is None:
+            return transactions.PassThroughTransaction()
+        else:
+            return self._transaction
+
+    def _set_transaction(self, new_transaction):
+        """Set a new active transaction."""
+        if self._transaction is not None:
+            raise errors.LockError('Branch %s is in a transaction already.' %
+                                   self)
+        self._transaction = new_transaction
+
+    def _finish_transaction(self):
+        """Exit the current transaction."""
+        if self._transaction is None:
+            raise errors.LockError('Branch %s is not in a transaction' %
+                                   self)
+        transaction = self._transaction
+        self._transaction = None
+        transaction.finish()

=== added file 'bzrlib/repository.py'
--- /dev/null	
+++ bzrlib/repository.py	
@@ -0,0 +1,253 @@
+# Copyright (C) 2005 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from cStringIO import StringIO
+
+from bzrlib.lockable_files import LockableFiles
+from bzrlib.tree import EmptyTree
+from bzrlib.revision import NULL_REVISION
+from bzrlib.store import copy_all
+from bzrlib.store.weave import WeaveStore
+from bzrlib.store.text import TextStore
+import bzrlib.xml5
+from bzrlib.tree import RevisionTree
+from bzrlib.errors import InvalidRevisionId
+from bzrlib.testament import Testament
+from bzrlib.decorators import needs_read_lock, needs_write_lock
+
+
+
+class Repository(object):
+    """Repository holding history for one or more branches.
+
+    The repository holds and retrieves historical information including
+    revisions and file history.  It's normally accessed only by the Branch,
+    which views a particular line of development through that history.
+
+    The Repository builds on top of Stores and a Transport, which respectively 
+    describe the disk data format and the way of accessing the (possibly 
+    remote) disk.
+    """
+
+    def __init__(self, transport, branch_format):
+        object.__init__(self)
+        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
+
+        dir_mode = self.control_files._dir_mode
+        file_mode = self.control_files._file_mode
+
+        def get_weave(name, prefixed=False):
+            if name:
+                name = bzrlib.BZRDIR + '/' + unicode(name)
+            else:
+                name = bzrlib.BZRDIR
+            relpath = self.control_files._escape(name)
+            weave_transport = transport.clone(relpath)
+            ws = WeaveStore(weave_transport, prefixed=prefixed,
+                            dir_mode=dir_mode,
+                            file_mode=file_mode)
+            if self.control_files._transport.should_cache():
+                ws.enable_cache = True
+            return ws
+
+        def get_store(name, compressed=True, prefixed=False):
+            # FIXME: This approach of assuming stores are all entirely compressed
+            # or entirely uncompressed is tidy, but breaks upgrade from 
+            # some existing branches where there's a mixture; we probably 
+            # still want the option to look for both.
+            if name:
+                name = bzrlib.BZRDIR + '/' + unicode(name)
+            else:
+                name = bzrlib.BZRDIR
+            relpath = self.control_files._escape(name)
+            store = TextStore(transport.clone(relpath),
+                              prefixed=prefixed, compressed=compressed,
+                              dir_mode=dir_mode,
+                              file_mode=file_mode)
+            #if self._transport.should_cache():
+            #    cache_path = os.path.join(self.cache_root, name)
+            #    os.mkdir(cache_path)
+            #    store = bzrlib.store.CachedStore(store, cache_path)
+            return store
+
+        if branch_format == 4:
+            self.inventory_store = get_store('inventory-store')
+            self.text_store = get_store('text-store')
+            self.revision_store = get_store('revision-store')
+        elif branch_format == 5:
+            self.control_weaves = get_weave('')
+            self.weave_store = get_weave('weaves')
+            self.revision_store = get_store('revision-store', compressed=False)
+        elif branch_format == 6:
+            self.control_weaves = get_weave('')
+            self.weave_store = get_weave('weaves', prefixed=True)
+            self.revision_store = get_store('revision-store', compressed=False,
+                                            prefixed=True)
+        self.revision_store.register_suffix('sig')
+
+    def lock_write(self):
+        self.control_files.lock_write()
+
+    def lock_read(self):
+        self.control_files.lock_read()
+
+    def unlock(self):
+        self.control_files.unlock()
+
+    def copy(self, destination):
+        destination.control_weaves.copy_multi(self.control_weaves, 
+                ['inventory'])
+        copy_all(self.weave_store, destination.weave_store)
+        copy_all(self.revision_store, destination.revision_store)
+
+    def has_revision(self, revision_id):
+        """True if this branch has a copy of the revision.
+
+        This does not necessarily imply the revision is merge
+        or on the mainline."""
+        return (revision_id is None
+                or self.revision_store.has_id(revision_id))
+
+    @needs_read_lock
+    def get_revision_xml_file(self, revision_id):
+        """Return XML file object for revision object."""
+        if not revision_id or not isinstance(revision_id, basestring):
+            raise InvalidRevisionId(revision_id=revision_id, branch=self)
+        try:
+            return self.revision_store.get(revision_id)
+        except (IndexError, KeyError):
+            raise bzrlib.errors.NoSuchRevision(self, revision_id)
+
+    def get_revision_xml(self, revision_id):
+        return self.get_revision_xml_file(revision_id).read()
+
+    def get_revision(self, revision_id):
+        """Return the Revision object for a named revision"""
+        xml_file = self.get_revision_xml_file(revision_id)
+
+        try:
+            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
+        except SyntaxError, e:
+            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
+                                         [revision_id,
+                                          str(e)])
+            
+        assert r.revision_id == revision_id
+        return r
+
+    def get_revision_sha1(self, revision_id):
+        """Hash the stored value of a revision, and return it."""
+        # In the future, revision entries will be signed. At that
+        # point, it is probably best *not* to include the signature
+        # in the revision hash. Because that lets you re-sign
+        # the revision, (add signatures/remove signatures) and still
+        # have all hash pointers stay consistent.
+        # But for now, just hash the contents.
+        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
+
+    @needs_write_lock
+    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
+        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
+                                revision_id, "sig")
+
+    def get_inventory_weave(self):
+        return self.control_weaves.get_weave('inventory',
+            self.get_transaction())
+
+    def get_inventory(self, revision_id):
+        """Get Inventory object by hash."""
+        xml = self.get_inventory_xml(revision_id)
+        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
+
+    def get_inventory_xml(self, revision_id):
+        """Get inventory XML as a file object."""
+        try:
+            assert isinstance(revision_id, basestring), type(revision_id)
+            iw = self.get_inventory_weave()
+            return iw.get_text(iw.lookup(revision_id))
+        except IndexError:
+            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
+
+    def get_inventory_sha1(self, revision_id):
+        """Return the sha1 hash of the inventory entry
+        """
+        return self.get_revision(revision_id).inventory_sha1
+
+    def get_revision_inventory(self, revision_id):
+        """Return inventory of a past revision."""
+        # TODO: Unify this with get_inventory()
+        # bzr 0.0.6 and later imposes the constraint that the inventory_id
+        # must be the same as its revision, so this is trivial.
+        if revision_id == None:
+            # This does not make sense: if there is no revision,
+            # then it is the current tree inventory surely ?!
+            # and thus get_root_id() is something that looks at the last
+            # commit on the branch, and the get_root_id is an inventory check.
+            raise NotImplementedError
+            # return Inventory(self.get_root_id())
+        else:
+            return self.get_inventory(revision_id)
+
+    def revision_tree(self, revision_id):
+        """Return Tree for a revision on this branch.
+
+        `revision_id` may be None for the null revision, in which case
+        an `EmptyTree` is returned."""
+        # TODO: refactor this to use an existing revision object
+        # so we don't need to read it in twice.
+        if revision_id == None or revision_id == NULL_REVISION:
+            return EmptyTree()
+        else:
+            inv = self.get_revision_inventory(revision_id)
+            return RevisionTree(self, inv, revision_id)
+
+    def get_ancestry(self, revision_id):
+        """Return a list of revision-ids integrated by a revision.
+        
+        This is topologically sorted.
+        """
+        if revision_id is None:
+            return [None]
+        w = self.get_inventory_weave()
+        return [None] + map(w.idx_to_name,
+                            w.inclusions([w.lookup(revision_id)]))
+
+    @needs_read_lock
+    def print_file(self, file, revision_id):
+        """Print `file` to stdout."""
+        tree = self.revision_tree(revision_id)
+        # use inventory as it was in that revision
+        file_id = tree.inventory.path2id(file)
+        if not file_id:
+            raise BzrError("%r is not present in revision %s" % (file, revno))
+            try:
+                revno = self.revision_id_to_revno(revision_id)
+            except errors.NoSuchRevision:
+                # TODO: This should not be BzrError,
+                # but NoSuchFile doesn't fit either
+                raise BzrError('%r is not present in revision %s' 
+                                % (file, revision_id))
+            else:
+                raise BzrError('%r is not present in revision %s'
+                                % (file, revno))
+        tree.print_file(file_id)
+
+    def get_transaction(self):
+        return self.control_files.get_transaction()
+
+    def sign_revision(self, revision_id, gpg_strategy):
+        plaintext = Testament.from_revision(self, revision_id).as_short_text()
+        self.store_revision_signature(gpg_strategy, plaintext, revision_id)

=== added file 'bzrlib/tests/test_lockable_files.py'
--- /dev/null	
+++ bzrlib/tests/test_lockable_files.py	
@@ -0,0 +1,85 @@
+# Copyright (C) 2005 by 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
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from StringIO import StringIO
+
+from bzrlib.branch import Branch
+from bzrlib.errors import NoSuchFile, ReadOnlyError
+from bzrlib.lockable_files import LockableFiles
+from bzrlib.tests import TestCaseInTempDir
+from bzrlib.transactions import PassThroughTransaction, ReadOnlyTransaction
+from bzrlib.transport import get_transport
+
+class TestLockableFiles(TestCaseInTempDir):
+
+    def setUp(self):
+        super(TestLockableFiles, self).setUp()
+        transport = get_transport('.')
+        transport.mkdir('.bzr')
+        transport.put('.bzr/my-lock', StringIO(''))
+        self.lockable = LockableFiles(transport.clone('.bzr'), 'my-lock')
+
+    def test_read_write(self):
+        self.assertRaises(NoSuchFile, self.lockable.controlfile, 'foo')
+        self.lockable.lock_write()
+        try:
+            unicode_string = u'bar\u1234'
+            self.assertEqual(4, len(unicode_string))
+            byte_string = unicode_string.encode('utf-8')
+            self.assertEqual(6, len(byte_string))
+            self.assertRaises(UnicodeEncodeError, self.lockable.put, 'foo', 
+                              StringIO(unicode_string))
+            self.lockable.put('foo', StringIO(byte_string))
+            self.assertEqual(byte_string,
+                             self.lockable.controlfile('foo', 'rb').read())
+            self.assertEqual(unicode_string,
+                             self.lockable.controlfile('foo', 'r').read())
+            self.lockable.put_utf8('bar', StringIO(unicode_string))
+            self.assertEqual(unicode_string, 
+                             self.lockable.controlfile('bar', 'r').read())
+            self.assertEqual(byte_string, 
+                             self.lockable.controlfile('bar', 'rb').read())
+        finally:
+            self.lockable.unlock()
+
+    def test_locks(self):
+        self.lockable.lock_read()
+        self.lockable.unlock()
+        self.assertRaises(ReadOnlyError, self.lockable.put, 'foo', 
+                          StringIO('bar\u1234'))
+
+    def test_transactions(self):
+        self.assertIs(self.lockable.get_transaction().__class__,
+                      PassThroughTransaction)
+        self.lockable.lock_read()
+        try:
+            self.assertIs(self.lockable.get_transaction().__class__,
+                          ReadOnlyTransaction)
+        finally:
+            self.lockable.unlock()
+        self.assertIs(self.lockable.get_transaction().__class__,
+                      PassThroughTransaction)
+        self.lockable.lock_write()
+        self.assertIs(self.lockable.get_transaction().__class__,
+                      PassThroughTransaction)
+        self.lockable.unlock()
+
+    def test__escape(self):
+        self.assertEqual('%25', self.lockable._escape('%'))
+        
+    def test__escape_empty(self):
+        self.assertEqual('', self.lockable._escape(''))
+

=== modified file 'BRANCH.TODO'
--- BRANCH.TODO	
+++ BRANCH.TODO	
@@ -1,3 +1,14 @@
 # This file is for listing TODOs for branches that are being worked on.
 # It should ALWAYS be empty in the mainline or in integration branches.
-# 
+
+The purpose of this branch is to separate the storage of history within a
+branch from the logical identity of the branch itself (being the tip
+pointer and revision-history).
+
+The major refactoring here is that historical data (revisions, file
+weaves, etc) are stored not in the Branch itself but in
+`branch.repository`, which is a `Repository` object.
+
+All the immediately accessible history for a Branch is stored in its
+repository, but the repository might hold information on multiple
+branches, to implement what's called "shared storage".

=== modified file 'NEWS'
--- NEWS	
+++ NEWS	
@@ -274,6 +274,12 @@
     * Simplify handling of DivergedBranches in cmd_pull().
       (Michael Ellerman)
 		   
+   
+    * Branch.controlfile* logic has moved to lockablefiles.LockableFiles, which
+      is exposed as Branch().control_files. Also this has been altered with the
+      controlfile pre/suffix replaced by simple method names like 'get' and
+      'put'. (Aaron Bentley, Robert Collins).
+
     * Deprecated functions and methods can now be marked as such using the 
       bzrlib.symbol_versioning module. Marked method have their docstring
       updated and will issue a DeprecationWarning using the warnings module

=== modified file 'bzrlib/annotate.py'
--- bzrlib/annotate.py	
+++ bzrlib/annotate.py	
@@ -55,7 +55,8 @@
 def _annotate_file(branch, rev_id, file_id ):
 
     rh = branch.revision_history()
-    w = branch.weave_store.get_weave(file_id, branch.get_transaction())
+    w = branch.repository.weave_store.get_weave(file_id, 
+        branch.repository.get_transaction())
     last_origin = None
     for origin, text in w.annotate_iter(rev_id):
         text = text.rstrip('\r\n')
@@ -64,14 +65,14 @@
         else:
             last_origin = origin
             line_rev_id = w.idx_to_name(origin)
-            if not branch.has_revision(line_rev_id):
+            if not branch.repository.has_revision(line_rev_id):
                 (revno_str, author, date_str) = ('?','?','?')
             else:
                 if line_rev_id in rh:
                     revno_str = str(rh.index(line_rev_id) + 1)
                 else:
                     revno_str = 'merge'
-            rev = branch.get_revision(line_rev_id)
+            rev = branch.repository.get_revision(line_rev_id)
             tz = rev.timezone or 0
             date_str = time.strftime('%Y%m%d', 
                                      time.gmtime(rev.timestamp + tz))

=== modified file 'bzrlib/branch.py'
--- bzrlib/branch.py	
+++ bzrlib/branch.py	
@@ -24,7 +24,6 @@
 
 
 import bzrlib
-import bzrlib.inventory as inventory
 from bzrlib.trace import mutter, note
 from bzrlib.osutils import (isdir, quotefn,
                             rename, splitpath, sha_file,
@@ -36,60 +35,43 @@
                            UnlistableBranch, NoSuchFile, NotVersionedError,
                            NoWorkingTree)
 from bzrlib.textui import show_status
-from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
-                             NULL_REVISION)
-
+from bzrlib.config import TreeConfig
 from bzrlib.delta import compare_trees
-from bzrlib.tree import EmptyTree, RevisionTree
+import bzrlib.inventory as inventory
 from bzrlib.inventory import Inventory
+from bzrlib.lockable_files import LockableFiles
+from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions)
+from bzrlib.repository import Repository
 from bzrlib.store import copy_all
-from bzrlib.store.text import TextStore
-from bzrlib.store.weave import WeaveStore
-from bzrlib.testament import Testament
 import bzrlib.transactions as transactions
 from bzrlib.transport import Transport, get_transport
+from bzrlib.tree import EmptyTree, RevisionTree
+import bzrlib.ui
 import bzrlib.xml5
-import bzrlib.ui
-from config import TreeConfig
+from bzrlib.decorators import needs_read_lock, needs_write_lock
 
 
 BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
-## TODO: Maybe include checks for common corruption of newlines, etc?
-
+
+
+# TODO: Maybe include checks for common corruption of newlines, etc?
 
 # TODO: Some operations like log might retrieve the same revisions
 # repeatedly to calculate deltas.  We could perhaps have a weakref
 # cache in memory to make this faster.  In general anything can be
 # cached in memory between lock and unlock operations.
 
+# FIXME: At the moment locking the Branch locks both the repository and the
+# control files, representing the two aspects currently controlled by one
+# object.  However, they currently both map to the same lockfile. 
+
 def find_branch(*ignored, **ignored_too):
     # XXX: leave this here for about one release, then remove it
     raise NotImplementedError('find_branch() is not supported anymore, '
                               'please use one of the new branch constructors')
 
-
-def needs_read_lock(unbound):
-    """Decorate unbound to take out and release a read lock."""
-    def decorated(self, *args, **kwargs):
-        self.lock_read()
-        try:
-            return unbound(self, *args, **kwargs)
-        finally:
-            self.unlock()
-    return decorated
-
-
-def needs_write_lock(unbound):
-    """Decorate unbound to take out and release a write lock."""
-    def decorated(self, *args, **kwargs):
-        self.lock_write()
-        try:
-            return unbound(self, *args, **kwargs)
-        finally:
-            self.unlock()
-    return decorated
 
 ######################################################################
 # branch objects
@@ -144,7 +126,7 @@
     @staticmethod
     def initialize(base):
         """Create a new branch, rooted at 'base' (url)"""
-        t = get_transport(base)
+        t = get_transport(unicode(base))
         return BzrBranch(t, init=True)
 
     def setup_caching(self, cache_root):
@@ -168,14 +150,6 @@
         """Copy the content of this branches store to branch_to."""
         raise NotImplementedError('push_stores is abstract')
 
-    def get_transaction(self):
-        """Return the current active transaction.
-
-        If no transaction is active, this returns a passthrough object
-        for which all data is immediately flushed and no caching happens.
-        """
-        raise NotImplementedError('get_transaction is abstract')
-
     def lock_write(self):
         raise NotImplementedError('lock_write is abstract')
         
@@ -185,6 +159,10 @@
     def unlock(self):
         raise NotImplementedError('unlock is abstract')
 
+    def peek_lock_mode(self):
+        """Return lock mode for the Branch: 'r', 'w' or None"""
+        raise NotImplementedError(self.is_locked)
+
     def abspath(self, name):
         """Return absolute filename for something in the branch
         
@@ -192,50 +170,11 @@
         method and not a tree method.
         """
         raise NotImplementedError('abspath is abstract')
-
-    def controlfilename(self, file_or_path):
-        """Return location relative to branch."""
-        raise NotImplementedError('controlfilename is abstract')
-
-    def controlfile(self, file_or_path, mode='r'):
-        """Open a control file for this branch.
-
-        There are two classes of file in the control directory: text
-        and binary.  binary files are untranslated byte streams.  Text
-        control files are stored with Unix newlines and in UTF-8, even
-        if the platform or locale defaults are different.
-
-        Controlfiles should almost never be opened in write mode but
-        rather should be atomically copied and replaced using atomicfile.
-        """
-        raise NotImplementedError('controlfile is abstract')
-
-    def put_controlfile(self, path, f, encode=True):
-        """Write an entry as a controlfile.
-
-        :param path: The path to put the file, relative to the .bzr control
-                     directory
-        :param f: A file-like or string object whose contents should be copied.
-        :param encode:  If true, encode the contents as utf-8
-        """
-        raise NotImplementedError('put_controlfile is abstract')
-
-    def put_controlfiles(self, files, encode=True):
-        """Write several entries as controlfiles.
-
-        :param files: A list of [(path, file)] pairs, where the path is the directory
-                      underneath the bzr control directory
-        :param encode:  If true, encode the contents as utf-8
-        """
-        raise NotImplementedError('put_controlfiles is abstract')
 
     def get_root_id(self):
         """Return the id of this branches root"""
         raise NotImplementedError('get_root_id is abstract')
 
-    def set_root_id(self, file_id):
-        raise NotImplementedError('set_root_id is abstract')
-
     def print_file(self, file, revision_id):
         """Print `file` to stdout."""
         raise NotImplementedError('print_file is abstract')
@@ -245,69 +184,6 @@
 
     def set_revision_history(self, rev_history):
         raise NotImplementedError('set_revision_history is abstract')
-
-    def has_revision(self, revision_id):
-        """True if this branch has a copy of the revision.
-
-        This does not necessarily imply the revision is merge
-        or on the mainline."""
-        raise NotImplementedError('has_revision is abstract')
-
-    def get_revision_xml(self, revision_id):
-        raise NotImplementedError('get_revision_xml is abstract')
-
-    def get_revision(self, revision_id):
-        """Return the Revision object for a named revision"""
-        raise NotImplementedError('get_revision is abstract')
-
-    def get_revision_delta(self, revno):
-        """Return the delta for one revision.
-
-        The delta is relative to its mainline predecessor, or the
-        empty tree for revision 1.
-        """
-        assert isinstance(revno, int)
-        rh = self.revision_history()
-        if not (1 <= revno <= len(rh)):
-            raise InvalidRevisionNumber(revno)
-
-        # revno is 1-based; list is 0-based
-
-        new_tree = self.revision_tree(rh[revno-1])
-        if revno == 1:
-            old_tree = EmptyTree()
-        else:
-            old_tree = self.revision_tree(rh[revno-2])
-
-        return compare_trees(old_tree, new_tree)
-
-    def get_revision_sha1(self, revision_id):
-        """Hash the stored value of a revision, and return it."""
-        raise NotImplementedError('get_revision_sha1 is abstract')
-
-    def get_ancestry(self, revision_id):
-        """Return a list of revision-ids integrated by a revision.
-        
-        This currently returns a list, but the ordering is not guaranteed:
-        treat it as a set.
-        """
-        raise NotImplementedError('get_ancestry is abstract')
-
-    def get_inventory(self, revision_id):
-        """Get Inventory object by hash."""
-        raise NotImplementedError('get_inventory is abstract')
-
-    def get_inventory_xml(self, revision_id):
-        """Get inventory XML as a file object."""
-        raise NotImplementedError('get_inventory_xml is abstract')
-
-    def get_inventory_sha1(self, revision_id):
-        """Return the sha1 hash of the inventory entry."""
-        raise NotImplementedError('get_inventory_sha1 is abstract')
-
-    def get_revision_inventory(self, revision_id):
-        """Return inventory of a past revision."""
-        raise NotImplementedError('get_revision_inventory is abstract')
 
     def revision_history(self):
         """Return sequence of revision hashes on to this branch."""
@@ -373,6 +249,7 @@
             if stop_revision > other_len:
                 raise bzrlib.errors.NoSuchRevision(self, stop_revision)
         return other_history[self_len:stop_revision]
+
     
     def update_revisions(self, other, stop_revision=None):
         """Pull in new perfect-fit revisions."""
@@ -401,13 +278,6 @@
             raise bzrlib.errors.NoSuchRevision(self, revno)
         return history[revno - 1]
 
-    def revision_tree(self, revision_id):
-        """Return Tree for a revision on this branch.
-
-        `revision_id` may be None for the null revision, in which case
-        an `EmptyTree` is returned."""
-        raise NotImplementedError('revision_tree is abstract')
-
     def working_tree(self):
         """Return a `Tree` for the working copy if this is a local branch."""
         raise NotImplementedError('working_tree is abstract')
@@ -420,7 +290,7 @@
 
         If there are no revisions yet, return an `EmptyTree`.
         """
-        return self.revision_tree(self.last_revision())
+        return self.repository.revision_tree(self.last_revision())
 
     def rename_one(self, from_rel, to_rel):
         """Rename one file.
@@ -487,6 +357,47 @@
     def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
         raise NotImplementedError('store_revision_signature is abstract')
 
+    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
+        """Copy this branch into the existing directory to_location.
+
+        Returns the newly created branch object.
+
+        revision
+            If not None, only revisions up to this point will be copied.
+            The head of the new branch will be that revision.  Must be a
+            revid or None.
+    
+        to_location -- The destination directory; must either exist and be 
+            empty, or not exist, in which case it is created.
+    
+        basis_branch
+            A local branch to copy revisions from, related to this branch. 
+            This is used when branching from a remote (slow) branch, and we have
+            a local branch that might contain some relevant revisions.
+    
+        to_branch_type
+            Branch type of destination branch
+        """
+        assert isinstance(to_location, basestring)
+        if not bzrlib.osutils.lexists(to_location):
+            os.mkdir(to_location)
+        if to_branch_type is None:
+            to_branch_type = BzrBranch
+        br_to = to_branch_type.initialize(to_location)
+        mutter("copy branch from %s to %s", self, br_to)
+        if basis_branch is not None:
+            basis_branch.push_stores(br_to)
+        br_to.working_tree().set_root_id(self.get_root_id())
+        if revision is None:
+            revision = self.last_revision()
+        br_to.update_revisions(self, stop_revision=revision)
+        br_to.set_parent(self.base)
+        # circular import protection
+        from bzrlib.merge import build_working_dir
+        build_working_dir(to_location)
+        mutter("copied")
+        return br_to
+
 class BzrBranch(Branch):
     """A branch stored in the actual filesystem.
 
@@ -494,28 +405,12 @@
     really matter if it's on an nfs/smb/afs/coda/... share, as long as
     it's writable, and can be accessed via the normal filesystem API.
 
-    _lock_mode
-        None, or 'r' or 'w'
-
-    _lock_count
-        If _lock_mode is true, a positive count of the number of times the
-        lock has been taken.
-
-    _lock
-        Lock object from bzrlib.lock.
     """
     # We actually expect this class to be somewhat short-lived; part of its
     # purpose is to try to isolate what bits of the branch logic are tied to
     # filesystem access, so that in a later step, we can extricate them to
     # a separarte ("storage") class.
-    _lock_mode = None
-    _lock_count = None
-    _lock = None
     _inventory_weave = None
-    # If set to False (by a plugin, etc) BzrBranch will not set the
-    # mode on created files or directories
-    _set_file_mode = True
-    _set_dir_mode = True
     
     # Map some sort of prefix into a namespace
     # stuff like "revno:10", "revid:", etc.
@@ -562,59 +457,22 @@
         """
         assert isinstance(transport, Transport), \
             "%r is not a Transport" % transport
-        self._transport = transport
+        # TODO: jam 20060103 We create a clone of this transport at .bzr/
+        #       and then we forget about it, should we keep a handle to it?
+        self._base = transport.base
+        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR),
+                                           'branch-lock')
         if init:
             self._make_control()
         self._check_format(relax_version_check)
-        self._find_modes()
-
-        def get_store(name, compressed=True, prefixed=False):
-            relpath = self._rel_controlfilename(unicode(name))
-            store = TextStore(self._transport.clone(relpath),
-                              dir_mode=self._dir_mode,
-                              file_mode=self._file_mode,
-                              prefixed=prefixed,
-                              compressed=compressed)
-            return store
-
-        def get_weave(name, prefixed=False):
-            relpath = self._rel_controlfilename(unicode(name))
-            ws = WeaveStore(self._transport.clone(relpath),
-                            prefixed=prefixed,
-                            dir_mode=self._dir_mode,
-                            file_mode=self._file_mode)
-            if self._transport.should_cache():
-                ws.enable_cache = True
-            return ws
-
-        if self._branch_format == 4:
-            self.inventory_store = get_store('inventory-store')
-            self.text_store = get_store('text-store')
-            self.revision_store = get_store('revision-store')
-        elif self._branch_format == 5:
-            self.control_weaves = get_weave(u'')
-            self.weave_store = get_weave(u'weaves')
-            self.revision_store = get_store(u'revision-store', compressed=False)
-        elif self._branch_format == 6:
-            self.control_weaves = get_weave(u'')
-            self.weave_store = get_weave(u'weaves', prefixed=True)
-            self.revision_store = get_store(u'revision-store', compressed=False,
-                                            prefixed=True)
-        self.revision_store.register_suffix('sig')
-        self._transaction = None
+        self.repository = Repository(transport, self._branch_format)
 
     def __str__(self):
-        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
+        return '%s(%r)' % (self.__class__.__name__, self.base)
 
     __repr__ = __str__
 
     def __del__(self):
-        if self._lock_mode or self._lock:
-            # XXX: This should show something every time, and be suitable for
-            # headless operation and embedding
-            warn("branch %r was not explicitly unlocked" % self)
-            self._lock.unlock()
-
         # TODO: It might be best to do this somewhere else,
         # but it is nice for a Branch object to automatically
         # cache it's information.
@@ -629,151 +487,36 @@
             self.cache_root = None
 
     def _get_base(self):
-        if self._transport:
-            return self._transport.base
-        return None
+        return self._base
 
     base = property(_get_base, doc="The URL for the root of this branch.")
 
     def _finish_transaction(self):
         """Exit the current transaction."""
-        if self._transaction is None:
-            raise errors.LockError('Branch %s is not in a transaction' %
-                                   self)
-        transaction = self._transaction
-        self._transaction = None
-        transaction.finish()
+        return self.control_files._finish_transaction()
 
     def get_transaction(self):
-        """See Branch.get_transaction."""
-        if self._transaction is None:
-            return transactions.PassThroughTransaction()
-        else:
-            return self._transaction
-
-    def _set_transaction(self, new_transaction):
+        """Return the current active transaction.
+
+        If no transaction is active, this returns a passthrough object
+        for which all data is immediately flushed and no caching happens.
+        """
+        # this is an explicit function so that we can do tricky stuff
+        # when the storage in rev_storage is elsewhere.
+        # we probably need to hook the two 'lock a location' and 
+        # 'have a transaction' together more delicately, so that
+        # we can have two locks (branch and storage) and one transaction
+        # ... and finishing the transaction unlocks both, but unlocking
+        # does not. - RBC 20051121
+        return self.control_files.get_transaction()
+
+    def _set_transaction(self, transaction):
         """Set a new active transaction."""
-        if self._transaction is not None:
-            raise errors.LockError('Branch %s is in a transaction already.' %
-                                   self)
-        self._transaction = new_transaction
-
-    def lock_write(self):
-        #mutter("lock write: %s (%s)", self, self._lock_count)
-        # TODO: Upgrade locking to support using a Transport,
-        # and potentially a remote locking protocol
-        if self._lock_mode:
-            if self._lock_mode != 'w':
-                raise LockError("can't upgrade to a write lock from %r" %
-                                self._lock_mode)
-            self._lock_count += 1
-        else:
-            self._lock = self._transport.lock_write(
-                    self._rel_controlfilename('branch-lock'))
-            self._lock_mode = 'w'
-            self._lock_count = 1
-            self._set_transaction(transactions.PassThroughTransaction())
-
-    def lock_read(self):
-        #mutter("lock read: %s (%s)", self, self._lock_count)
-        if self._lock_mode:
-            assert self._lock_mode in ('r', 'w'), \
-                   "invalid lock mode %r" % self._lock_mode
-            self._lock_count += 1
-        else:
-            self._lock = self._transport.lock_read(
-                    self._rel_controlfilename('branch-lock'))
-            self._lock_mode = 'r'
-            self._lock_count = 1
-            self._set_transaction(transactions.ReadOnlyTransaction())
-            # 5K may be excessive, but hey, its a knob.
-            self.get_transaction().set_cache_size(5000)
-                        
-    def unlock(self):
-        #mutter("unlock: %s (%s)", self, self._lock_count)
-        if not self._lock_mode:
-            raise LockError('branch %r is not locked' % (self))
-
-        if self._lock_count > 1:
-            self._lock_count -= 1
-        else:
-            self._finish_transaction()
-            self._lock.unlock()
-            self._lock = None
-            self._lock_mode = self._lock_count = None
+        return self.control_files._set_transaction(transaction)
 
     def abspath(self, name):
         """See Branch.abspath."""
-        return self._transport.abspath(name)
-
-    def _rel_controlfilename(self, file_or_path):
-        if not isinstance(file_or_path, basestring):
-            file_or_path = u'/'.join(file_or_path)
-        if file_or_path == '':
-            return bzrlib.BZRDIR
-        return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
-
-    def controlfilename(self, file_or_path):
-        """See Branch.controlfilename."""
-        return self._transport.abspath(self._rel_controlfilename(file_or_path))
-
-    def controlfile(self, file_or_path, mode='r'):
-        """See Branch.controlfile."""
-        import codecs
-
-        relpath = self._rel_controlfilename(file_or_path)
-        #TODO: codecs.open() buffers linewise, so it was overloaded with
-        # a much larger buffer, do we need to do the same for getreader/getwriter?
-        if mode == 'rb': 
-            return self._transport.get(relpath)
-        elif mode == 'wb':
-            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
-        elif mode == 'r':
-            # XXX: Do we really want errors='replace'?   Perhaps it should be
-            # an error, or at least reported, if there's incorrectly-encoded
-            # data inside a file.
-            # <https://launchpad.net/products/bzr/+bug/3823>
-            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
-        elif mode == 'w':
-            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
-        else:
-            raise BzrError("invalid controlfile mode %r" % mode)
-
-    def put_controlfile(self, path, f, encode=True):
-        """See Branch.put_controlfile."""
-        self.put_controlfiles([(path, f)], encode=encode)
-
-    def put_controlfiles(self, files, encode=True):
-        """See Branch.put_controlfiles."""
-        import codecs
-        ctrl_files = []
-        for path, f in files:
-            if encode:
-                if isinstance(f, basestring):
-                    f = f.encode('utf-8', 'replace')
-                else:
-                    f = codecs.getwriter('utf-8')(f, errors='replace')
-            path = self._rel_controlfilename(path)
-            ctrl_files.append((path, f))
-        self._transport.put_multi(ctrl_files, mode=self._file_mode)
-
-    def _find_modes(self, path=None):
-        """Determine the appropriate modes for files and directories."""
-        try:
-            if path is None:
-                path = self._rel_controlfilename('')
-            st = self._transport.stat(path)
-        except errors.TransportNotPossible:
-            self._dir_mode = 0755
-            self._file_mode = 0644
-        else:
-            self._dir_mode = st.st_mode & 07777
-            # Remove the sticky and execute bits for files
-            self._file_mode = self._dir_mode & ~07111
-        if not self._set_dir_mode:
-            self._dir_mode = None
-        if not self._set_file_mode:
-            self._file_mode = None
+        return self.control_files._transport.abspath(name)
 
     def _make_control(self):
         from bzrlib.inventory import Inventory
@@ -790,11 +533,6 @@
         sio = StringIO()
         bzrlib.weavefile.write_weave_v5(Weave(), sio)
         empty_weave = sio.getvalue()
-
-        cfn = self._rel_controlfilename
-        # Since we don't have a .bzr directory, inherit the
-        # mode from the root directory
-        self._find_modes(u'.')
 
         dirs = ['', 'revision-store', 'weaves']
         files = [('README', 
@@ -809,9 +547,16 @@
             ('inventory.weave', empty_weave),
             ('ancestry.weave', empty_weave)
         ]
-        self._transport.mkdir_multi([cfn(d) for d in dirs], mode=self._dir_mode)
-        self.put_controlfiles(files)
-        mutter('created control directory in ' + self._transport.base)
+        cfe = self.control_files._escape
+        self.control_files._transport.mkdir_multi([cfe(d) for d in dirs],
+                mode=self.control_files._dir_mode)
+        self.control_files.lock_write()
+        try:
+            for file, content in files:
+                self.control_files.put_utf8(file, content)
+            mutter('created control directory in ' + self.base)
+        finally:
+            self.control_files.unlock()
 
     def _check_format(self, relax_version_check):
         """Check this branch format is supported.
@@ -823,7 +568,7 @@
         classes to support downlevel branches.  But not yet.
         """
         try:
-            fmt = self.controlfile('branch-format', 'r').read()
+            fmt = self.control_files.controlfile('branch-format', 'r').read()
         except NoSuchFile:
             raise NotBranchError(path=self.base)
         mutter("got branch format %r", fmt)
@@ -845,27 +590,34 @@
     @needs_read_lock
     def get_root_id(self):
         """See Branch.get_root_id."""
-        inv = self.get_inventory(self.last_revision())
+        inv = self.repository.get_inventory(self.last_revision())
         return inv.root.file_id
+
+    def lock_write(self):
+        # TODO: test for failed two phase locks. This is known broken.
+        self.control_files.lock_write()
+        self.repository.lock_write()
+
+    def lock_read(self):
+        # TODO: test for failed two phase locks. This is known broken.
+        self.control_files.lock_read()
+        self.repository.lock_read()
+
+    def unlock(self):
+        # TODO: test for failed two phase locks. This is known broken.
+        self.repository.unlock()
+        self.control_files.unlock()
+
+    def peek_lock_mode(self):
+        if self.control_files._lock_count == 0:
+            return None
+        else:
+            return self.control_files._lock_mode
 
     @needs_read_lock
     def print_file(self, file, revision_id):
         """See Branch.print_file."""
-        tree = self.revision_tree(revision_id)
-        # use inventory as it was in that revision
-        file_id = tree.inventory.path2id(file)
-        if not file_id:
-            try:
-                revno = self.revision_id_to_revno(revision_id)
-            except errors.NoSuchRevision:
-                # TODO: This should not be BzrError,
-                # but NoSuchFile doesn't fit either
-                raise BzrError('%r is not present in revision %s' 
-                                % (file, revision_id))
-            else:
-                raise BzrError('%r is not present in revision %s'
-                                % (file, revno))
-        tree.print_file(file_id)
+        return self.repository.print_file(file, revision_id)
 
     @needs_write_lock
     def append_revision(self, *revision_ids):
@@ -881,109 +633,47 @@
         """See Branch.set_revision_history."""
         old_revision = self.last_revision()
         new_revision = rev_history[-1]
-        self.put_controlfile('revision-history', '\n'.join(rev_history))
+        self.control_files.put_utf8(
+            'revision-history', '\n'.join(rev_history))
         try:
+            # FIXME: RBC 20051207 this smells wrong, last_revision in the 
+            # working tree may be != to last_revision in the branch - so
+            # why is this passing in the branches last_revision ?
             self.working_tree().set_last_revision(new_revision, old_revision)
         except NoWorkingTree:
             mutter('Unable to set_last_revision without a working tree.')
 
-    def has_revision(self, revision_id):
-        """See Branch.has_revision."""
-        return (revision_id is None
-                or self.revision_store.has_id(revision_id))
-
-    @needs_read_lock
-    def _get_revision_xml_file(self, revision_id):
-        if not revision_id or not isinstance(revision_id, basestring):
-            raise InvalidRevisionId(revision_id=revision_id, branch=self)
-        try:
-            return self.revision_store.get(revision_id)
-        except (IndexError, KeyError):
-            raise bzrlib.errors.NoSuchRevision(self, revision_id)
-
-    def get_revision_xml(self, revision_id):
-        """See Branch.get_revision_xml."""
-        return self._get_revision_xml_file(revision_id).read()
-
-    def get_revision(self, revision_id):
-        """See Branch.get_revision."""
-        xml_file = self._get_revision_xml_file(revision_id)
-
-        try:
-            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
-        except SyntaxError, e:
-            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
-                                         [revision_id,
-                                          str(e)])
-            
-        assert r.revision_id == revision_id
-        return r
-
-    def get_revision_sha1(self, revision_id):
-        """See Branch.get_revision_sha1."""
-        # In the future, revision entries will be signed. At that
-        # point, it is probably best *not* to include the signature
-        # in the revision hash. Because that lets you re-sign
-        # the revision, (add signatures/remove signatures) and still
-        # have all hash pointers stay consistent.
-        # But for now, just hash the contents.
-        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
-
-    def get_ancestry(self, revision_id):
-        """See Branch.get_ancestry."""
-        if revision_id is None:
-            return [None]
-        w = self._get_inventory_weave()
-        return [None] + map(w.idx_to_name,
-                            w.inclusions([w.lookup(revision_id)]))
-
-    def _get_inventory_weave(self):
-        return self.control_weaves.get_weave('inventory',
-                                             self.get_transaction())
-
-    def get_inventory(self, revision_id):
-        """See Branch.get_inventory."""
-        xml = self.get_inventory_xml(revision_id)
-        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
-
-    def get_inventory_xml(self, revision_id):
-        """See Branch.get_inventory_xml."""
-        try:
-            assert isinstance(revision_id, basestring), type(revision_id)
-            iw = self._get_inventory_weave()
-            return iw.get_text(iw.lookup(revision_id))
-        except IndexError:
-            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
-
-    def get_inventory_sha1(self, revision_id):
-        """See Branch.get_inventory_sha1."""
-        return self.get_revision(revision_id).inventory_sha1
-
-    def get_revision_inventory(self, revision_id):
-        """See Branch.get_revision_inventory."""
-        # TODO: Unify this with get_inventory()
-        # bzr 0.0.6 and later imposes the constraint that the inventory_id
-        # must be the same as its revision, so this is trivial.
-        if revision_id == None:
-            # This does not make sense: if there is no revision,
-            # then it is the current tree inventory surely ?!
-            # and thus get_root_id() is something that looks at the last
-            # commit on the branch, and the get_root_id is an inventory check.
-            raise NotImplementedError
-            # return Inventory(self.get_root_id())
+    def get_revision_delta(self, revno):
+        """Return the delta for one revision.
+
+        The delta is relative to its mainline predecessor, or the
+        empty tree for revision 1.
+        """
+        assert isinstance(revno, int)
+        rh = self.revision_history()
+        if not (1 <= revno <= len(rh)):
+            raise InvalidRevisionNumber(revno)
+
+        # revno is 1-based; list is 0-based
+
+        new_tree = self.repository.revision_tree(rh[revno-1])
+        if revno == 1:
+            old_tree = EmptyTree()
         else:
-            return self.get_inventory(revision_id)
+            old_tree = self.repository.revision_tree(rh[revno-2])
+        return compare_trees(old_tree, new_tree)
 
     @needs_read_lock
     def revision_history(self):
         """See Branch.revision_history."""
+        # FIXME are transactions bound to control files ? RBC 20051121
         transaction = self.get_transaction()
         history = transaction.map.find_revision_history()
         if history is not None:
             mutter("cache hit for revision-history in %s", self)
             return list(history)
         history = [l.rstrip('\r\n') for l in
-                self.controlfile('revision-history', 'r').readlines()]
+                self.control_files.controlfile('revision-history', 'r').readlines()]
         transaction.map.add_revision_history(history)
         # this call is disabled because revision_history is 
         # not really an object yet, and the transaction is for objects.
@@ -1013,7 +703,8 @@
         except DivergedBranches, e:
             try:
                 pullable_revs = get_intervening_revisions(self.last_revision(),
-                                                          stop_revision, self)
+                                                          stop_revision, 
+                                                          self.repository)
                 assert self.last_revision() not in pullable_revs
                 return pullable_revs
             except bzrlib.errors.NotAncestor:
@@ -1022,30 +713,24 @@
                 else:
                     raise e
         
-    def revision_tree(self, revision_id):
-        """See Branch.revision_tree."""
-        # TODO: refactor this to use an existing revision object
-        # so we don't need to read it in twice.
-        if revision_id == None or revision_id == NULL_REVISION:
-            return EmptyTree()
-        else:
-            inv = self.get_revision_inventory(revision_id)
-            return RevisionTree(self, inv, revision_id)
-
     def basis_tree(self):
         """See Branch.basis_tree."""
         try:
             revision_id = self.revision_history()[-1]
+            # FIXME: This is an abstraction violation, the basis tree 
+            # here as defined is on the working tree, the method should
+            # be too. The basis tree for a branch can be different than
+            # that for a working tree. RBC 20051207
             xml = self.working_tree().read_basis_inventory(revision_id)
             inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
-            return RevisionTree(self, inv, revision_id)
+            return RevisionTree(self.repository, inv, revision_id)
         except (IndexError, NoSuchFile, NoWorkingTree), e:
-            return self.revision_tree(self.last_revision())
+            return self.repository.revision_tree(self.last_revision())
 
     def working_tree(self):
         """See Branch.working_tree."""
         from bzrlib.workingtree import WorkingTree
-        if self._transport.base.find('://') != -1:
+        if self.base.find('://') != -1:
             raise NoWorkingTree(self.base)
         return WorkingTree(self.base, branch=self)
 
@@ -1073,7 +758,7 @@
         _locs = ['parent', 'pull', 'x-pull']
         for l in _locs:
             try:
-                return self.controlfile(l, 'r').read().strip('\n')
+                return self.control_files.controlfile(l, 'r').read().strip('\n')
             except NoSuchFile:
                 pass
         return None
@@ -1094,7 +779,7 @@
         """See Branch.set_parent."""
         # TODO: Maybe delete old location files?
         from bzrlib.atomicfile import AtomicFile
-        f = AtomicFile(self.controlfilename('parent'))
+        f = AtomicFile(self.control_files.controlfilename('parent'))
         try:
             f.write(url + '\n')
             f.commit()
@@ -1104,16 +789,52 @@
     def tree_config(self):
         return TreeConfig(self)
 
-    def sign_revision(self, revision_id, gpg_strategy):
-        """See Branch.sign_revision."""
-        plaintext = Testament.from_revision(self, revision_id).as_short_text()
-        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
-
-    @needs_write_lock
-    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
-        """See Branch.store_revision_signature."""
-        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
-                                revision_id, "sig")
+    def _get_truncated_history(self, revision_id):
+        history = self.revision_history()
+        if revision_id is None:
+            return history
+        try:
+            idx = history.index(revision_id)
+        except ValueError:
+            raise InvalidRevisionId(revision_id=revision, branch=self)
+        return history[:idx+1]
+
+    @needs_read_lock
+    def _clone_weave(self, to_location, revision=None, basis_branch=None):
+        assert isinstance(to_location, basestring)
+        if basis_branch is not None:
+            note("basis_branch is not supported for fast weave copy yet.")
+
+        history = self._get_truncated_history(revision)
+        if not bzrlib.osutils.lexists(to_location):
+            os.mkdir(to_location)
+        branch_to = Branch.initialize(to_location)
+        mutter("copy branch from %s to %s", self, branch_to)
+        branch_to.working_tree().set_root_id(self.get_root_id())
+
+        self.repository.copy(branch_to.repository)
+        
+        # must be done *after* history is copied across
+        # FIXME duplicate code with base .clone().
+        # .. would template method be useful here.  RBC 20051207
+        branch_to.set_parent(self.base)
+        branch_to.append_revision(*history)
+        # circular import protection
+        from bzrlib.merge import build_working_dir
+        build_working_dir(to_location)
+        mutter("copied")
+        return branch_to
+
+    def clone(self, to_location, revision=None, basis_branch=None, to_branch_type=None):
+        if to_branch_type is None:
+            to_branch_type = BzrBranch
+
+        if to_branch_type == BzrBranch \
+            and self.repository.weave_store.listable() \
+            and self.repository.revision_store.listable():
+            return self._clone_weave(to_location, revision, basis_branch)
+
+        return Branch.clone(self, to_location, revision, basis_branch, to_branch_type)
 
 
 class ScratchBranch(BzrBranch):
@@ -1141,16 +862,23 @@
         else:
             super(ScratchBranch, self).__init__(transport)
 
+        # BzrBranch creates a clone to .bzr and then forgets about the
+        # original transport. A ScratchTransport() deletes itself and
+        # everything underneath it when it goes away, so we need to
+        # grab a local copy to prevent that from happening
+        self._transport = transport
+
         for d in dirs:
             self._transport.mkdir(d)
             
         for f in files:
             self._transport.put(f, 'content of %s' % f)
 
-
     def clone(self):
         """
         >>> orig = ScratchBranch(files=["file1", "file2"])
+        >>> os.listdir(orig.base)
+        [u'.bzr', u'file1', u'file2']
         >>> clone = orig.clone()
         >>> if os.name != 'nt':
         ...   os.path.samefile(orig.base, clone.base)
@@ -1158,8 +886,8 @@
         ...   orig.base == clone.base
         ...
         False
-        >>> os.path.isfile(pathjoin(clone.base, "file1"))
-        True
+        >>> os.listdir(clone.base)
+        [u'.bzr', u'file1', u'file2']
         """
         from shutil import copytree
         from bzrlib.osutils import mkdtemp

=== modified file 'bzrlib/builtins.py'
--- bzrlib/builtins.py	
+++ bzrlib/builtins.py	
@@ -148,13 +148,13 @@
             raise BzrCommandError('You must supply either --revision or a revision_id')
         b = WorkingTree.open_containing(u'.')[0].branch
         if revision_id is not None:
-            sys.stdout.write(b.get_revision_xml(revision_id))
+            sys.stdout.write(b.repository.get_revision_xml(revision_id))
         elif revision is not None:
             for rev in revision:
                 if rev is None:
                     raise BzrCommandError('You cannot specify a NULL revision.')
                 revno, rev_id = rev.in_history(b)
-                sys.stdout.write(b.get_revision_xml(rev_id))
+                sys.stdout.write(b.repository.get_revision_xml(rev_id))
     
 
 class cmd_revno(Command):
@@ -298,7 +298,7 @@
             if len(revision) > 1:
                 raise BzrCommandError('bzr inventory --revision takes'
                     ' exactly one revision identifier')
-            inv = tree.branch.get_revision_inventory(
+            inv = tree.branch.repository.get_revision_inventory(
                 revision[0].in_history(tree.branch).rev_id)
 
         for path, entry in inv.entries():
@@ -548,7 +548,6 @@
     aliases = ['get', 'clone']
 
     def run(self, from_location, to_location=None, revision=None, basis=None):
-        from bzrlib.clone import copy_branch
         import errno
         from shutil import rmtree
         if revision is None:
@@ -591,7 +590,7 @@
                 else:
                     raise
             try:
-                copy_branch(br_from, to_location, revision_id, basis_branch)
+                br_from.clone(to_location, revision_id, basis_branch)
             except bzrlib.errors.NoSuchRevision:
                 rmtree(to_location)
                 msg = "The branch %s has no revision %s." % (from_location, revision[0])
@@ -603,7 +602,12 @@
             branch = Branch.open(to_location)
             if name:
                 name = StringIO(name)
-                branch.put_controlfile('branch-name', name)
+                branch.lock_write()
+                try:
+                    branch.control_files.put_utf8('branch-name', name)
+                finally:
+                    branch.unlock()
+                    
             note('Branched %d revision(s).' % branch.revno())
         finally:
             br_from.unlock()
@@ -927,7 +931,7 @@
             if tree is None:
                 b, fp = Branch.open_containing(filename)
                 if fp != '':
-                    inv = b.get_inventory(b.last_revision())
+                    inv = b.repository.get_inventory(b.last_revision())
             if fp != '':
                 file_id = inv.path2id(fp)
             else:
@@ -1036,7 +1040,7 @@
         elif relpath:
             relpath += '/'
         if revision is not None:
-            tree = tree.branch.revision_tree(
+            tree = tree.branch.repository.revision_tree(
                 revision[0].in_history(tree.branch).rev_id)
         for fp, fc, kind, fid, entry in tree.list_files():
             if fp.startswith(relpath):
@@ -1194,7 +1198,7 @@
             if len(revision) != 1:
                 raise BzrError('bzr export --revision takes exactly 1 argument')
             rev_id = revision[0].in_history(b).rev_id
-        t = b.revision_tree(rev_id)
+        t = b.repository.revision_tree(rev_id)
         try:
             export(t, dest, format, root)
         except errors.NoSuchExportFormat, e:
@@ -1651,10 +1655,11 @@
                 raise BzrCommandError("Sorry, remerge only works after normal"
                                       + " merges.  Not cherrypicking or"
                                       + "multi-merges.")
+            repository = tree.branch.repository
             base_revision = common_ancestor(tree.branch.last_revision(), 
-                                            pending_merges[0], tree.branch)
-            base_tree = tree.branch.revision_tree(base_revision)
-            other_tree = tree.branch.revision_tree(pending_merges[0])
+                                            pending_merges[0], repository)
+            base_tree = repository.revision_tree(base_revision)
+            other_tree = repository.revision_tree(pending_merges[0])
             interesting_ids = None
             if file_list is not None:
                 interesting_ids = set()
@@ -1718,8 +1723,8 @@
         else:
             tree, file_list = tree_files(file_list)
             rev_id = revision[0].in_history(tree.branch).rev_id
-        tree.revert(file_list, tree.branch.revision_tree(rev_id),
-                                not no_backup)
+        tree.revert(file_list, tree.branch.repository.revision_tree(rev_id),
+                    not no_backup)
 
 
 class cmd_assert_fail(Command):
@@ -1821,7 +1826,8 @@
             remote_extra.reverse()
         if local_extra and not theirs_only:
             print "You have %d extra revision(s):" % len(local_extra)
-            for data in iter_log_data(local_extra, local_branch, verbose):
+            for data in iter_log_data(local_extra, local_branch.repository,
+                                      verbose):
                 lf.show(*data)
             printed_local = True
         else:
@@ -1830,7 +1836,8 @@
             if printed_local is True:
                 print "\n\n"
             print "You are missing %d revision(s):" % len(remote_extra)
-            for data in iter_log_data(remote_extra, remote_branch, verbose):
+            for data in iter_log_data(remote_extra, remote_branch.repository, 
+                                      verbose):
                 lf.show(*data)
         if not remote_extra and not local_extra:
             status_code = 0
@@ -1876,7 +1883,7 @@
                 rev_id = b.last_revision()
             else:
                 rev_id = revision[0].in_history(b).rev_id
-            t = Testament.from_revision(b, rev_id)
+            t = Testament.from_revision(b.repository, rev_id)
             if long:
                 sys.stdout.writelines(t.as_text_lines())
             else:
@@ -1912,7 +1919,7 @@
         branch.lock_read()
         try:
             file_id = tree.inventory.path2id(relpath)
-            tree = branch.revision_tree(branch.last_revision())
+            tree = branch.repository.revision_tree(branch.last_revision())
             file_version = tree.inventory[file_id].revision
             annotate_file(branch, file_version, file_id, long, all, sys.stdout)
         finally:
@@ -1937,11 +1944,11 @@
         b = WorkingTree.open_containing(u'.')[0].branch
         gpg_strategy = gpg.GPGStrategy(config.BranchConfig(b))
         if revision_id is not None:
-            b.sign_revision(revision_id, gpg_strategy)
+            b.repository.sign_revision(revision_id, gpg_strategy)
         elif revision is not None:
             if len(revision) == 1:
                 revno, rev_id = revision[0].in_history(b)
-                b.sign_revision(rev_id, gpg_strategy)
+                b.repository.sign_revision(rev_id, gpg_strategy)
             elif len(revision) == 2:
                 # are they both on rh- if so we can walk between them
                 # might be nice to have a range helper for arbitrary
@@ -1953,7 +1960,8 @@
                 if from_revno is None or to_revno is None:
                     raise BzrCommandError('Cannot sign a range of non-revision-history revisions')
                 for revno in range(from_revno, to_revno + 1):
-                    b.sign_revision(b.get_rev_id(revno), gpg_strategy)
+                    b.repository.sign_revision(b.get_rev_id(revno), 
+                                               gpg_strategy)
             else:
                 raise BzrCommandError('Please supply either one revision, or a range.')
 
@@ -2001,7 +2009,7 @@
         for r in range(revno, b.revno()+1):
             rev_id = b.get_rev_id(r)
             lf = log_formatter('short', to_file=sys.stdout,show_timezone='original')
-            lf.show(r, b.get_revision(rev_id), None)
+            lf.show(r, b.repository.get_revision(rev_id), None)
 
         if dry_run:
             print 'Dry-run, pretending to remove the above revisions.'

=== modified file 'bzrlib/check.py'
--- bzrlib/check.py	
+++ bzrlib/check.py	
@@ -41,6 +41,7 @@
 
     def __init__(self, branch):
         self.branch = branch
+        self.repository = branch.repository
         self.checked_text_cnt = 0
         self.checked_rev_cnt = 0
         self.ghosts = []
@@ -59,7 +60,7 @@
             self.progress.update('retrieving inventory', 0, 0)
             # do not put in init, as it should be done with progess,
             # and inside the lock.
-            self.inventory_weave = self.branch._get_inventory_weave()
+            self.inventory_weave = self.branch.repository.get_inventory_weave()
             self.history = self.branch.revision_history()
             if not len(self.history):
                 # nothing to see here
@@ -78,8 +79,9 @@
             self.branch.unlock()
 
     def plan_revisions(self):
-        if not self.branch.revision_store.listable():
-            self.planned_revisions = self.branch.get_ancestry(self.history[-1])
+        repository = self.branch.repository
+        if not repository.revision_store.listable():
+            self.planned_revisions = repository.get_ancestry(self.history[-1])
             self.planned_revisions.remove(None)
             # FIXME progress bars should support this more nicely.
             self.progress.clear()
@@ -87,7 +89,7 @@
                    " for a complete check use a local branch.")
             return
         
-        self.planned_revisions = set(self.branch.revision_store)
+        self.planned_revisions = set(repository.revision_store)
         inventoried = set(self.inventory_weave.names())
         awol = self.planned_revisions - inventoried
         if len(awol) > 0:
@@ -140,11 +142,11 @@
             rev_history_position = None
         last_rev_id = None
         if rev_history_position:
-            rev = branch.get_revision(rev_id)
+            rev = branch.repository.get_revision(rev_id)
             if rev_history_position > 0:
                 last_rev_id = self.history[rev_history_position - 1]
         else:
-            rev = branch.get_revision(rev_id)
+            rev = branch.repository.get_revision(rev_id)
                 
         if rev.revision_id != rev_id:
             raise BzrCheckError('wrong internal revision id in revision {%s}'
@@ -168,7 +170,7 @@
                     # list based so somewhat slow,
                     # TODO have a planned_revisions list and set.
                     if self.branch.has_revision(parent):
-                        missing_ancestry = self.branch.get_ancestry(parent)
+                        missing_ancestry = self.repository.get_ancestry(parent)
                         for missing in missing_ancestry:
                             if (missing is not None 
                                 and missing not in self.planned_revisions):
@@ -181,7 +183,7 @@
                                 % (rev_id, last_rev_id))
 
         if rev.inventory_sha1:
-            inv_sha1 = branch.get_inventory_sha1(rev_id)
+            inv_sha1 = branch.repository.get_inventory_sha1(rev_id)
             if inv_sha1 != rev.inventory_sha1:
                 raise BzrCheckError('Inventory sha1 hash doesn\'t match'
                     ' value in revision {%s}' % rev_id)
@@ -196,21 +198,21 @@
         """
         n_weaves = 1
         weave_ids = []
-        if self.branch.weave_store.listable():
-            weave_ids = list(self.branch.weave_store)
+        if self.branch.repository.weave_store.listable():
+            weave_ids = list(self.branch.repository.weave_store)
             n_weaves = len(weave_ids)
         self.progress.update('checking weave', 0, n_weaves)
         self.inventory_weave.check(progress_bar=self.progress)
         for i, weave_id in enumerate(weave_ids):
             self.progress.update('checking weave', i, n_weaves)
-            w = self.branch.weave_store.get_weave(weave_id,
-                    self.branch.get_transaction())
+            w = self.branch.repository.weave_store.get_weave(weave_id,
+                    self.branch.repository.get_transaction())
             # No progress here, because it looks ugly.
             w.check()
             self.checked_weaves[weave_id] = True
 
     def _check_revision_tree(self, rev_id):
-        tree = self.branch.revision_tree(rev_id)
+        tree = self.branch.repository.revision_tree(rev_id)
         inv = tree.inventory
         seen_ids = {}
         for file_id in inv:

=== modified file 'bzrlib/commit.py'
--- bzrlib/commit.py	
+++ bzrlib/commit.py	
@@ -202,7 +202,7 @@
         mutter('preparing to commit')
 
         self.branch = branch
-        self.weave_store = branch.weave_store
+        self.weave_store = branch.repository.weave_store
         self.rev_id = rev_id
         self.specific_files = specific_files
         self.allow_pointless = allow_pointless
@@ -290,7 +290,7 @@
         """Store the inventory for the new revision."""
         inv_text = serializer_v5.write_inventory_to_string(self.new_inv)
         self.inv_sha1 = sha_string(inv_text)
-        s = self.branch.control_weaves
+        s = self.branch.repository.control_weaves
         s.add_text('inventory', self.rev_id,
                    split_lines(inv_text), self.present_parents,
                    self.branch.get_transaction())
@@ -319,14 +319,15 @@
             self.parents.append(precursor_id)
         self.parents += pending_merges
         for revision in self.parents:
-            if self.branch.has_revision(revision):
-                self.parent_invs.append(self.branch.get_inventory(revision))
+            if self.branch.repository.has_revision(revision):
+                inventory = self.branch.repository.get_inventory(revision)
+                self.parent_invs.append(inventory)
                 self.present_parents.append(revision)
 
     def _check_parents_present(self):
         for parent_id in self.parents:
             mutter('commit parent revision {%s}', parent_id)
-            if not self.branch.has_revision(parent_id):
+            if not self.branch.repository.has_revision(parent_id):
                 if parent_id == self.branch.last_revision():
                     warning("parent is missing %r", parent_id)
                     raise HistoryMissing(self.branch, 'revision', parent_id)
@@ -348,9 +349,9 @@
         rev_tmp.seek(0)
         if self.config.signature_needed():
             plaintext = Testament(self.rev, self.new_inv).as_short_text()
-            self.branch.store_revision_signature(gpg.GPGStrategy(self.config),
-                                                 plaintext, self.rev_id)
-        self.branch.revision_store.add(rev_tmp, self.rev_id)
+            self.branch.repository.store_revision_signature(
+                gpg.GPGStrategy(self.config), plaintext, self.rev_id)
+        self.branch.repository.revision_store.add(rev_tmp, self.rev_id)
         mutter('new revision_id is {%s}', self.rev_id)
 
     def _remove_deleted(self):

=== modified file 'bzrlib/config.py'
--- bzrlib/config.py	
+++ bzrlib/config.py	
@@ -379,7 +379,7 @@
         This is looked up in the email controlfile for the branch.
         """
         try:
-            return (self.branch.controlfile("email", "r") 
+            return (self.branch.control_files.controlfile("email", "r") 
                     .read()
                     .decode(bzrlib.user_encoding)
                     .rstrip("\r\n"))
@@ -520,7 +520,7 @@
 
     def _get_config(self):
         try:
-            obj = ConfigObj(self.branch.controlfile('branch.conf',
+            obj = ConfigObj(self.branch.control_files.controlfile('branch.conf',
                                                     'rb').readlines())
             obj.decode('UTF-8')
         except errors.NoSuchFile:
@@ -558,6 +558,6 @@
             cfg_obj.encode('UTF-8')
             out_file = StringIO(''.join([l+'\n' for l in cfg_obj.write()]))
             out_file.seek(0)
-            self.branch.put_controlfile('branch.conf', out_file, encode=False)
+            self.branch.control_files.put('branch.conf', out_file)
         finally:
             self.branch.unlock()

=== modified file 'bzrlib/diff.py'
--- bzrlib/diff.py	
+++ bzrlib/diff.py	
@@ -164,7 +164,7 @@
         else:
             old_tree = b.working_tree()
     else:
-        old_tree = b.revision_tree(from_spec.in_history(b).rev_id)
+        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
 
     if revision2 is None:
         if b2 is None:
@@ -172,7 +172,7 @@
         else:
             new_tree = b2.working_tree()
     else:
-        new_tree = b.revision_tree(revision2.in_history(b).rev_id)
+        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
 
     return show_diff_trees(old_tree, new_tree, output, specific_files,
                            external_diff_options)

=== modified file 'bzrlib/fetch.py'
--- bzrlib/fetch.py	
+++ bzrlib/fetch.py	
@@ -91,11 +91,13 @@
         if to_branch == from_branch:
             raise Exception("can't fetch from a branch to itself")
         self.to_branch = to_branch
-        self.to_weaves = to_branch.weave_store
-        self.to_control = to_branch.control_weaves
+        self.to_repository = to_branch.repository
+        self.to_weaves = self.to_repository.weave_store
+        self.to_control = self.to_repository.control_weaves
         self.from_branch = from_branch
-        self.from_weaves = from_branch.weave_store
-        self.from_control = from_branch.control_weaves
+        self.from_repository = from_branch.repository
+        self.from_weaves = self.from_repository.weave_store
+        self.from_control = self.from_repository.control_weaves
         self.failed_revisions = []
         self.count_copied = 0
         self.count_total = 0
@@ -117,7 +119,7 @@
         self.last_revision = self._find_last_revision(last_revision)
         mutter('fetch up to rev {%s}', self.last_revision)
         if (self.last_revision is not None and 
-            self.to_branch.has_revision(self.last_revision)):
+            self.to_repository.has_revision(self.last_revision)):
             return
         try:
             revs_to_fetch = self._compare_ancestries()
@@ -150,12 +152,14 @@
         That is, every revision that's in the ancestry of the source
         branch and not in the destination branch."""
         self.pb.update('get source ancestry')
-        self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
+        from_repository = self.from_branch.repository
+        self.from_ancestry = from_repository.get_ancestry(self.last_revision)
 
         dest_last_rev = self.to_branch.last_revision()
         self.pb.update('get destination ancestry')
         if dest_last_rev:
-            dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
+            to_repository = self.to_branch.repository
+            dest_ancestry = to_repository.get_ancestry(dest_last_rev)
         else:
             dest_ancestry = []
         ss = set(dest_ancestry)
@@ -174,7 +178,7 @@
             i += 1
             if rev_id is None:
                 continue
-            if self.to_branch.has_revision(rev_id):
+            if self.to_repository.has_revision(rev_id):
                 continue
             self.pb.update('copy revision', i, self.count_total)
             self._copy_one_revision(rev_id)
@@ -184,8 +188,8 @@
     def _copy_one_revision(self, rev_id):
         """Copy revision and everything referenced by it."""
         mutter('copying revision {%s}', rev_id)
-        rev_xml = self.from_branch.get_revision_xml(rev_id)
-        inv_xml = self.from_branch.get_inventory_xml(rev_id)
+        rev_xml = self.from_repository.get_revision_xml(rev_id)
+        inv_xml = self.from_repository.get_inventory_xml(rev_id)
         rev = serializer_v5.read_revision_from_string(rev_xml)
         inv = serializer_v5.read_inventory_from_string(inv_xml)
         assert rev.revision_id == rev_id
@@ -197,16 +201,16 @@
         parents = rev.parent_ids
         new_parents = copy(parents)
         for parent in parents:
-            if not self.to_branch.has_revision(parent):
+            if not self.to_repository.has_revision(parent):
                 new_parents.pop(new_parents.index(parent))
         self._copy_inventory(rev_id, inv_xml, new_parents)
-        self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
+        self.to_repository.revision_store.add(StringIO(rev_xml), rev_id)
         mutter('copied revision %s', rev_id)
 
     def _copy_inventory(self, rev_id, inv_xml, parent_ids):
         self.to_control.add_text('inventory', rev_id,
                                 split_lines(inv_xml), parent_ids,
-                                self.to_branch.get_transaction())
+                                self.to_repository.get_transaction())
 
     def _copy_new_texts(self, rev_id, inv):
         """Copy any new texts occuring in this revision."""
@@ -223,13 +227,13 @@
             text_revision in self.file_ids_names[file_id]:
                 return        
         to_weave = self.to_weaves.get_weave_or_empty(file_id,
-            self.to_branch.get_transaction())
+            self.to_repository.get_transaction())
         if not file_id in self.file_ids_names.keys( ):
             self.file_ids_names[file_id] = to_weave.names( )
         if text_revision in to_weave:
             return
         from_weave = self.from_weaves.get_weave(file_id,
-            self.from_branch.get_transaction())
+            self.from_branch.repository.get_transaction())
         if text_revision not in from_weave:
             raise MissingText(self.from_branch, text_revision, file_id)
         mutter('copy file {%s} modified in {%s}', file_id, rev_id)
@@ -244,7 +248,7 @@
             # destination is empty, just replace it
             to_weave = from_weave.copy( )
         self.to_weaves.put_weave(file_id, to_weave,
-            self.to_branch.get_transaction())
+            self.to_repository.get_transaction())
         self.count_weaves += 1
         self.copied_file_ids.add(file_id)
         self.file_ids_names[file_id] = to_weave.names()

=== modified file 'bzrlib/info.py'
--- bzrlib/info.py	
+++ bzrlib/info.py	
@@ -33,7 +33,8 @@
 def show_info(b):
     import diff
     
-    print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
+    print 'branch format:', b.control_files.controlfile(
+        'branch-format', 'r').readline().rstrip('\n')
 
     def plural(n, base='', pl=None):
         if n == 1:
@@ -83,16 +84,16 @@
     print '  %8d revision%s' % (revno, plural(revno))
     committers = {}
     for rev in history:
-        committers[b.get_revision(rev).committer] = True
+        committers[b.repository.get_revision(rev).committer] = True
     print '  %8d committer%s' % (len(committers), plural(len(committers)))
     if revno > 0:
-        firstrev = b.get_revision(history[0])
+        firstrev = b.repository.get_revision(history[0])
         age = int((time.time() - firstrev.timestamp) / 3600 / 24)
         print '  %8d day%s old' % (age, plural(age))
         print '   first revision: %s' % format_date(firstrev.timestamp,
                                                     firstrev.timezone)
 
-        lastrev = b.get_revision(history[-1])
+        lastrev = b.repository.get_revision(history[-1])
         print '  latest revision: %s' % format_date(lastrev.timestamp,
                                                     lastrev.timezone)
 
@@ -104,7 +105,7 @@
 
     print
     print 'revision store:'
-    c, t = b.revision_store.total_size()
+    c, t = b.repository.revision_store.total_size()
     print '  %8d revisions' % c
     print '  %8d kB' % (t/1024)
 

=== modified file 'bzrlib/log.py'
--- bzrlib/log.py	
+++ bzrlib/log.py	
@@ -74,7 +74,7 @@
     last_path = None
     revno = 1
     for revision_id in branch.revision_history():
-        this_inv = branch.get_revision_inventory(revision_id)
+        this_inv = branch.repository.get_revision_inventory(revision_id)
         if file_id in this_inv:
             this_ie = this_inv[file_id]
             this_path = this_inv.id2path(file_id)
@@ -223,7 +223,7 @@
             # although we calculated it, throw it away without display
             delta = None
 
-        rev = branch.get_revision(rev_id)
+        rev = branch.repository.get_revision(rev_id)
 
         if searchRE:
             if not searchRE.search(rev.message):
@@ -235,7 +235,9 @@
                 excludes = set()
             else:
                 # revno is 1 based, so -2 to get back 1 less.
-                excludes = set(branch.get_ancestry(revision_history[revno - 2]))
+                repository = branch.repository
+                excludes = repository.get_ancestry(revision_history[revno - 2])
+                excludes = set(excludes)
             pending = list(rev.parent_ids)
             while pending:
                 rev_id = pending.pop()
@@ -244,7 +246,7 @@
                 # prevent showing merged revs twice if they multi-path.
                 excludes.add(rev_id)
                 try:
-                    rev = branch.get_revision(rev_id)
+                    rev = branch.repository.get_revision(rev_id)
                 except errors.NoSuchRevision:
                     continue
                 pending.extend(rev.parent_ids)
@@ -510,7 +512,7 @@
         to_file.write('*'*60)
         to_file.write('\nRemoved Revisions:\n')
         for i in range(base_idx, len(old_rh)):
-            rev = branch.get_revision(old_rh[i])
+            rev = branch.repository.get_revision(old_rh[i])
             lf.show(i+1, rev, None)
         to_file.write('*'*60)
         to_file.write('\n\n')

=== modified file 'bzrlib/merge.py'
--- bzrlib/merge.py	
+++ bzrlib/merge.py	
@@ -245,9 +245,9 @@
     else:
         if local_branch is not None:
             greedy_fetch(local_branch, branch, revision)
-            base_tree = local_branch.revision_tree(revision)
+            base_tree = local_branch.repository.revision_tree(revision)
         else:
-            base_tree = branch.revision_tree(revision)
+            base_tree = branch.repository.revision_tree(revision)
     return base_tree
 
 
@@ -389,11 +389,11 @@
                                                      other_tree)
 
     def revision_tree(self, revision_id):
-        return self.this_branch.revision_tree(revision_id)
+        return self.this_branch.repository.revision_tree(revision_id)
 
     def ensure_revision_trees(self):
         if self.this_revision_tree is None:
-            self.this_basis_tree = self.this_branch.revision_tree(
+            self.this_basis_tree = self.this_branch.repository.revision_tree(
                 self.this_basis)
             if self.this_basis == self.this_rev_id:
                 self.this_revision_tree = self.this_basis_tree
@@ -489,7 +489,8 @@
             return
         if self.other_rev_id is None:
             return
-        if self.other_rev_id in self.this_branch.get_ancestry(self.this_basis):
+        ancestry = self.this_branch.repository.get_ancestry(self.this_basis)
+        if self.other_rev_id in ancestry:
             return
         self.this_branch.working_tree().add_pending_merge(self.other_rev_id)
 
@@ -518,7 +519,7 @@
             try:
                 self.base_rev_id = common_ancestor(self.this_basis, 
                                                    self.other_basis, 
-                                                   self.this_branch)
+                                                   self.this_branch.repository)
             except NoCommonAncestor:
                 raise UnrelatedBranches()
             self.base_tree = get_revid_tree(self.this_branch, self.base_rev_id,

=== modified file 'bzrlib/missing.py'
--- bzrlib/missing.py	
+++ bzrlib/missing.py	
@@ -10,6 +10,7 @@
     for revno, rev_id in revisions:
         rev = revision_source.get_revision(rev_id)
         if verbose:
+            remote_tree = revision_source.revision_tree(rev_id)
             parent_rev_id = rev.parent_ids[0]
             if last_rev_id == parent_rev_id:
                 parent_tree = last_tree
@@ -43,10 +44,10 @@
                                                 remote_rev_history_map)
                 return local_extra, remote_extra
 
-            local_ancestry = _get_ancestry(local_branch, progress, "local",
-                                           2, local_rev_history)
-            remote_ancestry = _get_ancestry(remote_branch, progress, "remote",
-                                            3, remote_rev_history)
+            local_ancestry = _get_ancestry(local_branch.repository, progress, 
+                                           "local", 2, local_rev_history)
+            remote_ancestry = _get_ancestry(remote_branch.repository, progress,
+                                            "remote", 3, remote_rev_history)
             progress.update('pondering', 4, 5)
             extras = local_ancestry.symmetric_difference(remote_ancestry) 
             local_extra = extras.intersection(set(local_rev_history))
@@ -87,10 +88,10 @@
          for rev in rev_history])
     return rev_history, rev_history_map
 
-def _get_ancestry(branch, progress, label, step, rev_history):
+def _get_ancestry(repository, progress, label, step, rev_history):
     progress.update('%s ancestry' % label, step, 5)
     if len(rev_history) > 0:
-        ancestry = set(branch.get_ancestry(rev_history[-1]))
+        ancestry = set(repository.get_ancestry(rev_history[-1]))
     else:
         ancestry = set()
     return ancestry

=== modified file 'bzrlib/osutils.py'
--- bzrlib/osutils.py	
+++ bzrlib/osutils.py	
@@ -336,6 +336,14 @@
         tofile.write(b)
 
 
+def file_iterator(input_file, readsize=32768):
+    while True:
+        b = input_file.read(readsize)
+        if len(b) == 0:
+            break
+        yield b
+
+
 def sha_file(f):
     if hasattr(f, 'tell'):
         assert f.tell() == 0

=== modified file 'bzrlib/revision.py'
--- bzrlib/revision.py	
+++ bzrlib/revision.py	
@@ -88,7 +88,7 @@
     revisions_source is an object supporting a get_revision operation that
     behaves like Branch's.
     """
-    return candidate_id in branch.get_ancestry(revision_id)
+    return candidate_id in branch.repository.get_ancestry(revision_id)
 
 
 def iter_ancestors(revision_id, revision_source, only_present=False):

=== modified file 'bzrlib/revisionspec.py'
--- bzrlib/revisionspec.py	
+++ bzrlib/revisionspec.py	
@@ -57,7 +57,7 @@
         # TODO: otherwise, it should depend on how I was built -
         # if it's in_history(branch), then check revision_history(),
         # if it's in_store(branch), do the check below
-        return self.branch.revision_store.has_id(self.rev_id)
+        return self.branch.repository.has_revision(self.rev_id)
 
     def __len__(self):
         return 2
@@ -68,7 +68,7 @@
         raise IndexError(index)
 
     def get(self):
-        return self.branch.get_revision(self.rev_id)
+        return self.branch.repository.get_revision(self.rev_id)
 
     def __eq__(self, other):
         if type(other) not in (tuple, list, type(self)):
@@ -297,7 +297,7 @@
                     hour=hour, minute=minute, second=second)
         first = dt
         for i in range(len(revs)):
-            r = branch.get_revision(revs[i])
+            r = branch.repository.get_revision(revs[i])
             # TODO: Handle timezone.
             dt = datetime.datetime.fromtimestamp(r.timestamp)
             if first <= dt:
@@ -319,7 +319,8 @@
         for r, b in ((revision_a, branch), (revision_b, other_branch)):
             if r is None:
                 raise NoCommits(b)
-        revision_source = MultipleRevisionSources(branch, other_branch)
+        revision_source = MultipleRevisionSources(branch.repository,
+                                                  other_branch.repository)
         rev_id = common_ancestor(revision_a, revision_b, revision_source)
         try:
             revno = branch.revision_id_to_revno(rev_id)

=== modified file 'bzrlib/status.py'
--- bzrlib/status.py	
+++ bzrlib/status.py	
@@ -71,13 +71,13 @@
         elif len(revision) > 0:
             try:
                 rev_id = revision[0].in_history(branch).rev_id
-                old = branch.revision_tree(rev_id)
+                old = branch.repository.revision_tree(rev_id)
             except NoSuchRevision, e:
                 raise BzrCommandError(str(e))
             if len(revision) > 1:
                 try:
                     rev_id = revision[1].in_history(branch).rev_id
-                    new = branch.revision_tree(rev_id)
+                    new = branch.repository.revision_tree(rev_id)
                     new_is_working_tree = False
                 except NoSuchRevision, e:
                     raise BzrCommandError(str(e))
@@ -109,20 +109,20 @@
     print >>to_file, 'pending merges:'
     last_revision = branch.last_revision()
     if last_revision is not None:
-        ignore = set(branch.get_ancestry(last_revision))
+        ignore = set(branch.repository.get_ancestry(last_revision))
     else:
         ignore = set()
     for merge in new.pending_merges():
         ignore.add(merge)
         try:
-            m_revision = branch.get_revision(merge)
+            m_revision = branch.repository.get_revision(merge)
             print >> to_file, ' ', line_log(m_revision, 77)
-            inner_merges = branch.get_ancestry(merge)
+            inner_merges = branch.repository.get_ancestry(merge)
             inner_merges.reverse()
             for mmerge in inner_merges:
                 if mmerge in ignore:
                     continue
-                mm_revision = branch.get_revision(mmerge)
+                mm_revision = branch.repository.get_revision(mmerge)
                 print >> to_file, '   ', line_log(mm_revision, 75)
                 ignore.add(mmerge)
         except NoSuchRevision:

=== modified file 'bzrlib/store/__init__.py'
--- bzrlib/store/__init__.py	
+++ bzrlib/store/__init__.py	
@@ -181,6 +181,7 @@
         else:
             fn = self._relpath(fileid)
 
+        # FIXME RBC 20051128 this belongs in TextStore.
         fn_gz = fn + '.gz'
         if self._compressed:
             return fn_gz, fn
@@ -226,6 +227,7 @@
         super(TransportStore, self).__init__()
         self._transport = a_transport
         self._prefixed = prefixed
+        # FIXME RBC 20051128 this belongs in TextStore.
         self._compressed = compressed
         self._suffixes = set()
 

=== modified file 'bzrlib/testament.py'
--- bzrlib/testament.py	
+++ bzrlib/testament.py	
@@ -88,10 +88,10 @@
     """
 
     @classmethod
-    def from_revision(cls, branch, revision_id):
+    def from_revision(cls, repository, revision_id):
         """Produce a new testament from a historical revision"""
-        rev = branch.get_revision(revision_id)
-        inventory = branch.get_inventory(revision_id)
+        rev = repository.get_revision(revision_id)
+        inventory = repository.get_inventory(revision_id)
         return cls(rev, inventory)
 
     def __init__(self, rev, inventory):

=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py	
+++ bzrlib/tests/__init__.py	
@@ -39,6 +39,7 @@
 import bzrlib.commands
 from bzrlib.errors import BzrError
 import bzrlib.inventory
+import bzrlib.iterablefile
 import bzrlib.merge3
 import bzrlib.osutils
 import bzrlib.osutils as osutils
@@ -56,6 +57,7 @@
                       bzrlib.commands,
                       bzrlib.errors,
                       bzrlib.inventory,
+                      bzrlib.iterablefile,
                       bzrlib.merge3,
                       bzrlib.osutils,
                       bzrlib.store,
@@ -290,6 +292,10 @@
         if len(missing) > 0:
             raise AssertionError("value(s) %r not present in container %r" % 
                                  (missing, superlist))
+
+    def assertIs(self, left, right):
+        if not (left is right):
+            raise AssertionError("%r is not %r." % (left, right))
 
     def assertTransportMode(self, transport, path, mode):
         """Fail if a path does not have mode mode.
@@ -693,6 +699,7 @@
                    'bzrlib.tests.test_http',
                    'bzrlib.tests.test_identitymap',
                    'bzrlib.tests.test_inv',
+                   'bzrlib.tests.test_lockable_files',
                    'bzrlib.tests.test_log',
                    'bzrlib.tests.test_merge',
                    'bzrlib.tests.test_merge3',

=== modified file 'bzrlib/tests/blackbox/test_revision_info.py'
--- bzrlib/tests/blackbox/test_revision_info.py	
+++ bzrlib/tests/blackbox/test_revision_info.py	
@@ -92,9 +92,9 @@
         b.working_tree().commit('Commit three', rev_id='a at r-0-3')
 
         revs = {
-            1:b.get_revision_xml('a at r-0-1'),
-            2:b.get_revision_xml('a at r-0-2'),
-            3:b.get_revision_xml('a at r-0-3')
+            1:b.repository.get_revision_xml('a at r-0-1'),
+            2:b.repository.get_revision_xml('a at r-0-2'),
+            3:b.repository.get_revision_xml('a at r-0-3'),
         }
 
         self.check_output(revs[1], 'cat-revision', 'a at r-0-1')

=== modified file 'bzrlib/tests/blackbox/test_too_much.py'
--- bzrlib/tests/blackbox/test_too_much.py	
+++ bzrlib/tests/blackbox/test_too_much.py	
@@ -41,7 +41,6 @@
 import sys
 
 from bzrlib.branch import Branch
-from bzrlib.clone import copy_branch
 from bzrlib.errors import BzrCommandError
 from bzrlib.osutils import has_symlinks, pathjoin
 from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
@@ -364,7 +363,7 @@
         branch = Branch.initialize('branch1')
         branch.working_tree().add(['file'])
         branch.working_tree().commit('add file')
-        copy_branch(branch, 'branch2')
+        branch.clone('branch2')
         print >> open('branch2/file', 'wb'), 'new content'
         branch2 = Branch.open('branch2')
         branch2.working_tree().commit('update file')
@@ -439,7 +438,7 @@
         # Merging a branch pulls its revision into the tree
         a = Branch.open('.')
         b = Branch.open('../b')
-        a.get_revision_xml(b.last_revision())
+        a.repository.get_revision_xml(b.last_revision())
         self.log('pending merges: %s', a.working_tree().pending_merges())
         self.assertEquals(a.working_tree().pending_merges(),
                           [b.last_revision()])
@@ -598,6 +597,13 @@
                   'subdir/b\n'
                   , '--versioned')
 
+    def test_cat(self):
+        self.runbzr('init')
+        file("myfile", "wb").write("My contents\n")
+        self.runbzr('add')
+        self.runbzr('commit -m myfile')
+        self.run_bzr_captured('cat -r 1 myfile'.split(' '))
+
     def test_pull_verbose(self):
         """Pull changes from one branch to another and watch the output."""
 
@@ -825,6 +831,21 @@
         self.runbzr('commit -m done',)
         self.runbzr('remerge', retcode=3)
 
+    def test_status(self):
+        os.mkdir('branch1')
+        os.chdir('branch1')
+        self.runbzr('init')
+        self.runbzr('commit --unchanged --message f')
+        self.runbzr('branch . ../branch2')
+        self.runbzr('branch . ../branch3')
+        self.runbzr('commit --unchanged --message peter')
+        os.chdir('../branch2')
+        self.runbzr('merge ../branch1')
+        self.runbzr('commit --unchanged --message pumpkin')
+        os.chdir('../branch3')
+        self.runbzr('merge ../branch2')
+        message = self.capture('status')
+
 
     def test_conflicts(self):
         """Handling of merge conflicts"""
@@ -866,8 +887,10 @@
             from bzrlib.testament import Testament
             bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
             self.runbzr('re-sign -r revid:A')
-            self.assertEqual(Testament.from_revision(branch,'A').as_short_text(),
-                             branch.revision_store.get('A', 'sig').read())
+            self.assertEqual(Testament.from_revision(branch.repository,
+                             'A').as_short_text(),
+                             branch.repository.revision_store.get('A', 
+                             'sig').read())
         finally:
             bzrlib.gpg.GPGStrategy = oldstrategy
             
@@ -883,12 +906,16 @@
             from bzrlib.testament import Testament
             bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
             self.runbzr('re-sign -r 1..')
-            self.assertEqual(Testament.from_revision(branch,'A').as_short_text(),
-                             branch.revision_store.get('A', 'sig').read())
-            self.assertEqual(Testament.from_revision(branch,'B').as_short_text(),
-                             branch.revision_store.get('B', 'sig').read())
-            self.assertEqual(Testament.from_revision(branch,'C').as_short_text(),
-                             branch.revision_store.get('C', 'sig').read())
+            self.assertEqual(
+                Testament.from_revision(branch.repository,'A').as_short_text(),
+                branch.repository.revision_store.get('A', 'sig').read())
+            self.assertEqual(
+                Testament.from_revision(branch.repository,'B').as_short_text(),
+                branch.repository.revision_store.get('B', 'sig').read())
+            self.assertEqual(Testament.from_revision(branch.repository,
+                             'C').as_short_text(),
+                             branch.repository.revision_store.get('C', 
+                             'sig').read())
         finally:
             bzrlib.gpg.GPGStrategy = oldstrategy
 
@@ -1287,6 +1314,14 @@
         url = self.get_remote_url('branch/file')
         output = self.capture('log %s' % url)
         self.assertEqual(8, len(output.split('\n')))
+        # FIXME: rbc 20051128 what is the remainder of this test testing?
+        # - it does not seem to be http specific.
+        copy = branch.clone('branch2')
+        branch.working_tree().commit(message='empty commit')
+        os.chdir('branch2')
+        self.run_bzr('merge', '../branch')
+        copy.working_tree().commit(message='merge')
+        output = self.capture('log')
         
     def test_check(self):
         self.build_tree(['branch/', 'branch/file'])

=== modified file 'bzrlib/tests/blackbox/test_versioning.py'
--- bzrlib/tests/blackbox/test_versioning.py	
+++ bzrlib/tests/blackbox/test_versioning.py	
@@ -171,8 +171,8 @@
         mutter('start selective subdir commit')
         run_bzr('commit', 'a', '-m', 'commit a only')
         
-        old = b.revision_tree(b.get_rev_id(1))
-        new = b.revision_tree(b.get_rev_id(2))
+        old = b.repository.revision_tree(b.get_rev_id(1))
+        new = b.repository.revision_tree(b.get_rev_id(2))
         
         eq(new.get_file_by_path('b/two').read(), 'old contents')
         eq(new.get_file_by_path('top').read(), 'old contents')
@@ -181,14 +181,14 @@
         os.chdir('a')
         # commit from here should do nothing
         run_bzr('commit', '.', '-m', 'commit subdir only', '--unchanged')
-        v3 = b.revision_tree(b.get_rev_id(3))
+        v3 = b.repository.revision_tree(b.get_rev_id(3))
         eq(v3.get_file_by_path('b/two').read(), 'old contents')
         eq(v3.get_file_by_path('top').read(), 'old contents')
         eq(v3.get_file_by_path('a/one').read(), 'new contents')
                 
         # commit in subdirectory commits whole tree
         run_bzr('commit', '-m', 'commit whole tree from subdir')
-        v4 = b.revision_tree(b.get_rev_id(4))
+        v4 = b.repository.revision_tree(b.get_rev_id(4))
         eq(v4.get_file_by_path('b/two').read(), 'new contents')        
         eq(v4.get_file_by_path('top').read(), 'new contents')
         

=== modified file 'bzrlib/tests/test_ancestry.py'
--- bzrlib/tests/test_ancestry.py	
+++ bzrlib/tests/test_ancestry.py	
@@ -39,10 +39,10 @@
                   allow_pointless=True,
                   rev_id='tester at foo--2')
 
-        ancs = b.get_ancestry('tester at foo--2')
+        ancs = b.repository.get_ancestry('tester at foo--2')
         self.assertEqual([None, 'tester at foo--1', 'tester at foo--2'], ancs)
         self.assertEqual([None, 'tester at foo--1'], 
-                         b.get_ancestry('tester at foo--1'))
+                         b.repository.get_ancestry('tester at foo--1'))
 
     def test_none_is_always_an_ancestor(self):
         b = Branch.initialize(u'.')

=== modified file 'bzrlib/tests/test_annotate.py'
--- bzrlib/tests/test_annotate.py	
+++ bzrlib/tests/test_annotate.py	
@@ -31,7 +31,6 @@
 import os
 
 from bzrlib.branch import Branch
-from bzrlib.clone import copy_branch
 from bzrlib.errors import BzrCommandError
 from bzrlib.osutils import has_symlinks
 from bzrlib.tests import TestCaseInTempDir, BzrTestBase

=== modified file 'bzrlib/tests/test_basis_inventory.py'
--- bzrlib/tests/test_basis_inventory.py	
+++ bzrlib/tests/test_basis_inventory.py	
@@ -25,7 +25,7 @@
 
     def test_create(self):
         # Make sure the basis file is created by a commit
-        b = Branch.initialize(u'.')
+        b = Branch.initialize('.')
         t = b.working_tree()
         open('a', 'wb').write('a\n')
         t.add('a')
@@ -37,7 +37,7 @@
         basis_inv = serializer_v5.read_inventory_from_string(basis_inv_txt)
         #self.assertEquals('r1', basis_inv.revision_id)
         
-        store_inv = b.get_inventory('r1')
+        store_inv = b.repository.get_inventory('r1')
         self.assertEquals(store_inv._byid, basis_inv._byid)
 
         open('b', 'wb').write('b\n')
@@ -49,7 +49,7 @@
 
         basis_inv_txt = t.read_basis_inventory('r2')
         basis_inv = serializer_v5.read_inventory_from_string(basis_inv_txt)
-        store_inv = b.get_inventory('r2')
+        store_inv = b.repository.get_inventory('r2')
 
         self.assertEquals(store_inv._byid, basis_inv._byid)
 

=== modified file 'bzrlib/tests/test_branch.py'
--- bzrlib/tests/test_branch.py	
+++ bzrlib/tests/test_branch.py	
@@ -18,7 +18,6 @@
 import sys
 
 from bzrlib.branch import Branch, needs_read_lock, needs_write_lock
-from bzrlib.clone import copy_branch
 from bzrlib.commit import commit
 import bzrlib.errors as errors
 from bzrlib.errors import NoSuchRevision, UnlistableBranch, NotBranchError
@@ -61,17 +60,17 @@
         eq(f.count_copied, 1)
         eq(f.last_revision, 'revision-1')
 
-        rev = b2.get_revision('revision-1')
-        tree = b2.revision_tree('revision-1')
+        rev = b2.repository.get_revision('revision-1')
+        tree = b2.repository.revision_tree('revision-1')
         eq(tree.get_file_text('foo-id'), 'hello')
 
     def test_revision_tree(self):
         b1 = Branch.initialize(u'.')
         b1.working_tree().commit('lala!', rev_id='revision-1', allow_pointless=True)
-        tree = b1.revision_tree('revision-1')
-        tree = b1.revision_tree(None)
+        tree = b1.repository.revision_tree('revision-1')
+        tree = b1.repository.revision_tree(None)
         self.assertEqual(len(tree.list_files()), 0)
-        tree = b1.revision_tree(NULL_REVISION)
+        tree = b1.repository.revision_tree(NULL_REVISION)
         self.assertEqual(len(tree.list_files()), 0)
 
     def get_unbalanced_branch_pair(self):
@@ -95,26 +94,26 @@
         """Copy the stores from one branch to another"""
         br_a, br_b = self.get_unbalanced_branch_pair()
         # ensure the revision is missing.
-        self.assertRaises(NoSuchRevision, br_b.get_revision, 
+        self.assertRaises(NoSuchRevision, br_b.repository.get_revision, 
                           br_a.revision_history()[0])
         br_a.push_stores(br_b)
         # check that b now has all the data from a's first commit.
-        rev = br_b.get_revision(br_a.revision_history()[0])
-        tree = br_b.revision_tree(br_a.revision_history()[0])
+        rev = br_b.repository.get_revision(br_a.revision_history()[0])
+        tree = br_b.repository.revision_tree(br_a.revision_history()[0])
         for file_id in tree:
             if tree.inventory[file_id].kind == "file":
                 tree.get_file(file_id).read()
         return br_a, br_b
 
-    def test_copy_branch(self):
+    def test_clone_branch(self):
         """Copy the stores from one branch to another"""
         br_a, br_b = self.get_balanced_branch_pair()
         commit(br_b, "silly commit")
         os.mkdir('c')
-        br_c = copy_branch(br_a, 'c', basis_branch=br_b)
+        br_c = br_a.clone('c', basis_branch=br_b)
         self.assertEqual(br_a.revision_history(), br_c.revision_history())
 
-    def test_copy_partial(self):
+    def test_clone_partial(self):
         """Copy only part of the history of a branch."""
         self.build_tree(['a/', 'a/one'])
         br_a = Branch.initialize('a')
@@ -123,7 +122,7 @@
         self.build_tree(['a/two'])
         br_a.working_tree().add(['two'])
         br_a.working_tree().commit('commit two', rev_id='u at d-2')
-        br_b = copy_branch(br_a, 'b', revision='u at d-1')
+        br_b = br_a.clone('b', revision='u at d-1')
         self.assertEqual(br_b.last_revision(), 'u at d-1')
         self.assertTrue(os.path.exists('b/one'))
         self.assertFalse(os.path.exists('b/two'))
@@ -133,15 +132,16 @@
         branch = Branch.initialize(u'.')
         branch.working_tree().add_pending_merge('non:existent at rev--ision--0--2')
         branch.working_tree().commit('pretend to merge nonexistent-revision', rev_id='first')
-        rev = branch.get_revision(branch.last_revision())
+        rev = branch.repository.get_revision(branch.last_revision())
         self.assertEqual(len(rev.parent_ids), 1)
         # parent_sha1s is not populated now, WTF. rbc 20051003
         self.assertEqual(len(rev.parent_sha1s), 0)
         self.assertEqual(rev.parent_ids[0], 'non:existent at rev--ision--0--2')
 
     def test_bad_revision(self):
-        branch = Branch.initialize(u'.')
-        self.assertRaises(errors.InvalidRevisionId, branch.get_revision, None)
+        branch = Branch.initialize('.')
+        self.assertRaises(errors.InvalidRevisionId, 
+                          branch.repository.get_revision, None)
 
 # TODO 20051003 RBC:
 # compare the gpg-to-sign info for a commit with a ghost and 
@@ -162,7 +162,7 @@
                           ['foo at azkhazan-123123-abcabc',
                            'wibble at fofof--20050401--1928390812'])
         b.working_tree().commit("commit from base with two merges")
-        rev = b.get_revision(b.revision_history()[0])
+        rev = b.repository.get_revision(b.revision_history()[0])
         self.assertEquals(len(rev.parent_ids), 2)
         self.assertEquals(rev.parent_ids[0],
                           'foo at azkhazan-123123-abcabc')
@@ -175,23 +175,20 @@
         branch = Branch.initialize(u'.')
         branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
         from bzrlib.testament import Testament
-        branch.sign_revision('A', bzrlib.gpg.LoopbackGPGStrategy(None))
-        self.assertEqual(Testament.from_revision(branch, 'A').as_short_text(),
-                         branch.revision_store.get('A', 'sig').read())
+        strategy = bzrlib.gpg.LoopbackGPGStrategy(None)
+        branch.repository.sign_revision('A', strategy)
+        self.assertEqual(Testament.from_revision(branch.repository, 
+                         'A').as_short_text(),
+                         branch.repository.revision_store.get('A', 
+                         'sig').read())
 
     def test_store_signature(self):
-        branch = Branch.initialize(u'.')
-        branch.store_revision_signature(bzrlib.gpg.LoopbackGPGStrategy(None),
-                                        'FOO', 'A')
-        self.assertEqual('FOO', branch.revision_store.get('A', 'sig').read())
-
-    def test__relcontrolfilename(self):
-        branch = Branch.initialize(u'.')
-        self.assertEqual('.bzr/%25', branch._rel_controlfilename('%'))
-        
-    def test__relcontrolfilename_empty(self):
-        branch = Branch.initialize(u'.')
-        self.assertEqual('.bzr', branch._rel_controlfilename(''))
+        branch = Branch.initialize('.')
+        branch.repository.store_revision_signature(
+            bzrlib.gpg.LoopbackGPGStrategy(None), 'FOO', 'A')
+        self.assertEqual('FOO', 
+                         branch.repository.revision_store.get('A', 
+                         'sig').read())
 
     def test_nicks(self):
         """Branch nicknames"""
@@ -203,7 +200,7 @@
         self.assertEqual(branch.nick, 'bzr.ab')
         branch.nick = "Aaron's branch"
         branch.nick = "Aaron's branch"
-        self.failUnless(os.path.exists(branch.controlfilename("branch.conf")))
+        self.failUnlessExists(branch.control_files.controlfilename("branch.conf"))
         self.assertEqual(branch.nick, "Aaron's branch")
         os.rename('bzr.ab', 'integration')
         branch = Branch.open('integration')
@@ -217,7 +214,7 @@
         branch = Branch.initialize('bzr.dev')
         branch.nick = "My happy branch"
         branch.working_tree().commit('My commit respect da nick.')
-        committed = branch.get_revision(branch.last_revision())
+        committed = branch.repository.get_revision(branch.last_revision())
         self.assertEqual(committed.properties["branch-nick"], 
                          "My happy branch")
 
@@ -321,7 +318,7 @@
 
     def setUp(self):
         super(TestBranchTransaction, self).setUp()
-        self.branch = Branch.initialize(u'.')
+        self.branch = Branch.initialize('.')
         
     def test_default_get_transaction(self):
         """branch.get_transaction on a new branch should give a PassThrough."""
@@ -343,12 +340,12 @@
     def test_finish_readonly_transaction_works(self):
         self.branch._set_transaction(transactions.ReadOnlyTransaction())
         self.branch._finish_transaction()
-        self.assertEqual(None, self.branch._transaction)
+        self.assertEqual(None, self.branch.control_files._transaction)
 
     def test_unlock_calls_finish(self):
         self.branch.lock_read()
         transaction = InstrumentedTransaction()
-        self.branch._transaction = transaction
+        self.branch.control_files._transaction = transaction
         self.branch.unlock()
         self.assertEqual(['finish'], transaction.calls)
 
@@ -361,7 +358,7 @@
     def test_lock_write_acquires_passthrough_transaction(self):
         self.branch.lock_write()
         # cannot use get_transaction as its magic
-        self.failUnless(isinstance(self.branch._transaction,
+        self.failUnless(isinstance(self.branch.control_files._transaction,
                                    transactions.PassThroughTransaction))
         self.branch.unlock()
 

=== modified file 'bzrlib/tests/test_commit.py'
--- bzrlib/tests/test_commit.py	
+++ bzrlib/tests/test_commit.py	
@@ -59,14 +59,14 @@
         eq = self.assertEquals
         eq(b.revno(), 2)
         rh = b.revision_history()
-        rev = b.get_revision(rh[0])
+        rev = b.repository.get_revision(rh[0])
         eq(rev.message, 'add hello')
 
-        tree1 = b.revision_tree(rh[0])
+        tree1 = b.repository.revision_tree(rh[0])
         text = tree1.get_file_text(file_id)
         eq(text, 'hello world')
 
-        tree2 = b.revision_tree(rh[1])
+        tree2 = b.repository.revision_tree(rh[1])
         eq(tree2.get_file_text(file_id), 'version 2')
 
     def test_delete_commit(self):
@@ -79,7 +79,7 @@
         os.remove('hello')
         b.working_tree().commit('removed hello', rev_id='rev2')
 
-        tree = b.revision_tree('rev2')
+        tree = b.repository.revision_tree('rev2')
         self.assertFalse(tree.has_id('hello-id'))
 
     def test_pointless_commit(self):
@@ -132,12 +132,12 @@
         eq = self.assertEquals
         eq(b.revno(), 3)
 
-        tree2 = b.revision_tree('test at rev-2')
+        tree2 = b.repository.revision_tree('test at rev-2')
         self.assertTrue(tree2.has_filename('hello'))
         self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
         self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
         
-        tree3 = b.revision_tree('test at rev-3')
+        tree3 = b.repository.revision_tree('test at rev-3')
         self.assertFalse(tree3.has_filename('hello'))
         self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
 
@@ -154,7 +154,7 @@
         tree.commit(message='renamed', rev_id='test at rev-2', allow_pointless=False)
 
         eq = self.assertEquals
-        tree1 = b.revision_tree('test at rev-1')
+        tree1 = b.repository.revision_tree('test at rev-1')
         eq(tree1.id2path('hello-id'), 'hello')
         eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
         self.assertFalse(tree1.has_filename('fruity'))
@@ -162,7 +162,7 @@
         ie = tree1.inventory['hello-id']
         eq(ie.revision, 'test at rev-1')
 
-        tree2 = b.revision_tree('test at rev-2')
+        tree2 = b.repository.revision_tree('test at rev-2')
         eq(tree2.id2path('hello-id'), 'fruity')
         eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
         self.check_inventory_shape(tree2.inventory, ['fruity'])
@@ -198,7 +198,7 @@
         b.working_tree().commit('three', rev_id=r3, allow_pointless=False)
         self.check_inventory_shape(b.working_tree().read_working_inventory(),
                                    ['a', 'a/hello', 'a/b'])
-        self.check_inventory_shape(b.get_revision_inventory(r3),
+        self.check_inventory_shape(b.repository.get_revision_inventory(r3),
                                    ['a', 'a/hello', 'a/b'])
 
         b.working_tree().move(['a/hello'], 'a/b')
@@ -207,7 +207,7 @@
         self.check_inventory_shape(b.working_tree().read_working_inventory(),
                                    ['a', 'a/b/hello', 'a/b'])
 
-        inv = b.get_revision_inventory(r4)
+        inv = b.repository.get_revision_inventory(r4)
         eq(inv['hello-id'].revision, r4)
         eq(inv['a-id'].revision, r1)
         eq(inv['b-id'].revision, r3)
@@ -224,7 +224,7 @@
         wt.remove('hello')
         b.working_tree().commit('removed hello', rev_id='rev2')
 
-        tree = b.revision_tree('rev2')
+        tree = b.repository.revision_tree('rev2')
         self.assertFalse(tree.has_id('hello-id'))
 
 
@@ -243,7 +243,7 @@
         eq = self.assertEquals
         eq(b.revision_history(), rev_ids)
         for i in range(4):
-            anc = b.get_ancestry(rev_ids[i])
+            anc = b.repository.get_ancestry(rev_ids[i])
             eq(anc, [None] + rev_ids[:i+1])
 
     def test_commit_new_subdir_child_selective(self):
@@ -252,7 +252,7 @@
         b.working_tree().add(['dir', 'dir/file1', 'dir/file2'],
               ['dirid', 'file1id', 'file2id'])
         b.working_tree().commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
-        inv = b.get_inventory('1')
+        inv = b.repository.get_inventory('1')
         self.assertEqual('1', inv['dirid'].revision)
         self.assertEqual('1', inv['file1id'].revision)
         # FIXME: This should raise a KeyError I think, rbc20051006
@@ -299,7 +299,7 @@
         oldstrategy = bzrlib.gpg.GPGStrategy
         branch = Branch.initialize(u'.')
         branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
-        self.failIf(branch.revision_store.has_id('A', 'sig'))
+        self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
         try:
             from bzrlib.testament import Testament
             # monkey patch gpg signing mechanism
@@ -307,8 +307,10 @@
             commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
                                                       allow_pointless=True,
                                                       rev_id='B')
-            self.assertEqual(Testament.from_revision(branch,'B').as_short_text(),
-                             branch.revision_store.get('B', 'sig').read())
+            self.assertEqual(Testament.from_revision(branch.repository,
+                             'B').as_short_text(),
+                             branch.repository.revision_store.get('B', 
+                                                               'sig').read())
         finally:
             bzrlib.gpg.GPGStrategy = oldstrategy
 
@@ -318,7 +320,7 @@
         oldstrategy = bzrlib.gpg.GPGStrategy
         branch = Branch.initialize(u'.')
         branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
-        self.failIf(branch.revision_store.has_id('A', 'sig'))
+        self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
         try:
             from bzrlib.testament import Testament
             # monkey patch gpg signing mechanism
@@ -331,7 +333,7 @@
                               rev_id='B')
             branch = Branch.open(u'.')
             self.assertEqual(branch.revision_history(), ['A'])
-            self.failIf(branch.revision_store.has_id('B'))
+            self.failIf(branch.repository.revision_store.has_id('B'))
         finally:
             bzrlib.gpg.GPGStrategy = oldstrategy
 

=== modified file 'bzrlib/tests/test_commit_merge.py'
--- bzrlib/tests/test_commit_merge.py	
+++ bzrlib/tests/test_commit_merge.py	
@@ -58,7 +58,7 @@
         self.assertEquals(by.revno(), 2)
         self.assertEquals(list(by.revision_history()),
                           ['y at u-0-1', 'y at u-0-2'])
-        rev = by.get_revision('y at u-0-2')
+        rev = by.repository.get_revision('y at u-0-2')
         self.assertEquals(rev.parent_ids,
                           ['y at u-0-1', 'x at u-0-1'])
 
@@ -89,7 +89,7 @@
                           specific_files=['ecks'])
         
         commit(by, 'merge from x', rev_id='y at u-0-2', allow_pointless=False)
-        tree = by.revision_tree('y at u-0-2')
+        tree = by.repository.revision_tree('y at u-0-2')
         inv = tree.inventory
         self.assertEquals(inv['ecks-id'].revision, 'x at u-0-1')
         self.assertEquals(inv['why-id'].revision, 'y at u-0-1')

=== modified file 'bzrlib/tests/test_config.py'
--- bzrlib/tests/test_config.py	
+++ bzrlib/tests/test_config.py	
@@ -95,6 +95,12 @@
 
     def __init__(self):
         self.base = "http://example.com/branches/demo"
+        self.control_files = FakeControlFiles()
+
+
+class FakeControlFiles(object):
+
+    def __init__(self):
         self.email = 'Robert Collins <robertc at example.net>\n'
 
     def controlfile(self, filename, mode):
@@ -541,19 +547,19 @@
         my_config = config.BranchConfig(branch)
         self.assertEqual("Robert Collins <robertc at example.net>",
                          my_config._get_user_id())
-        branch.email = "John"
+        branch.control_files.email = "John"
         self.assertEqual("John", my_config._get_user_id())
 
     def test_not_set_in_branch(self):
         branch = FakeBranch()
         my_config = config.BranchConfig(branch)
-        branch.email = None
+        branch.control_files.email = None
         config_file = StringIO(sample_config_text)
         (my_config._get_location_config().
             _get_global_config()._get_parser(config_file))
         self.assertEqual("Robert Collins <robertc at example.com>",
                          my_config._get_user_id())
-        branch.email = "John"
+        branch.control_files.email = "John"
         self.assertEqual("John", my_config._get_user_id())
 
     def test_BZREMAIL_OVERRIDES(self):

=== modified file 'bzrlib/tests/test_fetch.py'
--- bzrlib/tests/test_fetch.py	
+++ bzrlib/tests/test_fetch.py	
@@ -23,7 +23,6 @@
 from bzrlib.branch import Branch
 from bzrlib.fetch import greedy_fetch
 from bzrlib.merge import merge
-from bzrlib.clone import copy_branch
 
 from bzrlib.tests import TestCaseInTempDir
 from bzrlib.tests.HTTPTestUtil import TestCaseWithWebserver
@@ -31,7 +30,7 @@
 
 def has_revision(branch, revision_id):
     try:
-        branch.get_revision_xml(revision_id)
+        branch.repository.get_revision_xml_file(revision_id)
         return True
     except bzrlib.errors.NoSuchRevision:
         return False
@@ -128,7 +127,7 @@
         os.mkdir('br1')
         br1 = Branch.initialize('br1')
         br1.working_tree().commit(message='rev 1-1', rev_id='1-1')
-        copy_branch(br1, 'br2')
+        br1.clone('br2')
         br2 = Branch.open('br2')
         br1.working_tree().commit(message='rev 1-2', rev_id='1-2')
         br2.working_tree().commit(message='rev 2-1', rev_id='2-1')
@@ -138,10 +137,10 @@
 
     def _check_revs_present(self, br2):
         for rev_id in '1-1', '1-2', '2-1':
-            self.assertTrue(br2.has_revision(rev_id))
-            rev = br2.get_revision(rev_id)
+            self.assertTrue(br2.repository.has_revision(rev_id))
+            rev = br2.repository.get_revision(rev_id)
             self.assertEqual(rev.revision_id, rev_id)
-            self.assertTrue(br2.get_inventory(rev_id))
+            self.assertTrue(br2.repository.get_inventory(rev_id))
 
 
 
@@ -153,7 +152,7 @@
         self.build_tree_contents([('br1/file', 'original contents\n')])
         br1.working_tree().add(['file'], ['this-file-id'])
         br1.working_tree().commit(message='rev 1-1', rev_id='1-1')
-        copy_branch(br1, 'br2')
+        br1.clone('br2')
         br2 = Branch.open('br2')
         self.build_tree_contents([('br1/file', 'original from 1\n')])
         br1.working_tree().commit(message='rev 1-2', rev_id='1-2')
@@ -173,8 +172,9 @@
                              ('1-3', 'agreement\n'),
                              ('2-1', 'contents in 2\n'),
                              ('2-2', 'agreement\n')]:
-            self.assertEqualDiff(br2.revision_tree(rev_id).get_file_text('this-file-id'),
-                                 text)
+            self.assertEqualDiff(
+                br2.repository.revision_tree(
+                    rev_id).get_file_text('this-file-id'), text)
 
 
 
@@ -184,7 +184,7 @@
     def test_fetch(self):
         #highest indices a: 5, b: 7
         br_a, br_b = make_branches(self)
-        br_rem_a = Branch.open(self.get_remote_url(br_a._transport.base))
+        br_rem_a = Branch.open(self.get_remote_url(br_a.base))
         fetch_steps(self, br_rem_a, br_b, br_a)
 
     def test_weaves_are_retrieved_once(self):

=== modified file 'bzrlib/tests/test_inv.py'
--- bzrlib/tests/test_inv.py	
+++ bzrlib/tests/test_inv.py	
@@ -18,7 +18,6 @@
 import os
 
 from bzrlib.branch import Branch
-from bzrlib.clone import copy_branch
 import bzrlib.errors as errors
 from bzrlib.diff import internal_diff
 from bzrlib.inventory import Inventory, ROOT_ID
@@ -149,8 +148,8 @@
         if has_symlinks():
             os.unlink('symlink')
             os.symlink('target2', 'symlink')
-        self.tree_1 = self.branch.revision_tree('1')
-        self.inv_1 = self.branch.get_inventory('1')
+        self.tree_1 = self.branch.repository.revision_tree('1')
+        self.inv_1 = self.branch.repository.get_inventory('1')
         self.file_1 = self.inv_1['fileid']
         self.tree_2 = self.branch.working_tree()
         self.inv_2 = self.tree_2.read_working_inventory()
@@ -248,8 +247,8 @@
             pass
         self.wt = self.branch.working_tree()
         self.wt.commit('message_1', rev_id = '1')
-        self.tree_1 = self.branch.revision_tree('1')
-        self.inv_1 = self.branch.get_inventory('1')
+        self.tree_1 = self.branch.repository.revision_tree('1')
+        self.inv_1 = self.branch.repository.get_inventory('1')
         self.file_1 = self.inv_1['fileid']
         self.work_tree = self.branch.working_tree()
         self.file_active = self.work_tree.inventory['fileid']
@@ -258,14 +257,14 @@
         # This tests that a simple commit with no parents makes a new
         # revision value in the inventory entry
         self.file_active.snapshot('2', 'subdir/file', {}, self.work_tree, 
-                                  self.branch.weave_store,
+                                  self.branch.repository.weave_store,
                                   self.branch.get_transaction())
         # expected outcome - file_1 has a revision id of '2', and we can get
         # its text of 'file contents' out of the weave.
         self.assertEqual(self.file_1.revision, '1')
         self.assertEqual(self.file_active.revision, '2')
         # this should be a separate test probably, but lets check it once..
-        lines = self.branch.weave_store.get_lines('fileid','2',
+        lines = self.branch.repository.weave_store.get_lines('fileid','2',
             self.branch.get_transaction())
         self.assertEqual(lines, ['contents of subdir/file\n'])
 
@@ -273,13 +272,14 @@
         #This tests that a simple commit does not make a new entry for
         # an unchanged inventory entry
         self.file_active.snapshot('2', 'subdir/file', {'1':self.file_1},
-                                  self.work_tree, self.branch.weave_store,
+                                  self.work_tree, 
+                                  self.branch.repository.weave_store,
                                   self.branch.get_transaction())
         self.assertEqual(self.file_1.revision, '1')
         self.assertEqual(self.file_active.revision, '1')
         self.assertRaises(errors.WeaveError,
-                          self.branch.weave_store.get_lines, 'fileid', '2',
-                          self.branch.get_transaction())
+                          self.branch.repository.weave_store.get_lines, 
+                          'fileid', '2', self.branch.get_transaction())
 
     def test_snapshot_merge_identical_different_revid(self):
         # This tests that a commit with two identical parents, one of which has
@@ -293,11 +293,12 @@
         self.assertEqual(self.file_1, other_ie)
         other_ie.revision = 'other'
         self.assertNotEqual(self.file_1, other_ie)
-        self.branch.weave_store.add_identical_text('fileid', '1', 'other', ['1'],
-            self.branch.get_transaction())
+        self.branch.repository.weave_store.add_identical_text('fileid', '1', 
+            'other', ['1'], self.branch.get_transaction())
         self.file_active.snapshot('2', 'subdir/file', 
                                   {'1':self.file_1, 'other':other_ie},
-                                  self.work_tree, self.branch.weave_store,
+                                  self.work_tree, 
+                                  self.branch.repository.weave_store,
                                   self.branch.get_transaction())
         self.assertEqual(self.file_active.revision, '2')
 
@@ -308,7 +309,7 @@
         rename('subdir/file', 'subdir/newname')
         self.file_active.snapshot('2', 'subdir/newname', {'1':self.file_1}, 
                                   self.work_tree, 
-                                  self.branch.weave_store,
+                                  self.branch.repository.weave_store,
                                   self.branch.get_transaction())
         # expected outcome - file_1 has a revision id of '2'
         self.assertEqual(self.file_active.revision, '2')
@@ -330,19 +331,23 @@
         self.branch = Branch.initialize(u'.')
         self.wt = self.branch.working_tree()
         self.wt.commit('new branch', allow_pointless=True, rev_id='A')
-        self.inv_A = self.branch.get_inventory('A')
-        self.branch.working_tree().add(['file'], ['fileid'])
+        self.inv_A = self.branch.repository.get_inventory('A')
+        self.wt.add(['file'], ['fileid'])
         self.wt.commit('add file', rev_id='B')
-        self.inv_B = self.branch.get_inventory('B')
-        self.branch.put_controlfile('revision-history', 'A\n')
+        self.inv_B = self.branch.repository.get_inventory('B')
+        self.branch.lock_write()
+        try:
+            self.branch.control_files.put_utf8('revision-history', 'A\n')
+        finally:
+            self.branch.unlock()
         self.assertEqual(self.branch.revision_history(), ['A'])
         self.wt.commit('another add of file', rev_id='C')
-        self.inv_C = self.branch.get_inventory('C')
+        self.inv_C = self.branch.repository.get_inventory('C')
         self.wt.add_pending_merge('B')
         self.wt.commit('merge in B', rev_id='D')
-        self.inv_D = self.branch.get_inventory('D')
-        self.file_active = self.wt.inventory['fileid']
-        self.weave = self.branch.weave_store.get_weave('fileid',
+        self.inv_D = self.branch.repository.get_inventory('D')
+        self.file_active = self.branch.working_tree().inventory['fileid']
+        self.weave = self.branch.repository.weave_store.get_weave('fileid',
             self.branch.get_transaction())
         
     def get_previous_heads(self, inventories):
@@ -404,7 +409,7 @@
 
         t.commit('adding a,b', rev_id='r1')
 
-        rev_tree = b.revision_tree('r1')
+        rev_tree = b.repository.revision_tree('r1')
         self.failUnless(rev_tree.is_executable(a_id), "'a' lost the execute bit")
         self.failIf(rev_tree.is_executable(b_id), "'b' gained an execute bit")
 
@@ -458,8 +463,7 @@
         # Now make sure that 'bzr branch' also preserves the
         # executable bit
         # TODO: Maybe this should be a blackbox test
-        from bzrlib.clone import copy_branch
-        copy_branch(b, 'b2', revision='r1')
+        b.clone('b2', revision='r1')
         b2 = Branch.open('b2')
         self.assertEquals('r1', b2.last_revision())
         t2 = b2.working_tree()

=== modified file 'bzrlib/tests/test_merge.py'
--- bzrlib/tests/test_merge.py	
+++ bzrlib/tests/test_merge.py	
@@ -52,7 +52,7 @@
         br1, br2 = self.test_pending_with_null()
         commit(br1, "blah")
         last = br1.last_revision()
-        self.assertEquals(common_ancestor(last, last, br1), last)
+        self.assertEquals(common_ancestor(last, last, br1.repository), last)
 
     def test_create_rename(self):
         """Rename an inventory entry while creating the file"""

=== modified file 'bzrlib/tests/test_merge_core.py'
--- bzrlib/tests/test_merge_core.py	
+++ bzrlib/tests/test_merge_core.py	
@@ -15,7 +15,6 @@
                                BackupBeforeChange, ExecFlagMerge, WeaveMerge)
 from bzrlib.changeset import Inventory, apply_changeset, invert_dict, \
     get_contents, ReplaceContents, ChangeExecFlag
-from bzrlib.clone import copy_branch
 from bzrlib.merge import merge
 from bzrlib.workingtree import WorkingTree
 
@@ -537,7 +536,6 @@
     def test_trivial_star_merge(self):
         """Test that merges in a star shape Just Work.""" 
         from bzrlib.add import smart_add_tree
-        from bzrlib.clone import copy_branch
         from bzrlib.merge import merge
         # John starts a branch
         self.build_tree(("original/", "original/file1", "original/file2"))
@@ -547,7 +545,7 @@
         tree.commit("start branch.", verbose=False)
         # Mary branches it.
         self.build_tree(("mary/",))
-        copy_branch(branch, "mary")
+        branch.clone("mary")
         # Now John commits a change
         file = open("original/file1", "wt")
         file.write("John\n")
@@ -577,7 +575,7 @@
         file('a/file', 'wb').write('contents\n')
         a.working_tree().add('file')
         a.working_tree().commit('base revision', allow_pointless=False)
-        b = copy_branch(a, 'b')
+        b = a.clone('b')
         file('a/file', 'wb').write('other contents\n')
         a.working_tree().commit('other revision', allow_pointless=False)
         file('b/file', 'wb').write('this contents contents\n')
@@ -659,7 +657,7 @@
         a_wt = a.working_tree()
         a_wt.add('file')
         a_wt.commit('r0')
-        copy_branch(a, 'b')
+        a.clone('b')
         b = Branch.open('b')
         b_wt = b.working_tree()
         os.chmod('b/file', 0755)
@@ -680,12 +678,12 @@
         a_wt.add('un')
         a_wt.add('deux')
         a_wt.commit('r0')
-        copy_branch(a,'b')
+        a.clone('b')
         b = Branch.open('b')
         b_wt = b.working_tree()
-        b_wt.rename_one('un','tmp')
-        b_wt.rename_one('deux','un')
-        b_wt.rename_one('tmp','deux')
+        b_wt.rename_one('un', 'tmp')
+        b_wt.rename_one('deux', 'un')
+        b_wt.rename_one('tmp', 'deux')
         b_wt.commit('r1')
         merge(['b', -1],['b', 1],this_dir='a')
         self.assert_(os.path.exists('a/un'))
@@ -701,7 +699,7 @@
         a_wt = a.working_tree()
         a_wt.add('file')
         a_wt.commit('r0')
-        copy_branch(a, 'b')
+        a.clone('b')
         b = Branch.open('b')
         b_wt = b.working_tree()
         os.remove('b/file')
@@ -733,7 +731,7 @@
         a_wt = a.working_tree()
         a_wt.add('foo')
         a_wt.commit('added foo')
-        copy_branch(a, 'b')
+        a.clone('b')
         b = Branch.open('b')
         b_wt = b.working_tree()
         b_wt.rename_one('foo', 'bar')
@@ -763,7 +761,7 @@
         a_wt = a.working_tree()
         a_wt.add('foo')
         a_wt.commit('added foo')
-        copy_branch(a, 'b')
+        a.clone('b')
         b = Branch.open('b')
         b_wt = b.working_tree()
         os.mkdir('b/bar')
@@ -795,7 +793,7 @@
         a_wt.add('foo')
         a_wt.add('foo/bar')
         a_wt.commit('added foo/bar')
-        copy_branch(a, 'b')
+        a.clone('b')
         b = Branch.open('b')
         b_wt = b.working_tree()
         b_wt.rename_one('foo/bar', 'bar')
@@ -827,7 +825,7 @@
         a_wt.add('foo')
         a_wt.add('bar')
         a_wt.commit('added foo and bar')
-        copy_branch(a, 'b')
+        a.clone('b')
         b = Branch.open('b')
         b_wt = b.working_tree()
         os.unlink('b/foo')

=== modified file 'bzrlib/tests/test_parent.py'
--- bzrlib/tests/test_parent.py	
+++ bzrlib/tests/test_parent.py	
@@ -16,9 +16,8 @@
 
 
 import os
+from bzrlib.branch import Branch
 from bzrlib.tests import TestCaseInTempDir
-from bzrlib.branch import Branch
-from bzrlib.clone import copy_branch
 from bzrlib.osutils import abspath, realpath
 
 
@@ -26,12 +25,12 @@
 
 
 class TestParent(TestCaseInTempDir):
+
     def test_no_default_parent(self):
         """Branches should have no parent by default"""
         b = Branch.initialize(u'.')
         self.assertEquals(b.get_parent(), None)
         
-    
     def test_set_get_parent(self):
         """Set and then re-get the parent"""
         b = Branch.initialize(u'.')
@@ -50,7 +49,7 @@
         branch_from.working_tree().commit('initial commit')
         
         os.mkdir('to')
-        copy_branch(branch_from, 'to', None)
+        branch_from.clone('to', None)
 
         branch_to = Branch.open('to')
         self.assertEquals(branch_to.get_parent(), branch_from.base)

=== modified file 'bzrlib/tests/test_permissions.py'
--- bzrlib/tests/test_permissions.py	
+++ bzrlib/tests/test_permissions.py	
@@ -23,19 +23,23 @@
 
 In the future, when we have Repository/Branch/Checkout information, the
 permissions should be inherited individually, rather than all be the same.
-
-TODO: jam 20051215 There are no tests for ftp yet, because we have no ftp server
-TODO: jam 20051215 Currently the default behavior for 'bzr branch' is just 
-                   defined by the local umask. This isn't terrible, is it
-                   the truly desired behavior?
 """
 
+# TODO: jam 20051215 There are no tests for ftp yet, because we have no ftp server
+# TODO: jam 20051215 Currently the default behavior for 'bzr branch' is just 
+#                    defined by the local umask. This isn't terrible, is it
+#                    the truly desired behavior?
+ 
 import os
 import sys
 import stat
+from StringIO import StringIO
 
 from bzrlib.branch import Branch
+from bzrlib.lockable_files import LockableFiles
 from bzrlib.tests import TestCaseInTempDir, TestSkipped
+from bzrlib.transport import get_transport
+
 from bzrlib.tests.test_sftp_transport import TestCaseWithSFTPServer
 from bzrlib.transport import get_transport
 
@@ -99,8 +103,8 @@
 
         b = Branch.open('.')
         t = b.working_tree()
-        assertEqualMode(self, 0755, b._dir_mode)
-        assertEqualMode(self, 0644, b._file_mode)
+        assertEqualMode(self, 0755, b.control_files._dir_mode)
+        assertEqualMode(self, 0644, b.control_files._file_mode)
 
         # Modifying a file shouldn't break the permissions
         open('a', 'wb').write('foo2\n')
@@ -120,8 +124,8 @@
         check_mode_r(self, '.bzr', 0664, 0775)
         b = Branch.open('.')
         t = b.working_tree()
-        assertEqualMode(self, 0775, b._dir_mode)
-        assertEqualMode(self, 0664, b._file_mode)
+        assertEqualMode(self, 0775, b.control_files._dir_mode)
+        assertEqualMode(self, 0664, b.control_files._file_mode)
 
         open('a', 'wb').write('foo3\n')
         t.commit('foo3')
@@ -139,8 +143,8 @@
         check_mode_r(self, '.bzr', 0664, 02775)
         b = Branch.open('.')
         t = b.working_tree()
-        assertEqualMode(self, 02775, b._dir_mode)
-        assertEqualMode(self, 0664, b._file_mode)
+        assertEqualMode(self, 02775, b.control_files._dir_mode)
+        assertEqualMode(self, 0664, b.control_files._file_mode)
 
         open('a', 'wb').write('foo4\n')
         t.commit('foo4')
@@ -154,64 +158,71 @@
     def test_disable_set_mode(self):
         # TODO: jam 20051215 Ultimately, this test should probably test that
         #                    extra chmod calls aren't being made
-        import bzrlib.branch
         try:
-            b = Branch.initialize(u'.')
-            self.assertNotEqual(None, b._dir_mode)
-            self.assertNotEqual(None, b._file_mode)
-
-            bzrlib.branch.BzrBranch._set_dir_mode = False
-            b = Branch.open(u'.')
-            self.assertEqual(None, b._dir_mode)
-            self.assertNotEqual(None, b._file_mode)
-
-            bzrlib.branch.BzrBranch._set_file_mode = False
-            b = Branch.open(u'.')
-            self.assertEqual(None, b._dir_mode)
-            self.assertEqual(None, b._file_mode)
-
-            bzrlib.branch.BzrBranch._set_dir_mode = True
-            b = Branch.open(u'.')
-            self.assertNotEqual(None, b._dir_mode)
-            self.assertEqual(None, b._file_mode)
-
-            bzrlib.branch.BzrBranch._set_file_mode = True
-            b = Branch.open(u'.')
-            self.assertNotEqual(None, b._dir_mode)
-            self.assertNotEqual(None, b._file_mode)
+            transport = get_transport('.')
+            transport.put('my-lock', StringIO(''))
+            lockable = LockableFiles(transport, 'my-lock')
+            self.assertNotEqual(None, lockable._dir_mode)
+            self.assertNotEqual(None, lockable._file_mode)
+
+            LockableFiles._set_dir_mode = False
+            transport = get_transport('.')
+            lockable = LockableFiles(transport, 'my-lock')
+            self.assertEqual(None, lockable._dir_mode)
+            self.assertNotEqual(None, lockable._file_mode)
+
+            LockableFiles._set_file_mode = False
+            transport = get_transport('.')
+            lockable = LockableFiles(transport, 'my-lock')
+            self.assertEqual(None, lockable._dir_mode)
+            self.assertEqual(None, lockable._file_mode)
+
+            LockableFiles._set_dir_mode = True
+            transport = get_transport('.')
+            lockable = LockableFiles(transport, 'my-lock')
+            self.assertNotEqual(None, lockable._dir_mode)
+            self.assertEqual(None, lockable._file_mode)
+
+            LockableFiles._set_file_mode = True
+            transport = get_transport('.')
+            lockable = LockableFiles(transport, 'my-lock')
+            self.assertNotEqual(None, lockable._dir_mode)
+            self.assertNotEqual(None, lockable._file_mode)
         finally:
-            bzrlib.branch.BzrBranch._set_dir_mode = True
-            bzrlib.branch.BzrBranch._set_file_mode = True
+            LockableFiles._set_dir_mode = True
+            LockableFiles._set_file_mode = True
 
     def test_new_branch(self):
         if sys.platform == 'win32':
             raise TestSkipped('chmod has no effect on win32')
-
+        #FIXME RBC 20060105 should test branch and repository 
+        # permissions ? 
+        # also, these are BzrBranch format specific things..
         os.mkdir('a')
         mode = stat.S_IMODE(os.stat('a').st_mode)
         b = Branch.initialize('a')
-        assertEqualMode(self, mode, b._dir_mode)
-        assertEqualMode(self, mode & ~07111, b._file_mode)
+        assertEqualMode(self, mode, b.control_files._dir_mode)
+        assertEqualMode(self, mode & ~07111, b.control_files._file_mode)
 
         os.mkdir('b')
         os.chmod('b', 02777)
         b = Branch.initialize('b')
-        assertEqualMode(self, 02777, b._dir_mode)
-        assertEqualMode(self, 00666, b._file_mode)
+        assertEqualMode(self, 02777, b.control_files._dir_mode)
+        assertEqualMode(self, 00666, b.control_files._file_mode)
         check_mode_r(self, 'b/.bzr', 00666, 02777)
 
         os.mkdir('c')
         os.chmod('c', 02750)
         b = Branch.initialize('c')
-        assertEqualMode(self, 02750, b._dir_mode)
-        assertEqualMode(self, 00640, b._file_mode)
+        assertEqualMode(self, 02750, b.control_files._dir_mode)
+        assertEqualMode(self, 00640, b.control_files._file_mode)
         check_mode_r(self, 'c/.bzr', 00640, 02750)
 
         os.mkdir('d')
         os.chmod('d', 0700)
         b = Branch.initialize('d')
-        assertEqualMode(self, 0700, b._dir_mode)
-        assertEqualMode(self, 0600, b._file_mode)
+        assertEqualMode(self, 0700, b.control_files._dir_mode)
+        assertEqualMode(self, 0600, b.control_files._file_mode)
         check_mode_r(self, 'd/.bzr', 00600, 0700)
 
 
@@ -244,8 +255,8 @@
 
         b_local = Branch.open(u'local')
         t_local = b_local.working_tree()
-        assertEqualMode(self, 0755, b_local._dir_mode)
-        assertEqualMode(self, 0644, b_local._file_mode)
+        assertEqualMode(self, 0755, b_local.control_files._dir_mode)
+        assertEqualMode(self, 0644, b_local.control_files._file_mode)
 
         os.mkdir('sftp')
         sftp_url = self.get_remote_url('sftp')
@@ -257,8 +268,8 @@
         check_mode_r(self, 'sftp/.bzr', 0644, 0755)
 
         b_sftp = Branch.open(sftp_url)
-        assertEqualMode(self, 0755, b_sftp._dir_mode)
-        assertEqualMode(self, 0644, b_sftp._file_mode)
+        assertEqualMode(self, 0755, b_sftp.control_files._dir_mode)
+        assertEqualMode(self, 0644, b_sftp.control_files._file_mode)
 
         open('local/a', 'wb').write('foo2\n')
         t_local.commit('foo2')
@@ -278,8 +289,8 @@
         check_mode_r(self, 'sftp/.bzr', 0664, 0775)
 
         b_sftp = Branch.open(sftp_url)
-        assertEqualMode(self, 0775, b_sftp._dir_mode)
-        assertEqualMode(self, 0664, b_sftp._file_mode)
+        assertEqualMode(self, 0775, b_sftp.control_files._dir_mode)
+        assertEqualMode(self, 0664, b_sftp.control_files._file_mode)
 
         open('local/a', 'wb').write('foo3\n')
         t_local.commit('foo3')

=== modified file 'bzrlib/tests/test_revision.py'
--- bzrlib/tests/test_revision.py	
+++ bzrlib/tests/test_revision.py	
@@ -112,9 +112,9 @@
                 if rev_id in br2_only and not branch is br2:
                     continue
                 mutter('ancestry of {%s}: %r',
-                       rev_id, branch.get_ancestry(rev_id))
-                self.assertEquals(sorted(branch.get_ancestry(rev_id)),
-                                  [None] + sorted(anc))
+                       rev_id, branch.repository.get_ancestry(rev_id))
+                result = sorted(branch.repository.get_ancestry(rev_id))
+                self.assertEquals(result, [None] + sorted(anc))
     
     
     def test_is_ancestor(self):
@@ -157,7 +157,8 @@
         self.br2.working_tree().commit("Commit fifteen", rev_id="b at u-0-10")
 
         from bzrlib.revision import MultipleRevisionSources
-        self.sources = MultipleRevisionSources(self.br1, self.br2)
+        self.sources = MultipleRevisionSources(self.br1.repository,
+                                               self.br2.repository)
 
     def intervene(self, ancestor, revision, revision_history=None):
         from bzrlib.revision import get_intervening_revisions
@@ -214,7 +215,7 @@
         br1, br2 = make_branches(self)
         revisions = br1.revision_history()
         revisions_2 = br2.revision_history()
-        sources = br1
+        sources = br1.repository
 
         expected_ancestors_list = {revisions[3]:(0, 0), 
                                    revisions[2]:(1, 1),
@@ -253,7 +254,7 @@
         br1, br2 = make_branches(self)
         revisions = br1.revision_history()
         revisions_2 = br2.revision_history()
-        sources = MultipleRevisionSources(br1, br2)
+        sources = MultipleRevisionSources(br1.repository, br2.repository)
         expected_ancestors_list = {revisions[3]:(0, 0), 
                                    revisions[2]:(1, 1),
                                    revisions_2[4]:(2, 1), 
@@ -288,7 +289,7 @@
         Ensure it's not order-sensitive
         """
         br1, br2 = make_branches(self)
-        source = MultipleRevisionSources(br1, br2)
+        source = MultipleRevisionSources(br1.repository, br2.repository)
         combined_1 = combined_graph(br1.last_revision(), 
                                     br2.last_revision(), source)
         combined_2 = combined_graph(br2.last_revision(),

=== modified file 'bzrlib/tests/test_revisionnamespaces.py'
--- bzrlib/tests/test_revisionnamespaces.py	
+++ bzrlib/tests/test_revisionnamespaces.py	
@@ -21,7 +21,6 @@
 from bzrlib.tests import TestCaseInTempDir
 from bzrlib.errors import NoCommonAncestor, NoCommits
 from bzrlib.errors import NoSuchRevision
-from bzrlib.clone import copy_branch
 from bzrlib.merge import merge
 from bzrlib.revisionspec import RevisionSpec
 
@@ -69,7 +68,7 @@
         self.assertRaises(NoCommits, RevisionSpec('ancestor:.').in_history, b2)
 
         os.mkdir('copy')
-        b3 = copy_branch(b, 'copy')
+        b3 = b.clone('copy')
         b3.working_tree().commit('Commit four', rev_id='b at r-0-4')
         self.assertEquals(RevisionSpec('ancestor:.').in_history(b3).rev_id,
                           'a at r-0-3')
@@ -90,7 +89,7 @@
         branch = Branch.initialize('branch1')
         branch.working_tree().add(['file'])
         branch.working_tree().commit('add file')
-        copy_branch(branch, 'branch2')
+        branch.clone('branch2')
         print >> open('branch2/file', 'w'), 'new content'
         branch2 = Branch.open('branch2')
         branch2.working_tree().commit('update file', rev_id='A')

=== modified file 'bzrlib/tests/test_revprops.py'
--- bzrlib/tests/test_revprops.py	
+++ bzrlib/tests/test_revprops.py	
@@ -16,7 +16,7 @@
                  revprops=props,
                  allow_pointless=True,
                  rev_id='test at user-1')
-        rev = b.get_revision('test at user-1')
+        rev = b.repository.get_revision('test at user-1')
         self.assertTrue('flavor' in rev.properties)
         self.assertEquals(rev.properties['flavor'], 'choc-mint')
         self.assertEquals(sorted(rev.properties.items()),

=== modified file 'bzrlib/tests/test_status.py'
--- bzrlib/tests/test_status.py	
+++ bzrlib/tests/test_status.py	
@@ -31,7 +31,6 @@
 from bzrlib.merge import merge
 from bzrlib.status import show_status
 from bzrlib.branch import Branch
-from bzrlib.clone import copy_branch
 
 class BranchStatus(TestCaseInTempDir):
     
@@ -111,7 +110,7 @@
         mkdir("./branch")
         b = Branch.initialize('./branch')
         b.working_tree().commit("Empty commit 1")
-        b_2 = copy_branch(b, './copy')
+        b_2 = b.clone('./copy')
         b.working_tree().commit(u"\N{TIBETAN DIGIT TWO} Empty commit 2")
         merge(["./branch", -1], [None, None], this_dir = './copy')
         message = self.status_string(b_2)

=== modified file 'bzrlib/tests/test_testament.py'
--- bzrlib/tests/test_testament.py	
+++ bzrlib/tests/test_testament.py	
@@ -53,7 +53,7 @@
 
     def test_null_testament(self):
         """Testament for a revision with no contents."""
-        t = Testament.from_revision(self.b, 'test at user-1')
+        t = Testament.from_revision(self.b.repository, 'test at user-1')
         ass = self.assertTrue
         eq = self.assertEqual
         ass(isinstance(t, Testament))
@@ -64,14 +64,14 @@
 
     def test_testment_text_form(self):
         """Conversion of testament to canonical text form."""
-        t = Testament.from_revision(self.b, 'test at user-1')
+        t = Testament.from_revision(self.b.repository, 'test at user-1')
         text_form = t.as_text()
         self.log('testament text form:\n' + text_form)
         self.assertEqual(text_form, REV_1_TESTAMENT)
 
     def test_testament_with_contents(self):
         """Testament containing a file and a directory."""
-        t = Testament.from_revision(self.b, 'test at user-2')
+        t = Testament.from_revision(self.b.repository, 'test at user-2')
         text_form = t.as_text()
         self.log('testament text form:\n' + text_form)
         self.assertEqualDiff(text_form, REV_2_TESTAMENT)
@@ -101,7 +101,7 @@
                  timezone=36000,
                  rev_id='test at user-3',
                  committer='test at user')
-        t = Testament.from_revision(self.b, 'test at user-3')
+        t = Testament.from_revision(self.b.repository, 'test at user-3')
         self.assertEqualDiff(t.as_text(), REV_3_TESTAMENT)
 
     def test_testament_revprops(self):
@@ -114,14 +114,14 @@
                       rev_id='test at user-3',
                       committer='test at user',
                       revprops=props)
-        t = Testament.from_revision(self.b, 'test at user-3')
+        t = Testament.from_revision(self.b.repository, 'test at user-3')
         self.assertEqualDiff(t.as_text(), REV_PROPS_TESTAMENT)
 
     def test___init__(self):
-        revision = self.b.get_revision('test at user-2')
-        inventory = self.b.get_inventory('test at user-2')
+        revision = self.b.repository.get_revision('test at user-2')
+        inventory = self.b.repository.get_inventory('test at user-2')
         testament_1 = Testament(revision, inventory).as_short_text()
-        testament_2 = Testament.from_revision(self.b, 
+        testament_2 = Testament.from_revision(self.b.repository, 
                                               'test at user-2').as_short_text()
         self.assertEqual(testament_1, testament_2)
                     

=== modified file 'bzrlib/tests/test_upgrade.py'
--- bzrlib/tests/test_upgrade.py	
+++ bzrlib/tests/test_upgrade.py	
@@ -48,28 +48,28 @@
         eq(rh,
            ['mbp at sourcefrog.net-20051004035611-176b16534b086b3c',
             'mbp at sourcefrog.net-20051004035756-235f2b7dcdddd8dd'])
-        t = b.revision_tree(rh[0])
+        t = b.repository.revision_tree(rh[0])
         foo_id = 'foo-20051004035605-91e788d1875603ae'
         eq(t.get_file_text(foo_id), 'initial contents\n')
-        t = b.revision_tree(rh[1])
+        t = b.repository.revision_tree(rh[1])
         eq(t.get_file_text(foo_id), 'new contents\n')
 
     def test_upgrade_with_ghosts(self):
         """Upgrade v0.0.4 tree containing ghost references.
 
         That is, some of the parents of revisions mentioned in the branch
-        aren't present in the branches storage. 
+        aren't present in the branch's storage. 
 
         This shouldn't normally happen in branches created entirely in 
-        bzr but can happen in imports from baz and arch, or from other  
-        systems, where the importer knows about a revision but not 
+        bzr, but can happen in branches imported from baz and arch, or from
+        other systems, where the importer knows about a revision but not 
         its contents."""
         eq = self.assertEquals
         self.build_tree_contents(_ghost_template)
         upgrade(u'.')
         b = Branch.open(u'.')
         revision_id = b.revision_history()[1]
-        rev = b.get_revision(revision_id)
+        rev = b.repository.get_revision(revision_id)
         eq(len(rev.parent_ids), 2)
         eq(rev.parent_ids[1], 'wibble at wobble-2')
 

=== modified file 'bzrlib/tests/test_whitebox.py'
--- bzrlib/tests/test_whitebox.py	
+++ bzrlib/tests/test_whitebox.py	
@@ -54,7 +54,7 @@
         revid = b.revision_history()[0]
         self.log('first revision_id is {%s}' % revid)
         
-        inv = b.get_revision_inventory(revid)
+        inv = b.repository.get_revision_inventory(revid)
         self.log('contents of inventory: %r' % inv.entries())
 
         self.check_inventory_shape(inv,

=== modified file 'bzrlib/tests/test_workingtree.py'
--- bzrlib/tests/test_workingtree.py	
+++ bzrlib/tests/test_workingtree.py	
@@ -102,15 +102,13 @@
         branch = Branch.initialize(u'.')
         tree = WorkingTree(branch.base)
         tree.lock_read()
-        self.assertEqual(1, tree.branch._lock_count)
-        self.assertEqual('r', tree.branch._lock_mode)
+        self.assertEqual('r', tree.branch.peek_lock_mode())
         tree.unlock()
-        self.assertEqual(None, tree.branch._lock_count)
+        self.assertEqual(None, tree.branch.peek_lock_mode())
         tree.lock_write()
-        self.assertEqual(1, tree.branch._lock_count)
-        self.assertEqual('w', tree.branch._lock_mode)
+        self.assertEqual('w', tree.branch.peek_lock_mode())
         tree.unlock()
-        self.assertEqual(None, tree.branch._lock_count)
+        self.assertEqual(None, tree.branch.peek_lock_mode())
  
     def get_pullable_branches(self):
         self.build_tree(['from/', 'from/file', 'to/'])
@@ -124,7 +122,7 @@
     def test_pull(self):
         br_a, br_b = self.get_pullable_branches()
         br_b.working_tree().pull(br_a)
-        self.failUnless(br_b.has_revision('A'))
+        self.failUnless(br_b.repository.has_revision('A'))
         self.assertEqual(['A'], br_b.revision_history())
 
     def test_pull_overwrites(self):
@@ -132,8 +130,8 @@
         br_b.working_tree().commit('foo', rev_id='B')
         self.assertEqual(['B'], br_b.revision_history())
         br_b.working_tree().pull(br_a, overwrite=True)
-        self.failUnless(br_b.has_revision('A'))
-        self.failUnless(br_b.has_revision('B'))
+        self.failUnless(br_b.repository.has_revision('A'))
+        self.failUnless(br_b.repository.has_revision('B'))
         self.assertEqual(['A'], br_b.revision_history())
 
     def test_revert(self):

=== modified file 'bzrlib/transport/__init__.py'
--- bzrlib/transport/__init__.py	
+++ bzrlib/transport/__init__.py	
@@ -431,6 +431,10 @@
 
 
 def get_transport(base):
+    """Open a transport to access a URL or directory.
+
+    base is either a URL or a directory name.  
+    """
     global _protocol_handlers
     if base is None:
         base = u'.'

=== modified file 'bzrlib/uncommit.py'
--- bzrlib/uncommit.py	
+++ bzrlib/uncommit.py	
@@ -27,16 +27,16 @@
         revno = len(rh)
 
     files_to_remove = []
-    new_rev_history = AtomicFile(branch.controlfilename('revision-history'))
+    new_rev_history = AtomicFile(branch.control_files.controlfilename('revision-history'))
     for r in range(revno-1, len(rh)):
         rev_id = rh.pop()
         if verbose:
             print 'Removing revno %d: %s' % (len(rh)+1, rev_id)
-        rev = branch.get_revision(rev_id)
-        inv = branch.get_revision_inventory(rev_id)
+        rev = branch.repository.get_revision(rev_id)
+        inv = branch.repository.get_revision_inventory(rev_id)
         inv_prev = []
         for p in rev.parent_ids:
-            inv_prev.append(branch.get_revision_inventory(p))
+            inv_prev.append(branch.repository.get_revision_inventory(p))
 
         if remove_files:
             # Figure out what text-store entries are new

=== modified file 'bzrlib/upgrade.py'
--- bzrlib/upgrade.py	
+++ bzrlib/upgrade.py	
@@ -102,11 +102,19 @@
         self.pb = ui_factory.progress_bar()
         if self.old_format == 4:
             note('starting upgrade from format 4 to 5')
-            self._convert_to_weaves()
+            self.branch.lock_write()
+            try:
+                self._convert_to_weaves()
+            finally:
+                self.branch.unlock()
             self._open_branch()
         if self.old_format == 5:
             note('starting upgrade from format 5 to 6')
-            self._convert_to_prefixed()
+            self.branch.lock_write()
+            try:
+                self._convert_to_prefixed()
+            finally:
+                self.branch.unlock()
             self._open_branch()
         cache = hashcache.HashCache(abspath(self.base))
         cache.clear()
@@ -139,7 +147,7 @@
         self.inv_weave = Weave('inventory')
         # holds in-memory weaves for all files
         self.text_weaves = {}
-        os.remove(self.branch.controlfilename('branch-format'))
+        os.remove(self.branch.control_files.controlfilename('branch-format'))
         self._convert_working_inv()
         rev_history = self.branch.revision_history()
         # to_read is a stack holding the revisions we still need to process;
@@ -178,21 +186,18 @@
                            self.branch._branch_format)
         return True
 
-
     def _set_new_format(self, format):
-        self.branch.put_controlfile('branch-format', format)
-
+        self.branch.control_files.put_utf8('branch-format', format)
 
     def _cleanup_spare_files(self):
         for n in 'merged-patches', 'pending-merged-patches':
-            p = self.branch.controlfilename(n)
+            p = self.branch.control_files.controlfilename(n)
             if not os.path.exists(p):
                 continue
             ## assert os.path.getsize(p) == 0
             os.remove(p)
         shutil.rmtree(self.base + '/.bzr/inventory-store')
         shutil.rmtree(self.base + '/.bzr/text-store')
-
 
     def _backup_control_dir(self):
         orig = self.base + '/.bzr'
@@ -203,14 +208,11 @@
         note('if conversion fails, you can move this directory back to .bzr')
         note('if it succeeds, you can remove this directory if you wish')
 
-
     def _convert_working_inv(self):
         branch = self.branch
-        inv = serializer_v4.read_inventory(branch.controlfile('inventory', 'rb'))
+        inv = serializer_v4.read_inventory(branch.control_files.controlfile('inventory', 'rb'))
         new_inv_xml = serializer_v5.write_inventory_to_string(inv)
-        branch.put_controlfile('inventory', new_inv_xml)
-
-
+        branch.control_files.put_utf8('inventory', new_inv_xml)
 
     def _write_all_weaves(self):
         write_a_weave(self.inv_weave, self.base + '/.bzr/inventory.weave')
@@ -248,14 +250,14 @@
         self.pb.update('loading revision',
                        len(self.revisions),
                        len(self.known_revisions))
-        if not self.branch.revision_store.has_id(rev_id):
+        if not self.branch.repository.revision_store.has_id(rev_id):
             self.pb.clear()
             note('revision {%s} not present in branch; '
                  'will be converted as a ghost',
                  rev_id)
             self.absent_revisions.add(rev_id)
         else:
-            rev_xml = self.branch.revision_store.get(rev_id).read()
+            rev_xml = self.branch.repository.revision_store.get(rev_id).read()
             rev = serializer_v4.read_revision_from_string(rev_xml)
             for parent_id in rev.parent_ids:
                 self.known_revisions.add(parent_id)
@@ -265,7 +267,7 @@
 
     def _load_old_inventory(self, rev_id):
         assert rev_id not in self.converted_revs
-        old_inv_xml = self.branch.inventory_store.get(rev_id).read()
+        old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
         inv = serializer_v4.read_inventory_from_string(old_inv_xml)
         rev = self.revisions[rev_id]
         if rev.inventory_sha1:
@@ -360,7 +362,8 @@
                 return
         parent_indexes = map(w.lookup, previous_revisions)
         if ie.has_text():
-            file_lines = self.branch.text_store.get(ie.text_id).readlines()
+            text = self.branch.repository.text_store.get(ie.text_id)
+            file_lines = text.readlines()
             assert sha_strings(file_lines) == ie.text_sha1
             assert sum(map(len, file_lines)) == ie.text_size
             w.add(rev_id, parent_indexes, file_lines, ie.text_sha1)

=== modified file 'bzrlib/workingtree.py'
--- bzrlib/workingtree.py	
+++ bzrlib/workingtree.py	
@@ -49,14 +49,13 @@
  
 from bzrlib.branch import (Branch,
                            is_control_file,
-                           needs_read_lock,
-                           needs_write_lock,
                            quotefn)
 from bzrlib.errors import (BzrCheckError,
                            BzrError,
                            DivergedBranches,
                            WeaveRevisionNotPresent,
                            NotBranchError,
+                           NoSuchFile,
                            NotVersionedError)
 from bzrlib.inventory import InventoryEntry
 from bzrlib.osutils import (appendpath,
@@ -77,6 +76,7 @@
 import bzrlib.tree
 from bzrlib.trace import mutter
 import bzrlib.xml5
+from bzrlib.decorators import needs_read_lock, needs_write_lock
 
 
 def gen_file_id(name):
@@ -410,23 +410,25 @@
         if updated:
             self.set_pending_merges(p)
 
+    @needs_read_lock
     def pending_merges(self):
         """Return a list of pending merges.
 
         These are revisions that have been merged into the working
         directory but not yet committed.
         """
-        cfn = self.branch._rel_controlfilename('pending-merges')
-        if not self.branch._transport.has(cfn):
+        try:
+            f = self.branch.control_files.controlfile('pending-merges', 'r')
+        except NoSuchFile:
             return []
         p = []
-        for l in self.branch.controlfile('pending-merges', 'r').readlines():
+        for l in f.readlines():
             p.append(l.rstrip('\n'))
         return p
 
     @needs_write_lock
     def set_pending_merges(self, rev_list):
-        self.branch.put_controlfile('pending-merges', '\n'.join(rev_list))
+        self.branch.control_files.put_utf8('pending-merges', '\n'.join(rev_list))
 
     def get_symlink_target(self, file_id):
         return os.readlink(self.id2abspath(file_id))
@@ -675,9 +677,10 @@
                     other_revision = old_revision_history[-1]
                 else:
                     other_revision = None
+                repository = self.branch.repository
                 merge_inner(self.branch,
                             self.branch.basis_tree(), 
-                            self.branch.revision_tree(other_revision))
+                            repository.revision_tree(other_revision))
             return count
         finally:
             source.unlock()
@@ -788,31 +791,31 @@
         return 'basis-inventory.%s' % revision_id
 
     def set_last_revision(self, new_revision, old_revision=None):
-        if old_revision:
+        if old_revision is not None:
             try:
                 path = self._basis_inventory_name(old_revision)
-                path = self.branch._rel_controlfilename(path)
-                self.branch._transport.delete(path)
-            except:
+                path = self.branch.control_files._escape(path)
+                self.branch.control_files._transport.delete(path)
+            except NoSuchFile:
                 pass
         try:
-            xml = self.branch.get_inventory_xml(new_revision)
+            xml = self.branch.repository.get_inventory_xml(new_revision)
             path = self._basis_inventory_name(new_revision)
-            self.branch.put_controlfile(path, xml)
+            self.branch.control_files.put_utf8(path, xml)
         except WeaveRevisionNotPresent:
             pass
 
     def read_basis_inventory(self, revision_id):
         """Read the cached basis inventory."""
         path = self._basis_inventory_name(revision_id)
-        return self.branch.controlfile(path, 'r').read()
+        return self.branch.control_files.controlfile(path, 'r').read()
         
     @needs_read_lock
     def read_working_inventory(self):
         """Read the working inventory."""
         # ElementTree does its own conversion from UTF-8, so open in
         # binary.
-        f = self.branch.controlfile('inventory', 'rb')
+        f = self.branch.control_files.controlfile('inventory', 'rb')
         return bzrlib.xml5.serializer_v5.read_inventory(f)
 
     @needs_write_lock
@@ -914,7 +917,12 @@
         between multiple working trees, i.e. via shared storage, then we 
         would probably want to lock both the local tree, and the branch.
         """
-        if self._hashcache.needs_write and self.branch._lock_count==1:
+        # FIXME: We want to write out the hashcache only when the last lock on
+        # this working copy is released.  Peeking at the lock count is a bit
+        # of a nasty hack; probably it's better to have a transaction object,
+        # which can do some finalization when it's either successfully or
+        # unsuccessfully completed.  (Denys's original patch did that.)
+        if self._hashcache.needs_write and self.branch.control_files._lock_count==1:
             self._hashcache.write()
         return self.branch.unlock()
 
@@ -926,7 +934,7 @@
         sio = StringIO()
         bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
         sio.seek(0)
-        f = AtomicFile(self.branch.controlfilename('inventory'))
+        f = AtomicFile(self.branch.control_files.controlfilename('inventory'))
         try:
             pumpfile(sio, f)
             f.commit()

-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: Digital signature
Url : https://lists.ubuntu.com/archives/bazaar/attachments/20060112/eb55c1a1/attachment.pgp 


More information about the bazaar mailing list