Rev 6283: (jelmer) Support HPSS calls for config stacks of remote branches and in file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/

Patch Queue Manager pqm at pqm.ubuntu.com
Tue Nov 22 13:52:59 UTC 2011


At file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 6283 [merge]
revision-id: pqm at pqm.ubuntu.com-20111122135259-dfjq13wb2z4ndbh6
parent: pqm at pqm.ubuntu.com-20111121012651-prdfm12cb04wtdkm
parent: jelmer at samba.org-20111122132704-e8uwuzhpharukn8c
committer: Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Tue 2011-11-22 13:52:59 +0000
message:
  (jelmer) Support HPSS calls for config stacks of remote branches and
   bzrdirs. (Jelmer Vernooij)
modified:
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/config.py               config.py-20051011043216-070c74f4e9e338e8
  bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
  bzrlib/smart/branch.py         branch.py-20061124031907-mzh3pla28r83r97f-1
  bzrlib/smart/request.py        request.py-20061108095550-gunadhxmzkdjfeek-1
  bzrlib/tests/blackbox/test_config.py test_config.py-20100927150753-x6rf54uibd08r636-1
  bzrlib/tests/test_config.py    testconfig.py-20051011041908-742d0c15d8d8c8eb
  bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
  bzrlib/tests/test_smart.py     test_smart.py-20061122024551-ol0l0o0oofsu9b3t-2
  doc/en/release-notes/bzr-2.5.txt bzr2.5.txt-20110708125756-587p0hpw7oke4h05-1
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2011-11-17 18:39:26 +0000
+++ b/bzrlib/branch.py	2011-11-22 11:48:31 +0000
@@ -2470,6 +2470,9 @@
     def _get_config(self):
         return _mod_config.TransportConfig(self._transport, 'branch.conf')
 
+    def _get_config_store(self):
+        return _mod_config.BranchStore(self)
+
     def is_locked(self):
         return self.control_files.is_locked()
 

=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2011-11-18 07:29:20 +0000
+++ b/bzrlib/config.py	2011-11-22 13:27:04 +0000
@@ -2776,16 +2776,10 @@
         serialize/deserialize the config file.
     """
 
-    def __init__(self, transport, file_name):
+    def __init__(self):
         """A config Store using ConfigObj for storage.
-
-        :param transport: The transport object where the config file is located.
-
-        :param file_name: The config file basename in the transport directory.
         """
         super(IniFileStore, self).__init__()
-        self.transport = transport
-        self.file_name = file_name
         self._config_obj = None
 
     def is_loaded(self):
@@ -2794,16 +2788,29 @@
     def unload(self):
         self._config_obj = None
 
+    def _load_content(self):
+        """Load the config file bytes.
+
+        This should be provided by subclasses
+
+        :return: Byte string
+        """
+        raise NotImplementedError(self._load_content)
+
+    def _save_content(self, content):
+        """Save the config file bytes.
+
+        This should be provided by subclasses
+
+        :param content: Config file bytes to write
+        """
+        raise NotImplementedError(self._save_content)
+
     def load(self):
         """Load the store from the associated file."""
         if self.is_loaded():
             return
-        try:
-            content = self.transport.get_bytes(self.file_name)
-        except errors.PermissionDenied:
-            trace.warning("Permission denied while trying to load "
-                          "configuration store %s.", self.external_url())
-            raise
+        content = self._load_content()
         self._load_from_string(content)
         for hook in ConfigHooks['load']:
             hook(self)
@@ -2832,18 +2839,10 @@
             return
         out = StringIO()
         self._config_obj.write(out)
-        self.transport.put_bytes(self.file_name, out.getvalue())
+        self._save_content(out.getvalue())
         for hook in ConfigHooks['save']:
             hook(self)
 
-    def external_url(self):
-        # FIXME: external_url should really accepts an optional relpath
-        # parameter (bug #750169) :-/ -- vila 2011-04-04
-        # The following will do in the interim but maybe we don't want to
-        # expose a path here but rather a config ID and its associated
-        # object </hand wawe>.
-        return urlutils.join(self.transport.external_url(), self.file_name)
-
     def get_sections(self):
         """Get the configobj section in the file order.
 
@@ -2877,13 +2876,47 @@
         return self.mutable_section_class(section_id, section)
 
 
+class TransportIniFileStore(IniFileStore):
+    """IniFileStore that loads files from a transport.
+    """
+
+    def __init__(self, transport, file_name):
+        """A Store using a ini file on a Transport
+
+        :param transport: The transport object where the config file is located.
+        :param file_name: The config file basename in the transport directory.
+        """
+        super(TransportIniFileStore, self).__init__()
+        self.transport = transport
+        self.file_name = file_name
+
+    def _load_content(self):
+        try:
+            return self.transport.get_bytes(self.file_name)
+        except errors.PermissionDenied:
+            trace.warning("Permission denied while trying to load "
+                          "configuration store %s.", self.external_url())
+            raise
+
+    def _save_content(self, content):
+        self.transport.put_bytes(self.file_name, content)
+
+    def external_url(self):
+        # FIXME: external_url should really accepts an optional relpath
+        # parameter (bug #750169) :-/ -- vila 2011-04-04
+        # The following will do in the interim but maybe we don't want to
+        # expose a path here but rather a config ID and its associated
+        # object </hand wawe>.
+        return urlutils.join(self.transport.external_url(), self.file_name)
+
+
 # Note that LockableConfigObjStore inherits from ConfigObjStore because we need
 # unlockable stores for use with objects that can already ensure the locking
 # (think branches). If different stores (not based on ConfigObj) are created,
 # they may face the same issue.
 
 
-class LockableIniFileStore(IniFileStore):
+class LockableIniFileStore(TransportIniFileStore):
     """A ConfigObjStore using locks on save to ensure store integrity."""
 
     def __init__(self, transport, file_name, lock_dir_name=None):
@@ -2951,7 +2984,7 @@
         self.id = 'locations'
 
 
-class BranchStore(IniFileStore):
+class BranchStore(TransportIniFileStore):
 
     def __init__(self, branch):
         super(BranchStore, self).__init__(branch.control_transport,
@@ -3380,7 +3413,7 @@
     """Per-location options falling back to branch then global options stack."""
 
     def __init__(self, branch):
-        bstore = BranchStore(branch)
+        bstore = branch._get_config_store()
         lstore = LocationStore()
         matcher = LocationMatcher(lstore, branch.base)
         gstore = GlobalStore()
@@ -3395,8 +3428,12 @@
 class RemoteControlStack(_CompatibleStack):
     """Remote control-only options stack."""
 
+    # FIXME 2011-11-22 JRV This should probably be renamed to avoid confusion
+    # with the stack used for remote bzr dirs. RemoteControlStack only uses
+    # control.conf and is used only for stack options.
+
     def __init__(self, bzrdir):
-        cstore = ControlStore(bzrdir)
+        cstore = bzrdir._get_config_store()
         super(RemoteControlStack, self).__init__(
             [cstore.get_sections],
             cstore)
@@ -3406,8 +3443,12 @@
 class RemoteBranchStack(_CompatibleStack):
     """Remote branch-only options stack."""
 
+    # FIXME 2011-11-22 JRV This should probably be renamed to avoid confusion
+    # with the stack used for remote branches. RemoteBranchStack only uses
+    # branch.conf and is used only for the stack options.
+
     def __init__(self, branch):
-        bstore = BranchStore(branch)
+        bstore = branch._get_config_store()
         super(RemoteBranchStack, self).__init__(
             [bstore.get_sections],
             bstore)

=== modified file 'bzrlib/remote.py'
--- a/bzrlib/remote.py	2011-11-18 14:37:41 +0000
+++ b/bzrlib/remote.py	2011-11-22 11:48:31 +0000
@@ -333,6 +333,62 @@
         _mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
 
 
+class RemoteControlStore(config.IniFileStore):
+    """Control store which attempts to use HPSS calls to retrieve control store.
+
+    Note that this is specific to bzr-based formats.
+    """
+
+    def __init__(self, bzrdir):
+        super(RemoteControlStore, self).__init__()
+        self.bzrdir = bzrdir
+        self._real_store = None
+
+    def lock_write(self, token=None):
+        self._ensure_real()
+        return self._real_store.lock_write(token)
+
+    def unlock(self):
+        self._ensure_real()
+        return self._real_store.unlock()
+
+    @needs_write_lock
+    def save(self):
+        # We need to be able to override the undecorated implementation
+        self.save_without_locking()
+
+    def save_without_locking(self):
+        super(RemoteControlStore, self).save()
+
+    def _ensure_real(self):
+        self.bzrdir._ensure_real()
+        if self._real_store is None:
+            self._real_store = config.ControlStore(self.bzrdir)
+
+    def external_url(self):
+        return self.bzrdir.user_url
+
+    def _load_content(self):
+        medium = self.bzrdir._client._medium
+        path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
+        try:
+            response, handler = self.bzrdir._call_expecting_body(
+                'BzrDir.get_config_file', path)
+        except errors.UnknownSmartMethod:
+            self._ensure_real()
+            return self._real_store._load_content()
+        if len(response) and response[0] != 'ok':
+            raise errors.UnexpectedSmartServerResponse(response)
+        return handler.read_body_bytes()
+
+    def _save_content(self, content):
+        # FIXME JRV 2011-11-22: Ideally this should use a
+        # HPSS call too, but at the moment it is not possible
+        # to write lock control directories.
+        self._ensure_real()
+        return self._real_store._save_content(content)
+
+
 class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
     """Control directory on a remote server, accessed via bzr:// or similar."""
 
@@ -718,6 +774,9 @@
     def _get_config(self):
         return RemoteBzrDirConfig(self)
 
+    def _get_config_store(self):
+        return RemoteControlStore(self)
+
 
 class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
     """Format for repositories accessed over a _SmartClient.
@@ -2538,6 +2597,68 @@
                 return True
         return False
 
+
+class RemoteBranchStore(config.IniFileStore):
+    """Branch store which attempts to use HPSS calls to retrieve branch store.
+
+    Note that this is specific to bzr-based formats.
+    """
+
+    def __init__(self, branch):
+        super(RemoteBranchStore, self).__init__()
+        self.branch = branch
+        self.id = "branch"
+        self._real_store = None
+
+    def lock_write(self, token=None):
+        return self.branch.lock_write(token)
+
+    def unlock(self):
+        return self.branch.unlock()
+
+    @needs_write_lock
+    def save(self):
+        # We need to be able to override the undecorated implementation
+        self.save_without_locking()
+
+    def save_without_locking(self):
+        super(RemoteBranchStore, self).save()
+
+    def external_url(self):
+        return self.branch.user_url
+
+    def _load_content(self):
+        path = self.branch._remote_path()
+        try:
+            response, handler = self.branch._call_expecting_body(
+                'Branch.get_config_file', path)
+        except errors.UnknownSmartMethod:
+            self._ensure_real()
+            return self._real_store._load_content()
+        if len(response) and response[0] != 'ok':
+            raise errors.UnexpectedSmartServerResponse(response)
+        return handler.read_body_bytes()
+
+    def _save_content(self, content):
+        path = self.branch._remote_path()
+        try:
+            response, handler = self.branch._call_with_body_bytes_expecting_body(
+                'Branch.put_config_file', (path,
+                    self.branch._lock_token, self.branch._repo_lock_token),
+                content)
+        except errors.UnknownSmartMethod:
+            self._ensure_real()
+            return self._real_store._save_content(content)
+        handler.cancel_read_body()
+        if response != ('ok', ):
+            raise errors.UnexpectedSmartServerResponse(response)
+
+    def _ensure_real(self):
+        self.branch._ensure_real()
+        if self._real_store is None:
+            self._real_store = config.BranchStore(self.branch)
+
+
 class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
     """Branch stored on a server accessed by HPSS RPC.
 
@@ -2632,6 +2753,9 @@
     def _get_config(self):
         return RemoteBranchConfig(self)
 
+    def _get_config_store(self):
+        return RemoteBranchStore(self)
+
     def _get_real_transport(self):
         # if we try vfs access, return the real branch's vfs transport
         self._ensure_real()

=== modified file 'bzrlib/smart/branch.py'
--- a/bzrlib/smart/branch.py	2011-10-14 13:56:45 +0000
+++ b/bzrlib/smart/branch.py	2011-11-22 11:20:10 +0000
@@ -85,12 +85,43 @@
         The body is not utf8 decoded - its the literal bytestream from disk.
         """
         try:
-            content = branch._transport.get_bytes('branch.conf')
+            content = branch.control_transport.get_bytes('branch.conf')
         except errors.NoSuchFile:
             content = ''
         return SuccessfulSmartServerResponse( ('ok', ), content)
 
 
+class SmartServerBranchPutConfigFile(SmartServerBranchRequest):
+    """Set the configuration data for a branch.
+
+    New in 2.5.
+    """
+
+    def do_with_branch(self, branch, branch_token, repo_token):
+        """Set the content of branch.conf.
+
+        The body is not utf8 decoded - its the literal bytestream for disk.
+        """
+        self._branch = branch
+        self._branch_token = branch_token
+        self._repo_token = repo_token
+        # Signal we want a body
+        return None
+
+    def do_body(self, body_bytes):
+        self._branch.repository.lock_write(token=self._repo_token)
+        try:
+            self._branch.lock_write(token=self._branch_token)
+            try:
+                self._branch.control_transport.put_bytes(
+                    'branch.conf', body_bytes)
+            finally:
+                self._branch.unlock()
+        finally:
+            self._branch.repository.unlock()
+        return SuccessfulSmartServerResponse(('ok', ))
+
+
 class SmartServerBranchGetParent(SmartServerBranchRequest):
 
     def do_with_branch(self, branch):

=== modified file 'bzrlib/smart/request.py'
--- a/bzrlib/smart/request.py	2011-11-18 14:37:41 +0000
+++ b/bzrlib/smart/request.py	2011-11-22 11:20:10 +0000
@@ -498,6 +498,9 @@
     'Branch.get_config_file', 'bzrlib.smart.branch',
     'SmartServerBranchGetConfigFile')
 request_handlers.register_lazy(
+    'Branch.put_config_file', 'bzrlib.smart.branch',
+    'SmartServerBranchPutConfigFile')
+request_handlers.register_lazy(
     'Branch.get_parent', 'bzrlib.smart.branch', 'SmartServerBranchGetParent')
 request_handlers.register_lazy(
     'Branch.get_tags_bytes', 'bzrlib.smart.branch',

=== modified file 'bzrlib/tests/blackbox/test_config.py'
--- a/bzrlib/tests/blackbox/test_config.py	2011-11-16 15:57:14 +0000
+++ b/bzrlib/tests/blackbox/test_config.py	2011-11-22 00:19:35 +0000
@@ -17,11 +17,8 @@
 
 """Black-box tests for bzr config."""
 
-import os
-
 from bzrlib import (
     config,
-    errors,
     tests,
     )
 from bzrlib.tests import (
@@ -325,3 +322,18 @@
             bazaar:
               file = bazaar
             ''')
+
+
+class TestSmartServerConfig(tests.TestCaseWithTransport):
+
+    def test_simple_branch_config(self):
+        self.setup_smart_server_with_call_log()
+        t = self.make_branch_and_tree('branch')
+        self.reset_smart_call_log()
+        out, err = self.run_bzr(['config', '-d', self.get_url('branch')])
+        # This figure represent the amount of work to perform this use case. It
+        # is entirely ok to reduce this number if a test fails due to rpc_count
+        # being too low. If rpc_count increases, more network roundtrips have
+        # become necessary for this use case. Please do not adjust this number
+        # upwards without agreement from bzr's network support maintainers.
+        self.assertLength(5, self.hpss_calls)

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2011-11-18 07:29:20 +0000
+++ b/bzrlib/tests/test_config.py	2011-11-22 11:50:36 +0000
@@ -67,8 +67,8 @@
 
 # Register helpers to build stores
 config.test_store_builder_registry.register(
-    'configobj', lambda test: config.IniFileStore(test.get_transport(),
-                                                  'configobj.conf'))
+    'configobj', lambda test: config.TransportIniFileStore(
+        test.get_transport(), 'configobj.conf'))
 config.test_store_builder_registry.register(
     'bazaar', lambda test: config.GlobalStore())
 config.test_store_builder_registry.register(
@@ -2722,7 +2722,7 @@
         utf8_content = unicode_content.encode('utf8')
         # Store the raw content in the config file
         t.put_bytes('foo.conf', utf8_content)
-        store = config.IniFileStore(t, 'foo.conf')
+        store = config.TransportIniFileStore(t, 'foo.conf')
         store.load()
         stack = config.Stack([store.get_sections], store)
         self.assertEquals(unicode_user, stack.get('user'))
@@ -2731,14 +2731,14 @@
         """Ensure we display a proper error on non-ascii, non utf-8 content."""
         t = self.get_transport()
         t.put_bytes('foo.conf', 'user=foo\n#%s\n' % (self.invalid_utf8_char,))
-        store = config.IniFileStore(t, 'foo.conf')
+        store = config.TransportIniFileStore(t, 'foo.conf')
         self.assertRaises(errors.ConfigContentError, store.load)
 
     def test_load_erroneous_content(self):
         """Ensure we display a proper error on content that can't be parsed."""
         t = self.get_transport()
         t.put_bytes('foo.conf', '[open_section\n')
-        store = config.IniFileStore(t, 'foo.conf')
+        store = config.TransportIniFileStore(t, 'foo.conf')
         self.assertRaises(errors.ParseConfigError, store.load)
 
     def test_load_permission_denied(self):
@@ -2753,7 +2753,7 @@
         def get_bytes(relpath):
             raise errors.PermissionDenied(relpath, "")
         t.get_bytes = get_bytes
-        store = config.IniFileStore(t, 'foo.conf')
+        store = config.TransportIniFileStore(t, 'foo.conf')
         self.assertRaises(errors.PermissionDenied, store.load)
         self.assertEquals(
             warnings,
@@ -2913,14 +2913,15 @@
         self.assertEquals((store,), calls[0])
 
 
-class TestIniFileStore(TestStore):
+class TestTransportIniFileStore(TestStore):
 
     def test_loading_unknown_file_fails(self):
-        store = config.IniFileStore(self.get_transport(), 'I-do-not-exist')
+        store = config.TransportIniFileStore(self.get_transport(),
+            'I-do-not-exist')
         self.assertRaises(errors.NoSuchFile, store.load)
 
     def test_invalid_content(self):
-        store = config.IniFileStore(self.get_transport(), 'foo.conf', )
+        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
         self.assertEquals(False, store.is_loaded())
         exc = self.assertRaises(
             errors.ParseConfigError, store._load_from_string,
@@ -2934,7 +2935,7 @@
         # option names share the same name space...)
         # FIXME: This should be fixed by forbidding dicts as values ?
         # -- vila 2011-04-05
-        store = config.IniFileStore(self.get_transport(), 'foo.conf', )
+        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
         store._load_from_string('''
 foo=bar
 l=1,2
@@ -3360,7 +3361,7 @@
         # We just want a simple stack with a simple store so we can inject
         # whatever content the tests need without caring about what section
         # names are valid for a given store/stack.
-        store = config.IniFileStore(self.get_transport(), 'foo.conf')
+        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
         self.conf = config.Stack([store.get_sections], store)
 
     def register_bool_option(self, name, default=None, default_from_env=None):

=== modified file 'bzrlib/tests/test_remote.py'
--- a/bzrlib/tests/test_remote.py	2011-11-18 14:37:41 +0000
+++ b/bzrlib/tests/test_remote.py	2011-11-22 13:27:04 +0000
@@ -1886,6 +1886,61 @@
         self.assertEqual(value_dict, branch._get_config().get_option('name'))
 
 
+class TestBranchGetPutConfigStore(RemoteBranchTestCase):
+
+    def test_get_branch_conf(self):
+        # in an empty branch we decode the response properly
+        client = FakeClient()
+        client.add_expected_call(
+            'Branch.get_stacked_on_url', ('memory:///',),
+            'error', ('NotStacked',),)
+        client.add_success_response_with_body('# config file body', 'ok')
+        transport = MemoryTransport()
+        branch = self.make_remote_branch(transport, client)
+        config = branch.get_config_stack()
+        config.get("email")
+        config.get("log_format")
+        self.assertEqual(
+            [('call', 'Branch.get_stacked_on_url', ('memory:///',)),
+             ('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
+            client._calls)
+
+    def test_set_branch_conf(self):
+        client = FakeClient()
+        client.add_expected_call(
+            'Branch.get_stacked_on_url', ('memory:///',),
+            'error', ('NotStacked',),)
+        client.add_expected_call(
+            'Branch.lock_write', ('memory:///', '', ''),
+            'success', ('ok', 'branch token', 'repo token'))
+        client.add_expected_call(
+            'Branch.get_config_file', ('memory:///', ),
+            'success', ('ok', ), "# line 1\n")
+        client.add_expected_call(
+            'Branch.put_config_file', ('memory:///', 'branch token',
+            'repo token'),
+            'success', ('ok',))
+        client.add_expected_call(
+            'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
+            'success', ('ok',))
+        transport = MemoryTransport()
+        branch = self.make_remote_branch(transport, client)
+        branch.lock_write()
+        config = branch.get_config_stack()
+        config.set('email', 'The Dude <lebowski at example.com>')
+        branch.unlock()
+        self.assertFinished(client)
+        self.assertEqual(
+            [('call', 'Branch.get_stacked_on_url', ('memory:///',)),
+             ('call', 'Branch.lock_write', ('memory:///', '', '')),
+             ('call_expecting_body', 'Branch.get_config_file', ('memory:///',)),
+             ('call_with_body_bytes_expecting_body', 'Branch.put_config_file',
+                 ('memory:///', 'branch token', 'repo token'),
+                 '# line 1\nemail = The Dude <lebowski at example.com>\n'),
+             ('call', 'Branch.unlock', ('memory:///', 'branch token', 'repo token'))],
+            client._calls)
+
+
 class TestBranchLockWrite(RemoteBranchTestCase):
 
     def test_lock_write_unlockable(self):

=== modified file 'bzrlib/tests/test_smart.py'
--- a/bzrlib/tests/test_smart.py	2011-11-18 14:37:41 +0000
+++ b/bzrlib/tests/test_smart.py	2011-11-22 11:20:10 +0000
@@ -817,6 +817,23 @@
         return branch_token, repo_token
 
 
+class TestSmartServerBranchRequestPutConfigFile(TestLockedBranch):
+
+    def test_with_content(self):
+        backing = self.get_transport()
+        request = smart_branch.SmartServerBranchPutConfigFile(backing)
+        branch = self.make_branch('.')
+        branch_token, repo_token = self.get_lock_tokens(branch)
+        self.assertIs(None, request.execute('', branch_token, repo_token))
+        self.assertEqual(
+            smart_req.SmartServerResponse(('ok', )),
+            request.do_body('foo bar baz'))
+        self.assertEquals(
+            branch.control_transport.get_bytes('branch.conf'),
+            'foo bar baz')
+        branch.unlock()
+
+
 class TestSmartServerBranchRequestSetConfigOption(TestLockedBranch):
 
     def test_value_name(self):
@@ -1987,6 +2004,8 @@
         """Test that known methods are registered to the correct object."""
         self.assertHandlerEqual('Branch.get_config_file',
             smart_branch.SmartServerBranchGetConfigFile)
+        self.assertHandlerEqual('Branch.put_config_file',
+            smart_branch.SmartServerBranchPutConfigFile)
         self.assertHandlerEqual('Branch.get_parent',
             smart_branch.SmartServerBranchGetParent)
         self.assertHandlerEqual('Branch.get_tags_bytes',

=== modified file 'doc/en/release-notes/bzr-2.5.txt'
--- a/doc/en/release-notes/bzr-2.5.txt	2011-11-21 00:18:58 +0000
+++ b/doc/en/release-notes/bzr-2.5.txt	2011-11-22 11:58:57 +0000
@@ -81,9 +81,13 @@
 * ``bzr config`` uses the new configuration implementation.
   (Vincent Ladeuil)
 
-* New HPSS calls ``Repository.has_signature_for_revision_id`` and
+* ``RemoteBranch.get_config_stack`` and ``RemoteBzrDir.get_config_stack``
+  will now use HPSS calls where possible. (Jelmer Vernooij)
+
+* New HPSS calls ``Repository.has_signature_for_revision_id``,
   ``Repository.make_working_trees``, ``BzrDir.destroy_repository``,
-  ``BzrDir.has_workingtree``.  (Jelmer Vernooij)
+  ``BzrDir.has_workingtree`` and ``Branch.put_config_file``.
+  (Jelmer Vernooij)
 
 Testing
 *******




More information about the bazaar-commits mailing list