/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/remote.py

  • Committer: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2012 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
import bz2
20
 
import os
21
 
import re
22
 
import sys
23
 
import zlib
24
18
 
25
 
from .. import (
 
19
from bzrlib import (
26
20
    bencode,
27
21
    branch,
28
 
    bzr as _mod_bzr,
29
 
    config as _mod_config,
30
 
    controldir,
 
22
    bzrdir,
 
23
    config,
31
24
    debug,
32
25
    errors,
33
 
    gpg,
34
26
    graph,
35
27
    lock,
36
28
    lockdir,
37
 
    osutils,
38
 
    registry,
 
29
    repository,
39
30
    repository as _mod_repository,
 
31
    revision,
40
32
    revision as _mod_revision,
41
 
    urlutils,
42
 
    )
43
 
from . import (
44
 
    branch as bzrbranch,
45
 
    bzrdir as _mod_bzrdir,
46
 
    inventory_delta,
47
 
    testament as _mod_testament,
48
 
    vf_repository,
49
 
    vf_search,
50
 
    )
51
 
from .branch import BranchReferenceFormat
52
 
from ..branch import BranchWriteLockResult
53
 
from ..decorators import only_raises
54
 
from ..errors import (
 
33
    static_tuple,
 
34
    symbol_versioning,
 
35
)
 
36
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
 
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
 
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
 
39
from bzrlib.errors import (
55
40
    NoSuchRevision,
56
41
    SmartProtocolError,
57
42
    )
58
 
from ..i18n import gettext
59
 
from .inventory import Inventory
60
 
from .inventorytree import InventoryRevisionTree
61
 
from ..lockable_files import LockableFiles
62
 
from ..sixish import (
63
 
    get_unbound_function,
64
 
    map,
65
 
    text_type,
66
 
    viewitems,
67
 
    viewvalues,
68
 
    )
69
 
from .smart import client, vfs, repository as smart_repo
70
 
from .smart.client import _SmartClient
71
 
from ..revision import NULL_REVISION
72
 
from ..repository import RepositoryWriteLockResult, _LazyListJoin
73
 
from .serializer import format_registry as serializer_format_registry
74
 
from ..trace import mutter, note, warning, log_exception_quietly
75
 
from .versionedfile import FulltextContentFactory
76
 
 
77
 
 
78
 
_DEFAULT_SEARCH_DEPTH = 100
 
43
from bzrlib.lockable_files import LockableFiles
 
44
from bzrlib.smart import client, vfs, repository as smart_repo
 
45
from bzrlib.revision import ensure_null, NULL_REVISION
 
46
from bzrlib.repository import RepositoryWriteLockResult
 
47
from bzrlib.trace import mutter, note, warning
79
48
 
80
49
 
81
50
class _RpcHelper(object):
84
53
    def _call(self, method, *args, **err_context):
85
54
        try:
86
55
            return self._client.call(method, *args)
87
 
        except errors.ErrorFromSmartServer as err:
 
56
        except errors.ErrorFromSmartServer, err:
88
57
            self._translate_error(err, **err_context)
89
58
 
90
59
    def _call_expecting_body(self, method, *args, **err_context):
91
60
        try:
92
61
            return self._client.call_expecting_body(method, *args)
93
 
        except errors.ErrorFromSmartServer as err:
 
62
        except errors.ErrorFromSmartServer, err:
94
63
            self._translate_error(err, **err_context)
95
64
 
96
65
    def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
97
66
        try:
98
67
            return self._client.call_with_body_bytes(method, args, body_bytes)
99
 
        except errors.ErrorFromSmartServer as err:
 
68
        except errors.ErrorFromSmartServer, err:
100
69
            self._translate_error(err, **err_context)
101
70
 
102
71
    def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
104
73
        try:
105
74
            return self._client.call_with_body_bytes_expecting_body(
106
75
                method, args, body_bytes)
107
 
        except errors.ErrorFromSmartServer as err:
 
76
        except errors.ErrorFromSmartServer, err:
108
77
            self._translate_error(err, **err_context)
109
78
 
110
79
 
111
80
def response_tuple_to_repo_format(response):
112
81
    """Convert a response tuple describing a repository format to a format."""
113
82
    format = RemoteRepositoryFormat()
114
 
    format._rich_root_data = (response[0] == b'yes')
115
 
    format._supports_tree_reference = (response[1] == b'yes')
116
 
    format._supports_external_lookups = (response[2] == b'yes')
 
83
    format._rich_root_data = (response[0] == 'yes')
 
84
    format._supports_tree_reference = (response[1] == 'yes')
 
85
    format._supports_external_lookups = (response[2] == 'yes')
117
86
    format._network_name = response[3]
118
87
    return format
119
88
 
120
89
 
121
 
# Note that RemoteBzrDirProber lives in breezy.bzrdir so breezy.bzr.remote
122
 
# does not have to be imported unless a remote format is involved.
123
 
 
124
 
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
125
 
    """Format representing bzrdirs accessed via a smart server"""
126
 
 
127
 
    supports_workingtrees = False
128
 
 
129
 
    colocated_branches = False
130
 
 
131
 
    def __init__(self):
132
 
        _mod_bzrdir.BzrDirMetaFormat1.__init__(self)
133
 
        # XXX: It's a bit ugly that the network name is here, because we'd
134
 
        # like to believe that format objects are stateless or at least
135
 
        # immutable,  However, we do at least avoid mutating the name after
136
 
        # it's returned.  See <https://bugs.launchpad.net/bzr/+bug/504102>
137
 
        self._network_name = None
138
 
 
139
 
    def __repr__(self):
140
 
        return "%s(_network_name=%r)" % (self.__class__.__name__,
141
 
                                         self._network_name)
142
 
 
143
 
    def get_format_description(self):
144
 
        if self._network_name:
145
 
            try:
146
 
                real_format = controldir.network_format_registry.get(
147
 
                    self._network_name)
148
 
            except KeyError:
149
 
                pass
150
 
            else:
151
 
                return 'Remote: ' + real_format.get_format_description()
152
 
        return 'bzr remote bzrdir'
153
 
 
154
 
    def get_format_string(self):
155
 
        raise NotImplementedError(self.get_format_string)
156
 
 
157
 
    def network_name(self):
158
 
        if self._network_name:
159
 
            return self._network_name
160
 
        else:
161
 
            raise AssertionError("No network name set.")
162
 
 
163
 
    def initialize_on_transport(self, transport):
164
 
        try:
165
 
            # hand off the request to the smart server
166
 
            client_medium = transport.get_smart_medium()
167
 
        except errors.NoSmartMedium:
168
 
            # TODO: lookup the local format from a server hint.
169
 
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
170
 
            return local_dir_format.initialize_on_transport(transport)
171
 
        client = _SmartClient(client_medium)
172
 
        path = client.remote_path_from_transport(transport)
173
 
        try:
174
 
            response = client.call(b'BzrDirFormat.initialize', path)
175
 
        except errors.ErrorFromSmartServer as err:
176
 
            _translate_error(err, path=path)
177
 
        if response[0] != b'ok':
178
 
            raise errors.SmartProtocolError(
179
 
                'unexpected response code %s' % (response,))
180
 
        format = RemoteBzrDirFormat()
181
 
        self._supply_sub_formats_to(format)
182
 
        return RemoteBzrDir(transport, format)
183
 
 
184
 
    def parse_NoneTrueFalse(self, arg):
185
 
        if not arg:
186
 
            return None
187
 
        if arg == b'False':
188
 
            return False
189
 
        if arg == b'True':
190
 
            return True
191
 
        raise AssertionError("invalid arg %r" % arg)
192
 
 
193
 
    def _serialize_NoneTrueFalse(self, arg):
194
 
        if arg is False:
195
 
            return b'False'
196
 
        if arg:
197
 
            return b'True'
198
 
        return b''
199
 
 
200
 
    def _serialize_NoneString(self, arg):
201
 
        return arg or b''
202
 
 
203
 
    def initialize_on_transport_ex(self, transport, use_existing_dir=False,
204
 
                                   create_prefix=False, force_new_repo=False, stacked_on=None,
205
 
                                   stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
206
 
                                   shared_repo=False):
207
 
        try:
208
 
            # hand off the request to the smart server
209
 
            client_medium = transport.get_smart_medium()
210
 
        except errors.NoSmartMedium:
211
 
            do_vfs = True
212
 
        else:
213
 
            # Decline to open it if the server doesn't support our required
214
 
            # version (3) so that the VFS-based transport will do it.
215
 
            if client_medium.should_probe():
216
 
                try:
217
 
                    server_version = client_medium.protocol_version()
218
 
                    if server_version != '2':
219
 
                        do_vfs = True
220
 
                    else:
221
 
                        do_vfs = False
222
 
                except errors.SmartProtocolError:
223
 
                    # Apparently there's no usable smart server there, even though
224
 
                    # the medium supports the smart protocol.
225
 
                    do_vfs = True
226
 
            else:
227
 
                do_vfs = False
228
 
        if not do_vfs:
229
 
            client = _SmartClient(client_medium)
230
 
            path = client.remote_path_from_transport(transport)
231
 
            if client_medium._is_remote_before((1, 16)):
232
 
                do_vfs = True
233
 
        if do_vfs:
234
 
            # TODO: lookup the local format from a server hint.
235
 
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
236
 
            self._supply_sub_formats_to(local_dir_format)
237
 
            return local_dir_format.initialize_on_transport_ex(transport,
238
 
                                                               use_existing_dir=use_existing_dir, create_prefix=create_prefix,
239
 
                                                               force_new_repo=force_new_repo, stacked_on=stacked_on,
240
 
                                                               stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
241
 
                                                               make_working_trees=make_working_trees, shared_repo=shared_repo,
242
 
                                                               vfs_only=True)
243
 
        return self._initialize_on_transport_ex_rpc(client, path, transport,
244
 
                                                    use_existing_dir, create_prefix, force_new_repo, stacked_on,
245
 
                                                    stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
246
 
 
247
 
    def _initialize_on_transport_ex_rpc(self, client, path, transport,
248
 
                                        use_existing_dir, create_prefix, force_new_repo, stacked_on,
249
 
                                        stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
250
 
        args = []
251
 
        args.append(self._serialize_NoneTrueFalse(use_existing_dir))
252
 
        args.append(self._serialize_NoneTrueFalse(create_prefix))
253
 
        args.append(self._serialize_NoneTrueFalse(force_new_repo))
254
 
        args.append(self._serialize_NoneString(stacked_on))
255
 
        # stack_on_pwd is often/usually our transport
256
 
        if stack_on_pwd:
257
 
            try:
258
 
                stack_on_pwd = transport.relpath(stack_on_pwd).encode('utf-8')
259
 
                if not stack_on_pwd:
260
 
                    stack_on_pwd = b'.'
261
 
            except errors.PathNotChild:
262
 
                pass
263
 
        args.append(self._serialize_NoneString(stack_on_pwd))
264
 
        args.append(self._serialize_NoneString(repo_format_name))
265
 
        args.append(self._serialize_NoneTrueFalse(make_working_trees))
266
 
        args.append(self._serialize_NoneTrueFalse(shared_repo))
267
 
        request_network_name = self._network_name or \
268
 
            _mod_bzrdir.BzrDirFormat.get_default_format().network_name()
269
 
        try:
270
 
            response = client.call(b'BzrDirFormat.initialize_ex_1.16',
271
 
                                   request_network_name, path, *args)
272
 
        except errors.UnknownSmartMethod:
273
 
            client._medium._remember_remote_is_before((1, 16))
274
 
            local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
275
 
            self._supply_sub_formats_to(local_dir_format)
276
 
            return local_dir_format.initialize_on_transport_ex(transport,
277
 
                                                               use_existing_dir=use_existing_dir, create_prefix=create_prefix,
278
 
                                                               force_new_repo=force_new_repo, stacked_on=stacked_on,
279
 
                                                               stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
280
 
                                                               make_working_trees=make_working_trees, shared_repo=shared_repo,
281
 
                                                               vfs_only=True)
282
 
        except errors.ErrorFromSmartServer as err:
283
 
            _translate_error(err, path=path.decode('utf-8'))
284
 
        repo_path = response[0]
285
 
        bzrdir_name = response[6]
286
 
        require_stacking = response[7]
287
 
        require_stacking = self.parse_NoneTrueFalse(require_stacking)
288
 
        format = RemoteBzrDirFormat()
289
 
        format._network_name = bzrdir_name
290
 
        self._supply_sub_formats_to(format)
291
 
        bzrdir = RemoteBzrDir(transport, format, _client=client)
292
 
        if repo_path:
293
 
            repo_format = response_tuple_to_repo_format(response[1:])
294
 
            if repo_path == b'.':
295
 
                repo_path = b''
296
 
            repo_path = repo_path.decode('utf-8')
297
 
            if repo_path:
298
 
                repo_bzrdir_format = RemoteBzrDirFormat()
299
 
                repo_bzrdir_format._network_name = response[5]
300
 
                repo_bzr = RemoteBzrDir(transport.clone(repo_path),
301
 
                                        repo_bzrdir_format)
302
 
            else:
303
 
                repo_bzr = bzrdir
304
 
            final_stack = response[8] or None
305
 
            if final_stack:
306
 
                final_stack = final_stack.decode('utf-8')
307
 
            final_stack_pwd = response[9] or None
308
 
            if final_stack_pwd:
309
 
                final_stack_pwd = urlutils.join(
310
 
                    transport.base, final_stack_pwd.decode('utf-8'))
311
 
            remote_repo = RemoteRepository(repo_bzr, repo_format)
312
 
            if len(response) > 10:
313
 
                # Updated server verb that locks remotely.
314
 
                repo_lock_token = response[10] or None
315
 
                remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
316
 
                if repo_lock_token:
317
 
                    remote_repo.dont_leave_lock_in_place()
318
 
            else:
319
 
                remote_repo.lock_write()
320
 
            policy = _mod_bzrdir.UseExistingRepository(remote_repo,
321
 
                                                       final_stack, final_stack_pwd, require_stacking)
322
 
            policy.acquire_repository()
323
 
        else:
324
 
            remote_repo = None
325
 
            policy = None
326
 
        bzrdir._format.set_branch_format(self.get_branch_format())
327
 
        if require_stacking:
328
 
            # The repo has already been created, but we need to make sure that
329
 
            # we'll make a stackable branch.
330
 
            bzrdir._format.require_stacking(_skip_repo=True)
331
 
        return remote_repo, bzrdir, require_stacking, policy
332
 
 
333
 
    def _open(self, transport):
334
 
        return RemoteBzrDir(transport, self)
335
 
 
336
 
    def __eq__(self, other):
337
 
        if not isinstance(other, RemoteBzrDirFormat):
338
 
            return False
339
 
        return self.get_format_description() == other.get_format_description()
340
 
 
341
 
    def __return_repository_format(self):
342
 
        # Always return a RemoteRepositoryFormat object, but if a specific bzr
343
 
        # repository format has been asked for, tell the RemoteRepositoryFormat
344
 
        # that it should use that for init() etc.
345
 
        result = RemoteRepositoryFormat()
346
 
        custom_format = getattr(self, '_repository_format', None)
347
 
        if custom_format:
348
 
            if isinstance(custom_format, RemoteRepositoryFormat):
349
 
                return custom_format
350
 
            else:
351
 
                # We will use the custom format to create repositories over the
352
 
                # wire; expose its details like rich_root_data for code to
353
 
                # query
354
 
                result._custom_format = custom_format
355
 
        return result
356
 
 
357
 
    def get_branch_format(self):
358
 
        result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
359
 
        if not isinstance(result, RemoteBranchFormat):
360
 
            new_result = RemoteBranchFormat()
361
 
            new_result._custom_format = result
362
 
            # cache the result
363
 
            self.set_branch_format(new_result)
364
 
            result = new_result
365
 
        return result
366
 
 
367
 
    repository_format = property(__return_repository_format,
368
 
                                 _mod_bzrdir.BzrDirMetaFormat1._set_repository_format)  # .im_func)
369
 
 
370
 
 
371
 
class RemoteControlStore(_mod_config.IniFileStore):
372
 
    """Control store which attempts to use HPSS calls to retrieve control store.
373
 
 
374
 
    Note that this is specific to bzr-based formats.
375
 
    """
376
 
 
377
 
    def __init__(self, bzrdir):
378
 
        super(RemoteControlStore, self).__init__()
379
 
        self.controldir = bzrdir
380
 
        self._real_store = None
381
 
 
382
 
    def lock_write(self, token=None):
383
 
        self._ensure_real()
384
 
        return self._real_store.lock_write(token)
385
 
 
386
 
    def unlock(self):
387
 
        self._ensure_real()
388
 
        return self._real_store.unlock()
389
 
 
390
 
    def save(self):
391
 
        with self.lock_write():
392
 
            # We need to be able to override the undecorated implementation
393
 
            self.save_without_locking()
394
 
 
395
 
    def save_without_locking(self):
396
 
        super(RemoteControlStore, self).save()
397
 
 
398
 
    def _ensure_real(self):
399
 
        self.controldir._ensure_real()
400
 
        if self._real_store is None:
401
 
            self._real_store = _mod_config.ControlStore(self.controldir)
402
 
 
403
 
    def external_url(self):
404
 
        return urlutils.join(self.branch.user_url, 'control.conf')
405
 
 
406
 
    def _load_content(self):
407
 
        medium = self.controldir._client._medium
408
 
        path = self.controldir._path_for_remote_call(self.controldir._client)
409
 
        try:
410
 
            response, handler = self.controldir._call_expecting_body(
411
 
                b'BzrDir.get_config_file', path)
412
 
        except errors.UnknownSmartMethod:
413
 
            self._ensure_real()
414
 
            return self._real_store._load_content()
415
 
        if len(response) and response[0] != b'ok':
416
 
            raise errors.UnexpectedSmartServerResponse(response)
417
 
        return handler.read_body_bytes()
418
 
 
419
 
    def _save_content(self, content):
420
 
        # FIXME JRV 2011-11-22: Ideally this should use a
421
 
        # HPSS call too, but at the moment it is not possible
422
 
        # to write lock control directories.
423
 
        self._ensure_real()
424
 
        return self._real_store._save_content(content)
425
 
 
426
 
 
427
 
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
 
90
# Note: RemoteBzrDirFormat is in bzrdir.py
 
91
 
 
92
class RemoteBzrDir(BzrDir, _RpcHelper):
428
93
    """Control directory on a remote server, accessed via bzr:// or similar."""
429
94
 
430
95
    def __init__(self, transport, format, _client=None, _force_probe=False):
433
98
        :param _client: Private parameter for testing. Disables probing and the
434
99
            use of a real bzrdir.
435
100
        """
436
 
        _mod_bzrdir.BzrDir.__init__(self, transport, format)
 
101
        BzrDir.__init__(self, transport, format)
437
102
        # this object holds a delegated bzrdir that uses file-level operations
438
103
        # to talk to the other side
439
104
        self._real_bzrdir = None
469
134
            self._rpc_open(path)
470
135
 
471
136
    def _rpc_open_2_1(self, path):
472
 
        response = self._call(b'BzrDir.open_2.1', path)
473
 
        if response == (b'no',):
 
137
        response = self._call('BzrDir.open_2.1', path)
 
138
        if response == ('no',):
474
139
            raise errors.NotBranchError(path=self.root_transport.base)
475
 
        elif response[0] == b'yes':
476
 
            if response[1] == b'yes':
 
140
        elif response[0] == 'yes':
 
141
            if response[1] == 'yes':
477
142
                self._has_working_tree = True
478
 
            elif response[1] == b'no':
 
143
            elif response[1] == 'no':
479
144
                self._has_working_tree = False
480
145
            else:
481
146
                raise errors.UnexpectedSmartServerResponse(response)
483
148
            raise errors.UnexpectedSmartServerResponse(response)
484
149
 
485
150
    def _rpc_open(self, path):
486
 
        response = self._call(b'BzrDir.open', path)
487
 
        if response not in [(b'yes',), (b'no',)]:
 
151
        response = self._call('BzrDir.open', path)
 
152
        if response not in [('yes',), ('no',)]:
488
153
            raise errors.UnexpectedSmartServerResponse(response)
489
 
        if response == (b'no',):
 
154
        if response == ('no',):
490
155
            raise errors.NotBranchError(path=self.root_transport.base)
491
156
 
492
157
    def _ensure_real(self):
498
163
            if 'hpssvfs' in debug.debug_flags:
499
164
                import traceback
500
165
                warning('VFS BzrDir access triggered\n%s',
501
 
                        ''.join(traceback.format_stack()))
502
 
            self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
503
 
                self.root_transport, probers=[_mod_bzr.BzrProber])
 
166
                    ''.join(traceback.format_stack()))
 
167
            self._real_bzrdir = BzrDir.open_from_transport(
 
168
                self.root_transport, _server_formats=False)
504
169
            self._format._network_name = \
505
170
                self._real_bzrdir._format.network_name()
506
171
 
511
176
        # Prevent aliasing problems in the next_open_branch_result cache.
512
177
        # See create_branch for rationale.
513
178
        self._next_open_branch_result = None
514
 
        return _mod_bzrdir.BzrDir.break_lock(self)
515
 
 
516
 
    def _vfs_checkout_metadir(self):
517
 
        self._ensure_real()
518
 
        return self._real_bzrdir.checkout_metadir()
519
 
 
520
 
    def checkout_metadir(self):
521
 
        """Retrieve the controldir format to use for checkouts of this one.
522
 
        """
523
 
        medium = self._client._medium
524
 
        if medium._is_remote_before((2, 5)):
525
 
            return self._vfs_checkout_metadir()
526
 
        path = self._path_for_remote_call(self._client)
527
 
        try:
528
 
            response = self._client.call(b'BzrDir.checkout_metadir',
529
 
                                         path)
530
 
        except errors.UnknownSmartMethod:
531
 
            medium._remember_remote_is_before((2, 5))
532
 
            return self._vfs_checkout_metadir()
533
 
        if len(response) != 3:
534
 
            raise errors.UnexpectedSmartServerResponse(response)
535
 
        control_name, repo_name, branch_name = response
536
 
        try:
537
 
            format = controldir.network_format_registry.get(control_name)
538
 
        except KeyError:
539
 
            raise errors.UnknownFormatError(kind='control',
540
 
                                            format=control_name)
541
 
        if repo_name:
542
 
            try:
543
 
                repo_format = _mod_repository.network_format_registry.get(
544
 
                    repo_name)
545
 
            except KeyError:
546
 
                raise errors.UnknownFormatError(kind='repository',
547
 
                                                format=repo_name)
548
 
            format.repository_format = repo_format
549
 
        if branch_name:
550
 
            try:
551
 
                format.set_branch_format(
552
 
                    branch.network_format_registry.get(branch_name))
553
 
            except KeyError:
554
 
                raise errors.UnknownFormatError(kind='branch',
555
 
                                                format=branch_name)
556
 
        return format
 
179
        return BzrDir.break_lock(self)
557
180
 
558
181
    def _vfs_cloning_metadir(self, require_stacking=False):
559
182
        self._ensure_real()
564
187
        medium = self._client._medium
565
188
        if medium._is_remote_before((1, 13)):
566
189
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
567
 
        verb = b'BzrDir.cloning_metadir'
 
190
        verb = 'BzrDir.cloning_metadir'
568
191
        if require_stacking:
569
 
            stacking = b'True'
 
192
            stacking = 'True'
570
193
        else:
571
 
            stacking = b'False'
 
194
            stacking = 'False'
572
195
        path = self._path_for_remote_call(self._client)
573
196
        try:
574
197
            response = self._call(verb, path, stacking)
575
198
        except errors.UnknownSmartMethod:
576
199
            medium._remember_remote_is_before((1, 13))
577
200
            return self._vfs_cloning_metadir(require_stacking=require_stacking)
578
 
        except errors.UnknownErrorFromSmartServer as err:
579
 
            if err.error_tuple != (b'BranchReference',):
 
201
        except errors.UnknownErrorFromSmartServer, err:
 
202
            if err.error_tuple != ('BranchReference',):
580
203
                raise
581
204
            # We need to resolve the branch reference to determine the
582
205
            # cloning_metadir.  This causes unnecessary RPCs to open the
583
206
            # referenced branch (and bzrdir, etc) but only when the caller
584
207
            # didn't already resolve the branch reference.
585
208
            referenced_branch = self.open_branch()
586
 
            return referenced_branch.controldir.cloning_metadir()
 
209
            return referenced_branch.bzrdir.cloning_metadir()
587
210
        if len(response) != 3:
588
211
            raise errors.UnexpectedSmartServerResponse(response)
589
212
        control_name, repo_name, branch_info = response
590
213
        if len(branch_info) != 2:
591
214
            raise errors.UnexpectedSmartServerResponse(response)
592
215
        branch_ref, branch_name = branch_info
593
 
        try:
594
 
            format = controldir.network_format_registry.get(control_name)
595
 
        except KeyError:
596
 
            raise errors.UnknownFormatError(
597
 
                kind='control', format=control_name)
598
 
 
 
216
        format = bzrdir.network_format_registry.get(control_name)
599
217
        if repo_name:
600
 
            try:
601
 
                format.repository_format = _mod_repository.network_format_registry.get(
602
 
                    repo_name)
603
 
            except KeyError:
604
 
                raise errors.UnknownFormatError(kind='repository',
605
 
                                                format=repo_name)
606
 
        if branch_ref == b'ref':
 
218
            format.repository_format = repository.network_format_registry.get(
 
219
                repo_name)
 
220
        if branch_ref == 'ref':
607
221
            # XXX: we need possible_transports here to avoid reopening the
608
222
            # connection to the referenced location
609
 
            ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
 
223
            ref_bzrdir = BzrDir.open(branch_name)
610
224
            branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
611
225
            format.set_branch_format(branch_format)
612
 
        elif branch_ref == b'branch':
 
226
        elif branch_ref == 'branch':
613
227
            if branch_name:
614
 
                try:
615
 
                    branch_format = branch.network_format_registry.get(
616
 
                        branch_name)
617
 
                except KeyError:
618
 
                    raise errors.UnknownFormatError(kind='branch',
619
 
                                                    format=branch_name)
620
 
                format.set_branch_format(branch_format)
 
228
                format.set_branch_format(
 
229
                    branch.network_format_registry.get(branch_name))
621
230
        else:
622
231
            raise errors.UnexpectedSmartServerResponse(response)
623
232
        return format
633
242
 
634
243
    def destroy_repository(self):
635
244
        """See BzrDir.destroy_repository"""
636
 
        path = self._path_for_remote_call(self._client)
637
 
        try:
638
 
            response = self._call(b'BzrDir.destroy_repository', path)
639
 
        except errors.UnknownSmartMethod:
640
 
            self._ensure_real()
641
 
            self._real_bzrdir.destroy_repository()
642
 
            return
643
 
        if response[0] != b'ok':
644
 
            raise SmartProtocolError(
645
 
                'unexpected response code %s' % (response,))
 
245
        self._ensure_real()
 
246
        self._real_bzrdir.destroy_repository()
646
247
 
647
 
    def create_branch(self, name=None, repository=None,
648
 
                      append_revisions_only=None):
649
 
        if name is None:
650
 
            name = self._get_selected_branch()
651
 
        if name != "":
652
 
            raise errors.NoColocatedBranchSupport(self)
 
248
    def create_branch(self, name=None):
653
249
        # as per meta1 formats - just delegate to the format object which may
654
250
        # be parameterised.
655
251
        real_branch = self._format.get_branch_format().initialize(self,
656
 
                                                                  name=name, repository=repository,
657
 
                                                                  append_revisions_only=append_revisions_only)
 
252
            name=name)
658
253
        if not isinstance(real_branch, RemoteBranch):
659
 
            if not isinstance(repository, RemoteRepository):
660
 
                raise AssertionError(
661
 
                    'need a RemoteRepository to use with RemoteBranch, got %r'
662
 
                    % (repository,))
663
 
            result = RemoteBranch(self, repository, real_branch, name=name)
 
254
            result = RemoteBranch(self, self.find_repository(), real_branch,
 
255
                                  name=name)
664
256
        else:
665
257
            result = real_branch
666
258
        # BzrDir.clone_on_transport() uses the result of create_branch but does
674
266
 
675
267
    def destroy_branch(self, name=None):
676
268
        """See BzrDir.destroy_branch"""
677
 
        if name is None:
678
 
            name = self._get_selected_branch()
679
 
        if name != "":
680
 
            raise errors.NoColocatedBranchSupport(self)
681
 
        path = self._path_for_remote_call(self._client)
682
 
        try:
683
 
            if name != "":
684
 
                args = (name, )
685
 
            else:
686
 
                args = ()
687
 
            response = self._call(b'BzrDir.destroy_branch', path, *args)
688
 
        except errors.UnknownSmartMethod:
689
 
            self._ensure_real()
690
 
            self._real_bzrdir.destroy_branch(name=name)
691
 
            self._next_open_branch_result = None
692
 
            return
 
269
        self._ensure_real()
 
270
        self._real_bzrdir.destroy_branch(name=name)
693
271
        self._next_open_branch_result = None
694
 
        if response[0] != b'ok':
695
 
            raise SmartProtocolError(
696
 
                'unexpected response code %s' % (response,))
697
272
 
698
 
    def create_workingtree(self, revision_id=None, from_branch=None,
699
 
                           accelerator_tree=None, hardlink=False):
 
273
    def create_workingtree(self, revision_id=None, from_branch=None):
700
274
        raise errors.NotLocalUrl(self.transport.base)
701
275
 
702
276
    def find_branch_format(self, name=None):
707
281
        b = self.open_branch(name=name)
708
282
        return b._format
709
283
 
710
 
    def branch_names(self):
711
 
        path = self._path_for_remote_call(self._client)
712
 
        try:
713
 
            response, handler = self._call_expecting_body(
714
 
                b'BzrDir.get_branches', path)
715
 
        except errors.UnknownSmartMethod:
716
 
            self._ensure_real()
717
 
            return self._real_bzrdir.branch_names()
718
 
        if response[0] != b"success":
719
 
            raise errors.UnexpectedSmartServerResponse(response)
720
 
        body = bencode.bdecode(handler.read_body_bytes())
721
 
        ret = []
722
 
        for name, value in viewitems(body):
723
 
            name = name.decode('utf-8')
724
 
            ret.append(name)
725
 
        return ret
726
 
 
727
 
    def get_branches(self, possible_transports=None, ignore_fallbacks=False):
728
 
        path = self._path_for_remote_call(self._client)
729
 
        try:
730
 
            response, handler = self._call_expecting_body(
731
 
                b'BzrDir.get_branches', path)
732
 
        except errors.UnknownSmartMethod:
733
 
            self._ensure_real()
734
 
            return self._real_bzrdir.get_branches()
735
 
        if response[0] != b"success":
736
 
            raise errors.UnexpectedSmartServerResponse(response)
737
 
        body = bencode.bdecode(handler.read_body_bytes())
738
 
        ret = {}
739
 
        for name, value in viewitems(body):
740
 
            name = name.decode('utf-8')
741
 
            ret[name] = self._open_branch(
742
 
                name, value[0].decode('ascii'), value[1],
743
 
                possible_transports=possible_transports,
744
 
                ignore_fallbacks=ignore_fallbacks)
745
 
        return ret
746
 
 
747
 
    def set_branch_reference(self, target_branch, name=None):
748
 
        """See BzrDir.set_branch_reference()."""
749
 
        if name is None:
750
 
            name = self._get_selected_branch()
751
 
        if name != "":
752
 
            raise errors.NoColocatedBranchSupport(self)
753
 
        self._ensure_real()
754
 
        return self._real_bzrdir.set_branch_reference(target_branch, name=name)
755
 
 
756
284
    def get_branch_reference(self, name=None):
757
285
        """See BzrDir.get_branch_reference()."""
758
 
        if name is None:
759
 
            name = self._get_selected_branch()
760
 
        if name != "":
 
286
        if name is not None:
 
287
            # XXX JRV20100304: Support opening colocated branches
761
288
            raise errors.NoColocatedBranchSupport(self)
762
289
        response = self._get_branch_reference()
763
290
        if response[0] == 'ref':
764
 
            return response[1].decode('utf-8')
 
291
            return response[1]
765
292
        else:
766
293
            return None
767
294
 
768
295
    def _get_branch_reference(self):
769
 
        """Get branch reference information
770
 
 
771
 
        :return: Tuple with (kind, location_or_format)
772
 
            if kind == 'ref', then location_or_format contains a location
773
 
            otherwise, it contains a format name
774
 
        """
775
296
        path = self._path_for_remote_call(self._client)
776
297
        medium = self._client._medium
777
298
        candidate_calls = [
778
 
            (b'BzrDir.open_branchV3', (2, 1)),
779
 
            (b'BzrDir.open_branchV2', (1, 13)),
780
 
            (b'BzrDir.open_branch', None),
 
299
            ('BzrDir.open_branchV3', (2, 1)),
 
300
            ('BzrDir.open_branchV2', (1, 13)),
 
301
            ('BzrDir.open_branch', None),
781
302
            ]
782
303
        for verb, required_version in candidate_calls:
783
304
            if required_version and medium._is_remote_before(required_version):
790
311
                medium._remember_remote_is_before(required_version)
791
312
            else:
792
313
                break
793
 
        if verb == b'BzrDir.open_branch':
794
 
            if response[0] != b'ok':
 
314
        if verb == 'BzrDir.open_branch':
 
315
            if response[0] != 'ok':
795
316
                raise errors.UnexpectedSmartServerResponse(response)
796
 
            if response[1] != b'':
 
317
            if response[1] != '':
797
318
                return ('ref', response[1])
798
319
            else:
799
 
                return ('branch', b'')
800
 
        if response[0] not in (b'ref', b'branch'):
 
320
                return ('branch', '')
 
321
        if response[0] not in ('ref', 'branch'):
801
322
            raise errors.UnexpectedSmartServerResponse(response)
802
 
        return (response[0].decode('ascii'), response[1])
 
323
        return response
803
324
 
804
325
    def _get_tree_branch(self, name=None):
805
326
        """See BzrDir._get_tree_branch()."""
806
327
        return None, self.open_branch(name=name)
807
328
 
808
 
    def _open_branch(self, name, kind, location_or_format,
809
 
                     ignore_fallbacks=False, possible_transports=None):
810
 
        if kind == 'ref':
 
329
    def open_branch(self, name=None, unsupported=False,
 
330
                    ignore_fallbacks=False):
 
331
        if unsupported:
 
332
            raise NotImplementedError('unsupported flag support not implemented yet.')
 
333
        if self._next_open_branch_result is not None:
 
334
            # See create_branch for details.
 
335
            result = self._next_open_branch_result
 
336
            self._next_open_branch_result = None
 
337
            return result
 
338
        response = self._get_branch_reference()
 
339
        if response[0] == 'ref':
811
340
            # a branch reference, use the existing BranchReference logic.
812
341
            format = BranchReferenceFormat()
813
 
            ref_loc = urlutils.join(self.user_url, location_or_format.decode('utf-8'))
814
342
            return format.open(self, name=name, _found=True,
815
 
                               location=ref_loc,
816
 
                               ignore_fallbacks=ignore_fallbacks,
817
 
                               possible_transports=possible_transports)
818
 
        branch_format_name = location_or_format
 
343
                location=response[1], ignore_fallbacks=ignore_fallbacks)
 
344
        branch_format_name = response[1]
819
345
        if not branch_format_name:
820
346
            branch_format_name = None
821
347
        format = RemoteBranchFormat(network_name=branch_format_name)
822
348
        return RemoteBranch(self, self.find_repository(), format=format,
823
 
                            setup_stacking=not ignore_fallbacks, name=name,
824
 
                            possible_transports=possible_transports)
825
 
 
826
 
    def open_branch(self, name=None, unsupported=False,
827
 
                    ignore_fallbacks=False, possible_transports=None):
828
 
        if name is None:
829
 
            name = self._get_selected_branch()
830
 
        if name != "":
831
 
            raise errors.NoColocatedBranchSupport(self)
832
 
        if unsupported:
833
 
            raise NotImplementedError(
834
 
                'unsupported flag support not implemented yet.')
835
 
        if self._next_open_branch_result is not None:
836
 
            # See create_branch for details.
837
 
            result = self._next_open_branch_result
838
 
            self._next_open_branch_result = None
839
 
            return result
840
 
        response = self._get_branch_reference()
841
 
        return self._open_branch(name, response[0], response[1],
842
 
                                 possible_transports=possible_transports,
843
 
                                 ignore_fallbacks=ignore_fallbacks)
 
349
            setup_stacking=not ignore_fallbacks, name=name)
844
350
 
845
351
    def _open_repo_v1(self, path):
846
 
        verb = b'BzrDir.find_repository'
 
352
        verb = 'BzrDir.find_repository'
847
353
        response = self._call(verb, path)
848
 
        if response[0] != b'ok':
 
354
        if response[0] != 'ok':
849
355
            raise errors.UnexpectedSmartServerResponse(response)
850
356
        # servers that only support the v1 method don't support external
851
357
        # references either.
852
358
        self._ensure_real()
853
359
        repo = self._real_bzrdir.open_repository()
854
 
        response = response + (b'no', repo._format.network_name())
 
360
        response = response + ('no', repo._format.network_name())
855
361
        return response, repo
856
362
 
857
363
    def _open_repo_v2(self, path):
858
 
        verb = b'BzrDir.find_repositoryV2'
 
364
        verb = 'BzrDir.find_repositoryV2'
859
365
        response = self._call(verb, path)
860
 
        if response[0] != b'ok':
 
366
        if response[0] != 'ok':
861
367
            raise errors.UnexpectedSmartServerResponse(response)
862
368
        self._ensure_real()
863
369
        repo = self._real_bzrdir.open_repository()
865
371
        return response, repo
866
372
 
867
373
    def _open_repo_v3(self, path):
868
 
        verb = b'BzrDir.find_repositoryV3'
 
374
        verb = 'BzrDir.find_repositoryV3'
869
375
        medium = self._client._medium
870
376
        if medium._is_remote_before((1, 13)):
871
377
            raise errors.UnknownSmartMethod(verb)
874
380
        except errors.UnknownSmartMethod:
875
381
            medium._remember_remote_is_before((1, 13))
876
382
            raise
877
 
        if response[0] != b'ok':
 
383
        if response[0] != 'ok':
878
384
            raise errors.UnexpectedSmartServerResponse(response)
879
385
        return response, None
880
386
 
882
388
        path = self._path_for_remote_call(self._client)
883
389
        response = None
884
390
        for probe in [self._open_repo_v3, self._open_repo_v2,
885
 
                      self._open_repo_v1]:
 
391
            self._open_repo_v1]:
886
392
            try:
887
393
                response, real_repo = probe(path)
888
394
                break
889
395
            except errors.UnknownSmartMethod:
890
396
                pass
891
397
        if response is None:
892
 
            raise errors.UnknownSmartMethod(b'BzrDir.find_repository{3,2,}')
893
 
        if response[0] != b'ok':
 
398
            raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
 
399
        if response[0] != 'ok':
894
400
            raise errors.UnexpectedSmartServerResponse(response)
895
401
        if len(response) != 6:
896
 
            raise SmartProtocolError(
897
 
                'incorrect response length %s' % (response,))
898
 
        if response[1] == b'':
 
402
            raise SmartProtocolError('incorrect response length %s' % (response,))
 
403
        if response[1] == '':
899
404
            # repo is at this dir.
900
405
            format = response_tuple_to_repo_format(response[2:])
901
406
            # Used to support creating a real format instance when needed.
910
415
 
911
416
    def has_workingtree(self):
912
417
        if self._has_working_tree is None:
913
 
            path = self._path_for_remote_call(self._client)
914
 
            try:
915
 
                response = self._call(b'BzrDir.has_workingtree', path)
916
 
            except errors.UnknownSmartMethod:
917
 
                self._ensure_real()
918
 
                self._has_working_tree = self._real_bzrdir.has_workingtree()
919
 
            else:
920
 
                if response[0] not in (b'yes', b'no'):
921
 
                    raise SmartProtocolError(
922
 
                        'unexpected response code %s' % (response,))
923
 
                self._has_working_tree = (response[0] == b'yes')
 
418
            self._ensure_real()
 
419
            self._has_working_tree = self._real_bzrdir.has_workingtree()
924
420
        return self._has_working_tree
925
421
 
926
422
    def open_workingtree(self, recommend_upgrade=True):
931
427
 
932
428
    def _path_for_remote_call(self, client):
933
429
        """Return the path to be used for this bzrdir in a remote call."""
934
 
        remote_path = client.remote_path_from_transport(self.root_transport)
935
 
        if sys.version_info[0] == 3:
936
 
            remote_path = remote_path.decode('utf-8')
937
 
        base_url, segment_parameters = urlutils.split_segment_parameters_raw(
938
 
            remote_path)
939
 
        if sys.version_info[0] == 3:
940
 
            base_url = base_url.encode('utf-8')
941
 
        return base_url
 
430
        return client.remote_path_from_transport(self.root_transport)
942
431
 
943
432
    def get_branch_transport(self, branch_format, name=None):
944
433
        self._ensure_real()
956
445
        """Upgrading of remote bzrdirs is not supported yet."""
957
446
        return False
958
447
 
959
 
    def needs_format_conversion(self, format):
 
448
    def needs_format_conversion(self, format=None):
960
449
        """Upgrading of remote bzrdirs is not supported yet."""
 
450
        if format is None:
 
451
            symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
 
452
                % 'needs_format_conversion(format=None)')
961
453
        return False
962
454
 
 
455
    def clone(self, url, revision_id=None, force_new_repo=False,
 
456
              preserve_stacking=False):
 
457
        self._ensure_real()
 
458
        return self._real_bzrdir.clone(url, revision_id=revision_id,
 
459
            force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
 
460
 
963
461
    def _get_config(self):
964
462
        return RemoteBzrDirConfig(self)
965
463
 
966
 
    def _get_config_store(self):
967
 
        return RemoteControlStore(self)
968
 
 
969
 
 
970
 
class RemoteInventoryTree(InventoryRevisionTree):
971
 
 
972
 
    def __init__(self, repository, inv, revision_id):
973
 
        super(RemoteInventoryTree, self).__init__(repository, inv, revision_id)
974
 
 
975
 
    def archive(self, format, name, root=None, subdir=None, force_mtime=None):
976
 
        ret = self._repository._revision_archive(
977
 
            self.get_revision_id(), format, name, root, subdir,
978
 
            force_mtime=force_mtime)
979
 
        if ret is None:
980
 
            return super(RemoteInventoryTree, self).archive(
981
 
                format, name, root, subdir, force_mtime=force_mtime)
982
 
        return ret
983
 
 
984
 
    def annotate_iter(self, path,
985
 
                      default_revision=_mod_revision.CURRENT_REVISION):
986
 
        """Return an iterator of revision_id, line tuples.
987
 
 
988
 
        For working trees (and mutable trees in general), the special
989
 
        revision_id 'current:' will be used for lines that are new in this
990
 
        tree, e.g. uncommitted changes.
991
 
        :param default_revision: For lines that don't match a basis, mark them
992
 
            with this revision id. Not all implementations will make use of
993
 
            this value.
994
 
        """
995
 
        ret = self._repository._annotate_file_revision(
996
 
            self.get_revision_id(), path, file_id=None,
997
 
            default_revision=default_revision)
998
 
        if ret is None:
999
 
            return super(RemoteInventoryTree, self).annotate_iter(
1000
 
                path, default_revision=default_revision)
1001
 
        return ret
1002
 
 
1003
 
 
1004
 
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
 
464
 
 
465
class RemoteRepositoryFormat(repository.RepositoryFormat):
1005
466
    """Format for repositories accessed over a _SmartClient.
1006
467
 
1007
468
    Instances of this repository are represented by RemoteRepository
1021
482
        to obtain data like the network name.
1022
483
    """
1023
484
 
1024
 
    _matchingcontroldir = RemoteBzrDirFormat()
1025
 
    supports_full_versioned_files = True
1026
 
    supports_leaving_lock = True
1027
 
    supports_overriding_transport = False
 
485
    _matchingbzrdir = RemoteBzrDirFormat()
1028
486
 
1029
487
    def __init__(self):
1030
 
        _mod_repository.RepositoryFormat.__init__(self)
 
488
        repository.RepositoryFormat.__init__(self)
1031
489
        self._custom_format = None
1032
490
        self._network_name = None
1033
491
        self._creating_bzrdir = None
1034
 
        self._revision_graph_can_have_wrong_parents = None
1035
492
        self._supports_chks = None
1036
493
        self._supports_external_lookups = None
1037
494
        self._supports_tree_reference = None
1038
 
        self._supports_funky_characters = None
1039
 
        self._supports_nesting_repositories = None
1040
495
        self._rich_root_data = None
1041
496
 
1042
497
    def __repr__(self):
1043
498
        return "%s(_network_name=%r)" % (self.__class__.__name__,
1044
 
                                         self._network_name)
 
499
            self._network_name)
1045
500
 
1046
501
    @property
1047
502
    def fast_deltas(self):
1071
526
        return self._supports_external_lookups
1072
527
 
1073
528
    @property
1074
 
    def supports_funky_characters(self):
1075
 
        if self._supports_funky_characters is None:
1076
 
            self._ensure_real()
1077
 
            self._supports_funky_characters = \
1078
 
                self._custom_format.supports_funky_characters
1079
 
        return self._supports_funky_characters
1080
 
 
1081
 
    @property
1082
 
    def supports_nesting_repositories(self):
1083
 
        if self._supports_nesting_repositories is None:
1084
 
            self._ensure_real()
1085
 
            self._supports_nesting_repositories = \
1086
 
                self._custom_format.supports_nesting_repositories
1087
 
        return self._supports_nesting_repositories
1088
 
 
1089
 
    @property
1090
529
    def supports_tree_reference(self):
1091
530
        if self._supports_tree_reference is None:
1092
531
            self._ensure_real()
1094
533
                self._custom_format.supports_tree_reference
1095
534
        return self._supports_tree_reference
1096
535
 
1097
 
    @property
1098
 
    def revision_graph_can_have_wrong_parents(self):
1099
 
        if self._revision_graph_can_have_wrong_parents is None:
1100
 
            self._ensure_real()
1101
 
            self._revision_graph_can_have_wrong_parents = \
1102
 
                self._custom_format.revision_graph_can_have_wrong_parents
1103
 
        return self._revision_graph_can_have_wrong_parents
1104
 
 
1105
 
    def _vfs_initialize(self, a_controldir, shared):
 
536
    def _vfs_initialize(self, a_bzrdir, shared):
1106
537
        """Helper for common code in initialize."""
1107
538
        if self._custom_format:
1108
539
            # Custom format requested
1109
 
            result = self._custom_format.initialize(
1110
 
                a_controldir, shared=shared)
 
540
            result = self._custom_format.initialize(a_bzrdir, shared=shared)
1111
541
        elif self._creating_bzrdir is not None:
1112
542
            # Use the format that the repository we were created to back
1113
543
            # has.
1114
544
            prior_repo = self._creating_bzrdir.open_repository()
1115
545
            prior_repo._ensure_real()
1116
546
            result = prior_repo._real_repository._format.initialize(
1117
 
                a_controldir, shared=shared)
 
547
                a_bzrdir, shared=shared)
1118
548
        else:
1119
549
            # assume that a_bzr is a RemoteBzrDir but the smart server didn't
1120
550
            # support remote initialization.
1121
551
            # We delegate to a real object at this point (as RemoteBzrDir
1122
552
            # delegate to the repository format which would lead to infinite
1123
 
            # recursion if we just called a_controldir.create_repository.
1124
 
            a_controldir._ensure_real()
1125
 
            result = a_controldir._real_bzrdir.create_repository(shared=shared)
 
553
            # recursion if we just called a_bzrdir.create_repository.
 
554
            a_bzrdir._ensure_real()
 
555
            result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1126
556
        if not isinstance(result, RemoteRepository):
1127
 
            return self.open(a_controldir)
 
557
            return self.open(a_bzrdir)
1128
558
        else:
1129
559
            return result
1130
560
 
1131
 
    def initialize(self, a_controldir, shared=False):
 
561
    def initialize(self, a_bzrdir, shared=False):
1132
562
        # Being asked to create on a non RemoteBzrDir:
1133
 
        if not isinstance(a_controldir, RemoteBzrDir):
1134
 
            return self._vfs_initialize(a_controldir, shared)
1135
 
        medium = a_controldir._client._medium
 
563
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
564
            return self._vfs_initialize(a_bzrdir, shared)
 
565
        medium = a_bzrdir._client._medium
1136
566
        if medium._is_remote_before((1, 13)):
1137
 
            return self._vfs_initialize(a_controldir, shared)
 
567
            return self._vfs_initialize(a_bzrdir, shared)
1138
568
        # Creating on a remote bzr dir.
1139
569
        # 1) get the network name to use.
1140
570
        if self._custom_format:
1142
572
        elif self._network_name:
1143
573
            network_name = self._network_name
1144
574
        else:
1145
 
            # Select the current breezy default and ask for that.
1146
 
            reference_bzrdir_format = controldir.format_registry.get(
1147
 
                'default')()
 
575
            # Select the current bzrlib default and ask for that.
 
576
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
1148
577
            reference_format = reference_bzrdir_format.repository_format
1149
578
            network_name = reference_format.network_name()
1150
579
        # 2) try direct creation via RPC
1151
 
        path = a_controldir._path_for_remote_call(a_controldir._client)
1152
 
        verb = b'BzrDir.create_repository'
 
580
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
581
        verb = 'BzrDir.create_repository'
1153
582
        if shared:
1154
 
            shared_str = b'True'
 
583
            shared_str = 'True'
1155
584
        else:
1156
 
            shared_str = b'False'
 
585
            shared_str = 'False'
1157
586
        try:
1158
 
            response = a_controldir._call(verb, path, network_name, shared_str)
 
587
            response = a_bzrdir._call(verb, path, network_name, shared_str)
1159
588
        except errors.UnknownSmartMethod:
1160
589
            # Fallback - use vfs methods
1161
590
            medium._remember_remote_is_before((1, 13))
1162
 
            return self._vfs_initialize(a_controldir, shared)
 
591
            return self._vfs_initialize(a_bzrdir, shared)
1163
592
        else:
1164
593
            # Turn the response into a RemoteRepository object.
1165
594
            format = response_tuple_to_repo_format(response[1:])
1166
595
            # Used to support creating a real format instance when needed.
1167
 
            format._creating_bzrdir = a_controldir
1168
 
            remote_repo = RemoteRepository(a_controldir, format)
 
596
            format._creating_bzrdir = a_bzrdir
 
597
            remote_repo = RemoteRepository(a_bzrdir, format)
1169
598
            format._creating_repo = remote_repo
1170
599
            return remote_repo
1171
600
 
1172
 
    def open(self, a_controldir):
1173
 
        if not isinstance(a_controldir, RemoteBzrDir):
1174
 
            raise AssertionError('%r is not a RemoteBzrDir' % (a_controldir,))
1175
 
        return a_controldir.open_repository()
 
601
    def open(self, a_bzrdir):
 
602
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
603
            raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
 
604
        return a_bzrdir.open_repository()
1176
605
 
1177
606
    def _ensure_real(self):
1178
607
        if self._custom_format is None:
1179
 
            try:
1180
 
                self._custom_format = _mod_repository.network_format_registry.get(
1181
 
                    self._network_name)
1182
 
            except KeyError:
1183
 
                raise errors.UnknownFormatError(kind='repository',
1184
 
                                                format=self._network_name)
 
608
            self._custom_format = repository.network_format_registry.get(
 
609
                self._network_name)
1185
610
 
1186
611
    @property
1187
612
    def _fetch_order(self):
1222
647
        return self._custom_format._serializer
1223
648
 
1224
649
 
1225
 
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1226
 
                       lock._RelockDebugMixin):
 
650
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
 
651
    bzrdir.ControlComponent):
1227
652
    """Repository accessed over rpc.
1228
653
 
1229
654
    For the moment most operations are performed using local transport-backed
1245
670
            self._real_repository = real_repository
1246
671
        else:
1247
672
            self._real_repository = None
1248
 
        self.controldir = remote_bzrdir
 
673
        self.bzrdir = remote_bzrdir
1249
674
        if _client is None:
1250
675
            self._client = remote_bzrdir._client
1251
676
        else:
1253
678
        self._format = format
1254
679
        self._lock_mode = None
1255
680
        self._lock_token = None
1256
 
        self._write_group_tokens = None
1257
681
        self._lock_count = 0
1258
682
        self._leave_lock = False
1259
683
        # Cache of revision parents; misses are cached during read locks, and
1269
693
        self._reconcile_does_inventory_gc = False
1270
694
        self._reconcile_fixes_text_parents = False
1271
695
        self._reconcile_backsup_inventory = False
1272
 
        self.base = self.controldir.transport.base
 
696
        self.base = self.bzrdir.transport.base
1273
697
        # Additional places to query for data.
1274
698
        self._fallback_repositories = []
1275
699
 
1276
700
    @property
1277
701
    def user_transport(self):
1278
 
        return self.controldir.user_transport
 
702
        return self.bzrdir.user_transport
1279
703
 
1280
704
    @property
1281
705
    def control_transport(self):
1282
706
        # XXX: Normally you shouldn't directly get at the remote repository
1283
707
        # transport, but I'm not sure it's worth making this method
1284
708
        # optional -- mbp 2010-04-21
1285
 
        return self.controldir.get_repository_transport(None)
1286
 
 
 
709
        return self.bzrdir.get_repository_transport(None)
 
710
        
1287
711
    def __str__(self):
1288
712
        return "%s(%s)" % (self.__class__.__name__, self.base)
1289
713
 
1299
723
 
1300
724
        :param suppress_errors: see Repository.abort_write_group.
1301
725
        """
1302
 
        if self._real_repository:
1303
 
            self._ensure_real()
1304
 
            return self._real_repository.abort_write_group(
1305
 
                suppress_errors=suppress_errors)
1306
 
        if not self.is_in_write_group():
1307
 
            if suppress_errors:
1308
 
                mutter('(suppressed) not in write group')
1309
 
                return
1310
 
            raise errors.BzrError("not in write group")
1311
 
        path = self.controldir._path_for_remote_call(self._client)
1312
 
        try:
1313
 
            response = self._call(b'Repository.abort_write_group', path,
1314
 
                                  self._lock_token,
1315
 
                                  [token.encode('utf-8') for token in self._write_group_tokens])
1316
 
        except Exception as exc:
1317
 
            self._write_group = None
1318
 
            if not suppress_errors:
1319
 
                raise
1320
 
            mutter('abort_write_group failed')
1321
 
            log_exception_quietly()
1322
 
            note(gettext('bzr: ERROR (ignored): %s'), exc)
1323
 
        else:
1324
 
            if response != (b'ok', ):
1325
 
                raise errors.UnexpectedSmartServerResponse(response)
1326
 
            self._write_group_tokens = None
 
726
        self._ensure_real()
 
727
        return self._real_repository.abort_write_group(
 
728
            suppress_errors=suppress_errors)
1327
729
 
1328
730
    @property
1329
731
    def chk_bytes(self):
1343
745
        for older plugins that don't use e.g. the CommitBuilder
1344
746
        facility.
1345
747
        """
1346
 
        if self._real_repository:
1347
 
            self._ensure_real()
1348
 
            return self._real_repository.commit_write_group()
1349
 
        if not self.is_in_write_group():
1350
 
            raise errors.BzrError("not in write group")
1351
 
        path = self.controldir._path_for_remote_call(self._client)
1352
 
        response = self._call(b'Repository.commit_write_group', path,
1353
 
                              self._lock_token, [token.encode('utf-8') for token in self._write_group_tokens])
1354
 
        if response != (b'ok', ):
1355
 
            raise errors.UnexpectedSmartServerResponse(response)
1356
 
        self._write_group_tokens = None
1357
 
        # Refresh data after writing to the repository.
1358
 
        self.refresh_data()
 
748
        self._ensure_real()
 
749
        return self._real_repository.commit_write_group()
1359
750
 
1360
751
    def resume_write_group(self, tokens):
1361
 
        if self._real_repository:
1362
 
            return self._real_repository.resume_write_group(tokens)
1363
 
        path = self.controldir._path_for_remote_call(self._client)
1364
 
        try:
1365
 
            response = self._call(b'Repository.check_write_group', path,
1366
 
                                  self._lock_token, [token.encode('utf-8') for token in tokens])
1367
 
        except errors.UnknownSmartMethod:
1368
 
            self._ensure_real()
1369
 
            return self._real_repository.resume_write_group(tokens)
1370
 
        if response != (b'ok', ):
1371
 
            raise errors.UnexpectedSmartServerResponse(response)
1372
 
        self._write_group_tokens = tokens
 
752
        self._ensure_real()
 
753
        return self._real_repository.resume_write_group(tokens)
1373
754
 
1374
755
    def suspend_write_group(self):
1375
 
        if self._real_repository:
1376
 
            return self._real_repository.suspend_write_group()
1377
 
        ret = self._write_group_tokens or []
1378
 
        self._write_group_tokens = None
1379
 
        return ret
 
756
        self._ensure_real()
 
757
        return self._real_repository.suspend_write_group()
1380
758
 
1381
759
    def get_missing_parent_inventories(self, check_for_missing_texts=True):
1382
760
        self._ensure_real()
1390
768
 
1391
769
    def get_rev_id_for_revno(self, revno, known_pair):
1392
770
        """See Repository.get_rev_id_for_revno."""
1393
 
        path = self.controldir._path_for_remote_call(self._client)
 
771
        path = self.bzrdir._path_for_remote_call(self._client)
1394
772
        try:
1395
773
            if self._client._medium._is_remote_before((1, 17)):
1396
774
                return self._get_rev_id_for_revno_vfs(revno, known_pair)
1397
775
            response = self._call(
1398
 
                b'Repository.get_rev_id_for_revno', path, revno, known_pair)
 
776
                'Repository.get_rev_id_for_revno', path, revno, known_pair)
1399
777
        except errors.UnknownSmartMethod:
1400
778
            self._client._medium._remember_remote_is_before((1, 17))
1401
779
            return self._get_rev_id_for_revno_vfs(revno, known_pair)
1402
 
        except errors.UnknownErrorFromSmartServer as e:
1403
 
            # Older versions of Bazaar/Breezy (<< 3.0.0) would raise a
1404
 
            # ValueError instead of returning revno-outofbounds
1405
 
            if len(e.error_tuple) < 3:
1406
 
                raise
1407
 
            if e.error_tuple[:2] != (b'error', b'ValueError'):
1408
 
                raise
1409
 
            m = re.match(
1410
 
                br"requested revno \(([0-9]+)\) is later than given "
1411
 
                br"known revno \(([0-9]+)\)", e.error_tuple[2])
1412
 
            if not m:
1413
 
                raise
1414
 
            raise errors.RevnoOutOfBounds(
1415
 
                int(m.group(1)), (0, int(m.group(2))))
1416
 
        if response[0] == b'ok':
 
780
        if response[0] == 'ok':
1417
781
            return True, response[1]
1418
 
        elif response[0] == b'history-incomplete':
 
782
        elif response[0] == 'history-incomplete':
1419
783
            known_pair = response[1:3]
1420
784
            for fallback in self._fallback_repositories:
1421
 
                found, result = fallback.get_rev_id_for_revno(
1422
 
                    revno, known_pair)
 
785
                found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1423
786
                if found:
1424
787
                    return True, result
1425
788
                else:
1446
809
            if 'hpssvfs' in debug.debug_flags:
1447
810
                import traceback
1448
811
                warning('VFS Repository access triggered\n%s',
1449
 
                        ''.join(traceback.format_stack()))
 
812
                    ''.join(traceback.format_stack()))
1450
813
            self._unstacked_provider.missing_keys.clear()
1451
 
            self.controldir._ensure_real()
 
814
            self.bzrdir._ensure_real()
1452
815
            self._set_real_repository(
1453
 
                self.controldir._real_bzrdir.open_repository())
 
816
                self.bzrdir._real_bzrdir.open_repository())
1454
817
 
1455
818
    def _translate_error(self, err, **context):
1456
 
        self.controldir._translate_error(err, repository=self, **context)
 
819
        self.bzrdir._translate_error(err, repository=self, **context)
1457
820
 
1458
821
    def find_text_key_references(self):
1459
822
        """Find the text key references within the repository.
1460
823
 
 
824
        :return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
 
825
        revision_ids. Each altered file-ids has the exact revision_ids that
 
826
        altered it listed explicitly.
1461
827
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1462
828
            to whether they were referred to by the inventory of the
1463
829
            revision_id that they contain. The inventory texts from all present
1480
846
    def _get_revision_graph(self, revision_id):
1481
847
        """Private method for using with old (< 1.2) servers to fallback."""
1482
848
        if revision_id is None:
1483
 
            revision_id = b''
1484
 
        elif _mod_revision.is_null(revision_id):
 
849
            revision_id = ''
 
850
        elif revision.is_null(revision_id):
1485
851
            return {}
1486
852
 
1487
 
        path = self.controldir._path_for_remote_call(self._client)
 
853
        path = self.bzrdir._path_for_remote_call(self._client)
1488
854
        response = self._call_expecting_body(
1489
 
            b'Repository.get_revision_graph', path, revision_id)
 
855
            'Repository.get_revision_graph', path, revision_id)
1490
856
        response_tuple, response_handler = response
1491
 
        if response_tuple[0] != b'ok':
 
857
        if response_tuple[0] != 'ok':
1492
858
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1493
859
        coded = response_handler.read_body_bytes()
1494
 
        if coded == b'':
 
860
        if coded == '':
1495
861
            # no revisions in this repository!
1496
862
            return {}
1497
 
        lines = coded.split(b'\n')
 
863
        lines = coded.split('\n')
1498
864
        revision_graph = {}
1499
865
        for line in lines:
1500
866
            d = tuple(line.split())
1510
876
        """Return a source for streaming from this repository."""
1511
877
        return RemoteStreamSource(self, to_format)
1512
878
 
1513
 
    def get_file_graph(self):
1514
 
        with self.lock_read():
1515
 
            return graph.Graph(self.texts)
1516
 
 
 
879
    @needs_read_lock
1517
880
    def has_revision(self, revision_id):
1518
881
        """True if this repository has a copy of the revision."""
1519
 
        # Copy of breezy.repository.Repository.has_revision
1520
 
        with self.lock_read():
1521
 
            return revision_id in self.has_revisions((revision_id,))
 
882
        # Copy of bzrlib.repository.Repository.has_revision
 
883
        return revision_id in self.has_revisions((revision_id,))
1522
884
 
 
885
    @needs_read_lock
1523
886
    def has_revisions(self, revision_ids):
1524
887
        """Probe to find out the presence of multiple revisions.
1525
888
 
1526
889
        :param revision_ids: An iterable of revision_ids.
1527
890
        :return: A set of the revision_ids that were present.
1528
891
        """
1529
 
        with self.lock_read():
1530
 
            # Copy of breezy.repository.Repository.has_revisions
1531
 
            parent_map = self.get_parent_map(revision_ids)
1532
 
            result = set(parent_map)
1533
 
            if _mod_revision.NULL_REVISION in revision_ids:
1534
 
                result.add(_mod_revision.NULL_REVISION)
1535
 
            return result
 
892
        # Copy of bzrlib.repository.Repository.has_revisions
 
893
        parent_map = self.get_parent_map(revision_ids)
 
894
        result = set(parent_map)
 
895
        if _mod_revision.NULL_REVISION in revision_ids:
 
896
            result.add(_mod_revision.NULL_REVISION)
 
897
        return result
1536
898
 
1537
899
    def _has_same_fallbacks(self, other_repo):
1538
900
        """Returns true if the repositories have the same fallbacks."""
1551
913
        # TODO: Move to RepositoryBase and unify with the regular Repository
1552
914
        # one; unfortunately the tests rely on slightly different behaviour at
1553
915
        # present -- mbp 20090710
1554
 
        return (self.__class__ is other.__class__
1555
 
                and self.controldir.transport.base == other.controldir.transport.base)
 
916
        return (self.__class__ is other.__class__ and
 
917
                self.bzrdir.transport.base == other.bzrdir.transport.base)
1556
918
 
1557
919
    def get_graph(self, other_repository=None):
1558
920
        """Return the graph for this repository format"""
1559
921
        parents_provider = self._make_parents_provider(other_repository)
1560
922
        return graph.Graph(parents_provider)
1561
923
 
 
924
    @needs_read_lock
1562
925
    def get_known_graph_ancestry(self, revision_ids):
1563
926
        """Return the known graph for a set of revision ids and their ancestors.
1564
927
        """
1565
 
        with self.lock_read():
1566
 
            revision_graph = dict(((key, value) for key, value in
1567
 
                                   self.get_graph().iter_ancestry(revision_ids) if value is not None))
1568
 
            revision_graph = _mod_repository._strip_NULL_ghosts(revision_graph)
1569
 
            return graph.KnownGraph(revision_graph)
 
928
        st = static_tuple.StaticTuple
 
929
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
930
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
931
        return graph.GraphThunkIdsToKeys(known_graph)
1570
932
 
1571
933
    def gather_stats(self, revid=None, committers=None):
1572
934
        """See Repository.gather_stats()."""
1573
 
        path = self.controldir._path_for_remote_call(self._client)
 
935
        path = self.bzrdir._path_for_remote_call(self._client)
1574
936
        # revid can be None to indicate no revisions, not just NULL_REVISION
1575
 
        if revid is None or _mod_revision.is_null(revid):
1576
 
            fmt_revid = b''
 
937
        if revid is None or revision.is_null(revid):
 
938
            fmt_revid = ''
1577
939
        else:
1578
940
            fmt_revid = revid
1579
941
        if committers is None or not committers:
1580
 
            fmt_committers = b'no'
 
942
            fmt_committers = 'no'
1581
943
        else:
1582
 
            fmt_committers = b'yes'
 
944
            fmt_committers = 'yes'
1583
945
        response_tuple, response_handler = self._call_expecting_body(
1584
 
            b'Repository.gather_stats', path, fmt_revid, fmt_committers)
1585
 
        if response_tuple[0] != b'ok':
 
946
            'Repository.gather_stats', path, fmt_revid, fmt_committers)
 
947
        if response_tuple[0] != 'ok':
1586
948
            raise errors.UnexpectedSmartServerResponse(response_tuple)
1587
949
 
1588
950
        body = response_handler.read_body_bytes()
1589
951
        result = {}
1590
 
        for line in body.split(b'\n'):
 
952
        for line in body.split('\n'):
1591
953
            if not line:
1592
954
                continue
1593
 
            key, val_text = line.split(b':')
1594
 
            key = key.decode('ascii')
 
955
            key, val_text = line.split(':')
1595
956
            if key in ('revisions', 'size', 'committers'):
1596
957
                result[key] = int(val_text)
1597
958
            elif key in ('firstrev', 'latestrev'):
1598
 
                values = val_text.split(b' ')[1:]
1599
 
                result[key] = (float(values[0]), int(values[1]))
 
959
                values = val_text.split(' ')[1:]
 
960
                result[key] = (float(values[0]), long(values[1]))
1600
961
 
1601
962
        return result
1602
963
 
1608
969
 
1609
970
    def get_physical_lock_status(self):
1610
971
        """See Repository.get_physical_lock_status()."""
1611
 
        path = self.controldir._path_for_remote_call(self._client)
1612
 
        try:
1613
 
            response = self._call(b'Repository.get_physical_lock_status', path)
1614
 
        except errors.UnknownSmartMethod:
1615
 
            self._ensure_real()
1616
 
            return self._real_repository.get_physical_lock_status()
1617
 
        if response[0] not in (b'yes', b'no'):
1618
 
            raise errors.UnexpectedSmartServerResponse(response)
1619
 
        return (response[0] == b'yes')
 
972
        # should be an API call to the server.
 
973
        self._ensure_real()
 
974
        return self._real_repository.get_physical_lock_status()
1620
975
 
1621
976
    def is_in_write_group(self):
1622
977
        """Return True if there is an open write group.
1623
978
 
1624
979
        write groups are only applicable locally for the smart server..
1625
980
        """
1626
 
        if self._write_group_tokens is not None:
1627
 
            return True
1628
981
        if self._real_repository:
1629
982
            return self._real_repository.is_in_write_group()
1630
983
 
1633
986
 
1634
987
    def is_shared(self):
1635
988
        """See Repository.is_shared()."""
1636
 
        path = self.controldir._path_for_remote_call(self._client)
1637
 
        response = self._call(b'Repository.is_shared', path)
1638
 
        if response[0] not in (b'yes', b'no'):
1639
 
            raise SmartProtocolError(
1640
 
                'unexpected response code %s' % (response,))
1641
 
        return response[0] == b'yes'
 
989
        path = self.bzrdir._path_for_remote_call(self._client)
 
990
        response = self._call('Repository.is_shared', path)
 
991
        if response[0] not in ('yes', 'no'):
 
992
            raise SmartProtocolError('unexpected response code %s' % (response,))
 
993
        return response[0] == 'yes'
1642
994
 
1643
995
    def is_write_locked(self):
1644
996
        return self._lock_mode == 'w'
1651
1003
    def lock_read(self):
1652
1004
        """Lock the repository for read operations.
1653
1005
 
1654
 
        :return: A breezy.lock.LogicalLockResult.
 
1006
        :return: A bzrlib.lock.LogicalLockResult.
1655
1007
        """
1656
1008
        # wrong eventually - want a local lock cache context
1657
1009
        if not self._lock_mode:
1668
1020
        return lock.LogicalLockResult(self.unlock)
1669
1021
 
1670
1022
    def _remote_lock_write(self, token):
1671
 
        path = self.controldir._path_for_remote_call(self._client)
 
1023
        path = self.bzrdir._path_for_remote_call(self._client)
1672
1024
        if token is None:
1673
 
            token = b''
 
1025
            token = ''
1674
1026
        err_context = {'token': token}
1675
 
        response = self._call(b'Repository.lock_write', path, token,
 
1027
        response = self._call('Repository.lock_write', path, token,
1676
1028
                              **err_context)
1677
 
        if response[0] == b'ok':
 
1029
        if response[0] == 'ok':
1678
1030
            ok, token = response
1679
1031
            return token
1680
1032
        else:
1752
1104
        # 3) new servers, RemoteRepository.ensure_real is triggered before
1753
1105
        # RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1754
1106
        # and need to populate it.
1755
 
        if (self._fallback_repositories
1756
 
            and len(self._real_repository._fallback_repositories)
1757
 
                != len(self._fallback_repositories)):
 
1107
        if (self._fallback_repositories and
 
1108
            len(self._real_repository._fallback_repositories) !=
 
1109
            len(self._fallback_repositories)):
1758
1110
            if len(self._real_repository._fallback_repositories):
1759
1111
                raise AssertionError(
1760
1112
                    "cannot cleanly remove existing _fallback_repositories")
1766
1118
            self._real_repository.lock_write(self._lock_token)
1767
1119
        elif self._lock_mode == 'r':
1768
1120
            self._real_repository.lock_read()
1769
 
        if self._write_group_tokens is not None:
1770
 
            # if we are already in a write group, resume it
1771
 
            self._real_repository.resume_write_group(self._write_group_tokens)
1772
 
            self._write_group_tokens = None
1773
1121
 
1774
1122
    def start_write_group(self):
1775
1123
        """Start a write group on the decorated repository.
1779
1127
        for older plugins that don't use e.g. the CommitBuilder
1780
1128
        facility.
1781
1129
        """
1782
 
        if self._real_repository:
1783
 
            self._ensure_real()
1784
 
            return self._real_repository.start_write_group()
1785
 
        if not self.is_write_locked():
1786
 
            raise errors.NotWriteLocked(self)
1787
 
        if self._write_group_tokens is not None:
1788
 
            raise errors.BzrError('already in a write group')
1789
 
        path = self.controldir._path_for_remote_call(self._client)
1790
 
        try:
1791
 
            response = self._call(b'Repository.start_write_group', path,
1792
 
                                  self._lock_token)
1793
 
        except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1794
 
            self._ensure_real()
1795
 
            return self._real_repository.start_write_group()
1796
 
        if response[0] != b'ok':
1797
 
            raise errors.UnexpectedSmartServerResponse(response)
1798
 
        self._write_group_tokens = [
1799
 
            token.decode('utf-8') for token in response[1]]
 
1130
        self._ensure_real()
 
1131
        return self._real_repository.start_write_group()
1800
1132
 
1801
1133
    def _unlock(self, token):
1802
 
        path = self.controldir._path_for_remote_call(self._client)
 
1134
        path = self.bzrdir._path_for_remote_call(self._client)
1803
1135
        if not token:
1804
1136
            # with no token the remote repository is not persistently locked.
1805
1137
            return
1806
1138
        err_context = {'token': token}
1807
 
        response = self._call(b'Repository.unlock', path, token,
 
1139
        response = self._call('Repository.unlock', path, token,
1808
1140
                              **err_context)
1809
 
        if response == (b'ok',):
 
1141
        if response == ('ok',):
1810
1142
            return
1811
1143
        else:
1812
1144
            raise errors.UnexpectedSmartServerResponse(response)
1829
1161
            # This is just to let the _real_repository stay up to date.
1830
1162
            if self._real_repository is not None:
1831
1163
                self._real_repository.unlock()
1832
 
            elif self._write_group_tokens is not None:
1833
 
                self.abort_write_group()
1834
1164
        finally:
1835
1165
            # The rpc-level lock should be released even if there was a
1836
1166
            # problem releasing the vfs-based lock.
1848
1178
 
1849
1179
    def break_lock(self):
1850
1180
        # should hand off to the network
1851
 
        path = self.controldir._path_for_remote_call(self._client)
1852
 
        try:
1853
 
            response = self._call(b"Repository.break_lock", path)
1854
 
        except errors.UnknownSmartMethod:
1855
 
            self._ensure_real()
1856
 
            return self._real_repository.break_lock()
1857
 
        if response != (b'ok',):
1858
 
            raise errors.UnexpectedSmartServerResponse(response)
 
1181
        self._ensure_real()
 
1182
        return self._real_repository.break_lock()
1859
1183
 
1860
1184
    def _get_tarball(self, compression):
1861
1185
        """Return a TemporaryFile containing a repository tarball.
1863
1187
        Returns None if the server does not support sending tarballs.
1864
1188
        """
1865
1189
        import tempfile
1866
 
        path = self.controldir._path_for_remote_call(self._client)
 
1190
        path = self.bzrdir._path_for_remote_call(self._client)
1867
1191
        try:
1868
1192
            response, protocol = self._call_expecting_body(
1869
 
                b'Repository.tarball', path, compression.encode('ascii'))
 
1193
                'Repository.tarball', path, compression)
1870
1194
        except errors.UnknownSmartMethod:
1871
1195
            protocol.cancel_read_body()
1872
1196
            return None
1873
 
        if response[0] == b'ok':
 
1197
        if response[0] == 'ok':
1874
1198
            # Extract the tarball and return it
1875
1199
            t = tempfile.NamedTemporaryFile()
1876
1200
            # TODO: rpc layer should read directly into it...
1880
1204
        raise errors.UnexpectedSmartServerResponse(response)
1881
1205
 
1882
1206
    def sprout(self, to_bzrdir, revision_id=None):
1883
 
        """Create a descendent repository for new development.
1884
 
 
1885
 
        Unlike clone, this does not copy the settings of the repository.
1886
 
        """
1887
 
        with self.lock_read():
1888
 
            dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1889
 
            dest_repo.fetch(self, revision_id=revision_id)
1890
 
            return dest_repo
1891
 
 
1892
 
    def _create_sprouting_repo(self, a_controldir, shared):
1893
 
        if not isinstance(a_controldir._format, self.controldir._format.__class__):
1894
 
            # use target default format.
1895
 
            dest_repo = a_controldir.create_repository()
1896
 
        else:
1897
 
            # Most control formats need the repository to be specifically
1898
 
            # created, but on some old all-in-one formats it's not needed
1899
 
            try:
1900
 
                dest_repo = self._format.initialize(
1901
 
                    a_controldir, shared=shared)
1902
 
            except errors.UninitializableFormat:
1903
 
                dest_repo = a_controldir.open_repository()
 
1207
        # TODO: Option to control what format is created?
 
1208
        self._ensure_real()
 
1209
        dest_repo = self._real_repository._format.initialize(to_bzrdir,
 
1210
                                                             shared=False)
 
1211
        dest_repo.fetch(self, revision_id=revision_id)
1904
1212
        return dest_repo
1905
1213
 
1906
 
    # These methods are just thin shims to the VFS object for now.
 
1214
    ### These methods are just thin shims to the VFS object for now.
1907
1215
 
1908
1216
    def revision_tree(self, revision_id):
1909
 
        with self.lock_read():
1910
 
            revision_id = _mod_revision.ensure_null(revision_id)
1911
 
            if revision_id == _mod_revision.NULL_REVISION:
1912
 
                return InventoryRevisionTree(self,
1913
 
                                             Inventory(root_id=None), _mod_revision.NULL_REVISION)
1914
 
            else:
1915
 
                return list(self.revision_trees([revision_id]))[0]
 
1217
        self._ensure_real()
 
1218
        return self._real_repository.revision_tree(revision_id)
1916
1219
 
1917
1220
    def get_serializer_format(self):
1918
 
        path = self.controldir._path_for_remote_call(self._client)
1919
 
        try:
1920
 
            response = self._call(b'VersionedFileRepository.get_serializer_format',
1921
 
                                  path)
1922
 
        except errors.UnknownSmartMethod:
1923
 
            self._ensure_real()
1924
 
            return self._real_repository.get_serializer_format()
1925
 
        if response[0] != b'ok':
1926
 
            raise errors.UnexpectedSmartServerResponse(response)
1927
 
        return response[1]
 
1221
        self._ensure_real()
 
1222
        return self._real_repository.get_serializer_format()
1928
1223
 
1929
1224
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1930
1225
                           timezone=None, committer=None, revprops=None,
1931
 
                           revision_id=None, lossy=False):
1932
 
        """Obtain a CommitBuilder for this repository.
1933
 
 
1934
 
        :param branch: Branch to commit to.
1935
 
        :param parents: Revision ids of the parents of the new revision.
1936
 
        :param config: Configuration to use.
1937
 
        :param timestamp: Optional timestamp recorded for commit.
1938
 
        :param timezone: Optional timezone for timestamp.
1939
 
        :param committer: Optional committer to set for commit.
1940
 
        :param revprops: Optional dictionary of revision properties.
1941
 
        :param revision_id: Optional revision id.
1942
 
        :param lossy: Whether to discard data that can not be natively
1943
 
            represented, when pushing to a foreign VCS
1944
 
        """
1945
 
        if self._fallback_repositories and not self._format.supports_chks:
1946
 
            raise errors.BzrError("Cannot commit directly to a stacked branch"
1947
 
                                  " in pre-2a formats. See "
1948
 
                                  "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1949
 
        commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1950
 
        result = commit_builder_kls(self, parents, config,
1951
 
                                    timestamp, timezone, committer, revprops, revision_id,
1952
 
                                    lossy)
1953
 
        self.start_write_group()
1954
 
        return result
 
1226
                           revision_id=None):
 
1227
        # FIXME: It ought to be possible to call this without immediately
 
1228
        # triggering _ensure_real.  For now it's the easiest thing to do.
 
1229
        self._ensure_real()
 
1230
        real_repo = self._real_repository
 
1231
        builder = real_repo.get_commit_builder(branch, parents,
 
1232
                config, timestamp=timestamp, timezone=timezone,
 
1233
                committer=committer, revprops=revprops, revision_id=revision_id)
 
1234
        return builder
1955
1235
 
1956
1236
    def add_fallback_repository(self, repository):
1957
1237
        """Add a repository to use for looking up data not held locally.
1964
1244
        # We need to accumulate additional repositories here, to pass them in
1965
1245
        # on various RPC's.
1966
1246
        #
1967
 
        # Make the check before we lock: this raises an exception.
1968
 
        self._check_fallback_repository(repository)
1969
1247
        if self.is_locked():
1970
1248
            # We will call fallback.unlock() when we transition to the unlocked
1971
1249
            # state, so always add a lock here. If a caller passes us a locked
1972
1250
            # repository, they are responsible for unlocking it later.
1973
1251
            repository.lock_read()
 
1252
        self._check_fallback_repository(repository)
1974
1253
        self._fallback_repositories.append(repository)
1975
1254
        # If self._real_repository was parameterised already (e.g. because a
1976
1255
        # _real_branch had its get_stacked_on_url method called), then the
1977
1256
        # repository to be added may already be in the _real_repositories list.
1978
1257
        if self._real_repository is not None:
1979
1258
            fallback_locations = [repo.user_url for repo in
1980
 
                                  self._real_repository._fallback_repositories]
 
1259
                self._real_repository._fallback_repositories]
1981
1260
            if repository.user_url not in fallback_locations:
1982
1261
                self._real_repository.add_fallback_repository(repository)
1983
1262
 
1996
1275
        return self._real_repository.add_inventory(revid, inv, parents)
1997
1276
 
1998
1277
    def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1999
 
                               parents, basis_inv=None, propagate_caches=False):
 
1278
            parents, basis_inv=None, propagate_caches=False):
2000
1279
        self._ensure_real()
2001
1280
        return self._real_repository.add_inventory_by_delta(basis_revision_id,
2002
 
                                                            delta, new_revision_id, parents, basis_inv=basis_inv,
2003
 
                                                            propagate_caches=propagate_caches)
2004
 
 
2005
 
    def add_revision(self, revision_id, rev, inv=None):
2006
 
        _mod_revision.check_not_reserved_id(revision_id)
2007
 
        key = (revision_id,)
2008
 
        # check inventory present
2009
 
        if not self.inventories.get_parent_map([key]):
2010
 
            if inv is None:
2011
 
                raise errors.WeaveRevisionNotPresent(revision_id,
2012
 
                                                     self.inventories)
2013
 
            else:
2014
 
                # yes, this is not suitable for adding with ghosts.
2015
 
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
2016
 
                                                        rev.parent_ids)
2017
 
        else:
2018
 
            rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
2019
 
        self._add_revision(rev)
2020
 
 
2021
 
    def _add_revision(self, rev):
2022
 
        if self._real_repository is not None:
2023
 
            return self._real_repository._add_revision(rev)
2024
 
        lines = self._serializer.write_revision_to_lines(rev)
2025
 
        key = (rev.revision_id,)
2026
 
        parents = tuple((parent,) for parent in rev.parent_ids)
2027
 
        self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
2028
 
            [('revisions', [ChunkedContentFactory(key, parents, None, lines, chunks_are_lines=True)])],
2029
 
            self._format, self._write_group_tokens)
2030
 
 
 
1281
            delta, new_revision_id, parents, basis_inv=basis_inv,
 
1282
            propagate_caches=propagate_caches)
 
1283
 
 
1284
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
1285
        self._ensure_real()
 
1286
        return self._real_repository.add_revision(
 
1287
            rev_id, rev, inv=inv, config=config)
 
1288
 
 
1289
    @needs_read_lock
2031
1290
    def get_inventory(self, revision_id):
2032
 
        with self.lock_read():
2033
 
            return list(self.iter_inventories([revision_id]))[0]
2034
 
 
2035
 
    def _iter_inventories_rpc(self, revision_ids, ordering):
2036
 
        if ordering is None:
2037
 
            ordering = 'unordered'
2038
 
        path = self.controldir._path_for_remote_call(self._client)
2039
 
        body = b"\n".join(revision_ids)
2040
 
        response_tuple, response_handler = (
2041
 
            self._call_with_body_bytes_expecting_body(
2042
 
                b"VersionedFileRepository.get_inventories",
2043
 
                (path, ordering.encode('ascii')), body))
2044
 
        if response_tuple[0] != b"ok":
2045
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2046
 
        deserializer = inventory_delta.InventoryDeltaDeserializer()
2047
 
        byte_stream = response_handler.read_streamed_body()
2048
 
        decoded = smart_repo._byte_stream_to_stream(byte_stream)
2049
 
        if decoded is None:
2050
 
            # no results whatsoever
2051
 
            return
2052
 
        src_format, stream = decoded
2053
 
        if src_format.network_name() != self._format.network_name():
2054
 
            raise AssertionError(
2055
 
                "Mismatched RemoteRepository and stream src %r, %r" % (
2056
 
                    src_format.network_name(), self._format.network_name()))
2057
 
        # ignore the src format, it's not really relevant
2058
 
        prev_inv = Inventory(root_id=None,
2059
 
                             revision_id=_mod_revision.NULL_REVISION)
2060
 
        # there should be just one substream, with inventory deltas
2061
 
        try:
2062
 
            substream_kind, substream = next(stream)
2063
 
        except StopIteration:
2064
 
            return
2065
 
        if substream_kind != "inventory-deltas":
2066
 
            raise AssertionError(
2067
 
                "Unexpected stream %r received" % substream_kind)
2068
 
        for record in substream:
2069
 
            (parent_id, new_id, versioned_root, tree_references, invdelta) = (
2070
 
                deserializer.parse_text_bytes(record.get_bytes_as("lines")))
2071
 
            if parent_id != prev_inv.revision_id:
2072
 
                raise AssertionError("invalid base %r != %r" % (parent_id,
2073
 
                                                                prev_inv.revision_id))
2074
 
            inv = prev_inv.create_by_apply_delta(invdelta, new_id)
2075
 
            yield inv, inv.revision_id
2076
 
            prev_inv = inv
2077
 
 
2078
 
    def _iter_inventories_vfs(self, revision_ids, ordering=None):
2079
1291
        self._ensure_real()
2080
 
        return self._real_repository._iter_inventories(revision_ids, ordering)
 
1292
        return self._real_repository.get_inventory(revision_id)
2081
1293
 
2082
1294
    def iter_inventories(self, revision_ids, ordering=None):
2083
 
        """Get many inventories by revision_ids.
2084
 
 
2085
 
        This will buffer some or all of the texts used in constructing the
2086
 
        inventories in memory, but will only parse a single inventory at a
2087
 
        time.
2088
 
 
2089
 
        :param revision_ids: The expected revision ids of the inventories.
2090
 
        :param ordering: optional ordering, e.g. 'topological'.  If not
2091
 
            specified, the order of revision_ids will be preserved (by
2092
 
            buffering if necessary).
2093
 
        :return: An iterator of inventories.
2094
 
        """
2095
 
        if ((None in revision_ids) or
2096
 
                (_mod_revision.NULL_REVISION in revision_ids)):
2097
 
            raise ValueError('cannot get null revision inventory')
2098
 
        for inv, revid in self._iter_inventories(revision_ids, ordering):
2099
 
            if inv is None:
2100
 
                raise errors.NoSuchRevision(self, revid)
2101
 
            yield inv
2102
 
 
2103
 
    def _iter_inventories(self, revision_ids, ordering=None):
2104
 
        if len(revision_ids) == 0:
2105
 
            return
2106
 
        missing = set(revision_ids)
2107
 
        if ordering is None:
2108
 
            order_as_requested = True
2109
 
            invs = {}
2110
 
            order = list(revision_ids)
2111
 
            order.reverse()
2112
 
            next_revid = order.pop()
2113
 
        else:
2114
 
            order_as_requested = False
2115
 
            if ordering != 'unordered' and self._fallback_repositories:
2116
 
                raise ValueError('unsupported ordering %r' % ordering)
2117
 
        iter_inv_fns = [self._iter_inventories_rpc] + [
2118
 
            fallback._iter_inventories for fallback in
2119
 
            self._fallback_repositories]
2120
 
        try:
2121
 
            for iter_inv in iter_inv_fns:
2122
 
                request = [revid for revid in revision_ids if revid in missing]
2123
 
                for inv, revid in iter_inv(request, ordering):
2124
 
                    if inv is None:
2125
 
                        continue
2126
 
                    missing.remove(inv.revision_id)
2127
 
                    if ordering != 'unordered':
2128
 
                        invs[revid] = inv
2129
 
                    else:
2130
 
                        yield inv, revid
2131
 
                if order_as_requested:
2132
 
                    # Yield as many results as we can while preserving order.
2133
 
                    while next_revid in invs:
2134
 
                        inv = invs.pop(next_revid)
2135
 
                        yield inv, inv.revision_id
2136
 
                        try:
2137
 
                            next_revid = order.pop()
2138
 
                        except IndexError:
2139
 
                            # We still want to fully consume the stream, just
2140
 
                            # in case it is not actually finished at this point
2141
 
                            next_revid = None
2142
 
                            break
2143
 
        except errors.UnknownSmartMethod:
2144
 
            for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2145
 
                yield inv, revid
2146
 
            return
2147
 
        # Report missing
2148
 
        if order_as_requested:
2149
 
            if next_revid is not None:
2150
 
                yield None, next_revid
2151
 
            while order:
2152
 
                revid = order.pop()
2153
 
                yield invs.get(revid), revid
2154
 
        else:
2155
 
            while missing:
2156
 
                yield None, missing.pop()
2157
 
 
 
1295
        self._ensure_real()
 
1296
        return self._real_repository.iter_inventories(revision_ids, ordering)
 
1297
 
 
1298
    @needs_read_lock
2158
1299
    def get_revision(self, revision_id):
2159
 
        with self.lock_read():
2160
 
            return self.get_revisions([revision_id])[0]
 
1300
        self._ensure_real()
 
1301
        return self._real_repository.get_revision(revision_id)
2161
1302
 
2162
1303
    def get_transaction(self):
2163
1304
        self._ensure_real()
2164
1305
        return self._real_repository.get_transaction()
2165
1306
 
2166
 
    def clone(self, a_controldir, revision_id=None):
2167
 
        with self.lock_read():
2168
 
            dest_repo = self._create_sprouting_repo(
2169
 
                a_controldir, shared=self.is_shared())
2170
 
            self.copy_content_into(dest_repo, revision_id)
2171
 
            return dest_repo
 
1307
    @needs_read_lock
 
1308
    def clone(self, a_bzrdir, revision_id=None):
 
1309
        self._ensure_real()
 
1310
        return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
2172
1311
 
2173
1312
    def make_working_trees(self):
2174
1313
        """See Repository.make_working_trees"""
2175
 
        path = self.controldir._path_for_remote_call(self._client)
2176
 
        try:
2177
 
            response = self._call(b'Repository.make_working_trees', path)
2178
 
        except errors.UnknownSmartMethod:
2179
 
            self._ensure_real()
2180
 
            return self._real_repository.make_working_trees()
2181
 
        if response[0] not in (b'yes', b'no'):
2182
 
            raise SmartProtocolError(
2183
 
                'unexpected response code %s' % (response,))
2184
 
        return response[0] == b'yes'
 
1314
        self._ensure_real()
 
1315
        return self._real_repository.make_working_trees()
2185
1316
 
2186
1317
    def refresh_data(self):
2187
1318
        """Re-read any data needed to synchronise with disk.
2189
1320
        This method is intended to be called after another repository instance
2190
1321
        (such as one used by a smart server) has inserted data into the
2191
1322
        repository. On all repositories this will work outside of write groups.
2192
 
        Some repository formats (pack and newer for breezy native formats)
 
1323
        Some repository formats (pack and newer for bzrlib native formats)
2193
1324
        support refresh_data inside write groups. If called inside a write
2194
1325
        group on a repository that does not support refreshing in a write group
2195
1326
        IsInWriteGroupError will be raised.
2196
1327
        """
2197
1328
        if self._real_repository is not None:
2198
1329
            self._real_repository.refresh_data()
2199
 
        # Refresh the parents cache for this object
2200
 
        self._unstacked_provider.disable_cache()
2201
 
        self._unstacked_provider.enable_cache()
2202
1330
 
2203
1331
    def revision_ids_to_search_result(self, result_set):
2204
1332
        """Convert a set of revision ids to a graph SearchResult."""
2205
1333
        result_parents = set()
2206
 
        for parents in viewvalues(self.get_graph().get_parent_map(result_set)):
 
1334
        for parents in self.get_graph().get_parent_map(
 
1335
            result_set).itervalues():
2207
1336
            result_parents.update(parents)
2208
1337
        included_keys = result_set.intersection(result_parents)
2209
1338
        start_keys = result_set.difference(included_keys)
2210
1339
        exclude_keys = result_parents.difference(result_set)
2211
 
        result = vf_search.SearchResult(start_keys, exclude_keys,
2212
 
                                        len(result_set), result_set)
 
1340
        result = graph.SearchResult(start_keys, exclude_keys,
 
1341
            len(result_set), result_set)
2213
1342
        return result
2214
1343
 
2215
 
    def search_missing_revision_ids(self, other,
2216
 
                                    find_ghosts=True, revision_ids=None, if_present_ids=None,
2217
 
                                    limit=None):
 
1344
    @needs_read_lock
 
1345
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
2218
1346
        """Return the revision ids that other has that this does not.
2219
1347
 
2220
1348
        These are returned in topological order.
2221
1349
 
2222
1350
        revision_id: only return revision ids included by revision_id.
2223
1351
        """
2224
 
        with self.lock_read():
2225
 
            inter_repo = _mod_repository.InterRepository.get(other, self)
2226
 
            return inter_repo.search_missing_revision_ids(
2227
 
                find_ghosts=find_ghosts, revision_ids=revision_ids,
2228
 
                if_present_ids=if_present_ids, limit=limit)
 
1352
        return repository.InterRepository.get(
 
1353
            other, self).search_missing_revision_ids(revision_id, find_ghosts)
2229
1354
 
2230
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
2231
 
              fetch_spec=None, lossy=False):
 
1355
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1356
            fetch_spec=None):
2232
1357
        # No base implementation to use as RemoteRepository is not a subclass
2233
1358
        # of Repository; so this is a copy of Repository.fetch().
2234
1359
        if fetch_spec is not None and revision_id is not None:
2238
1363
            raise errors.InternalBzrError(
2239
1364
                "May not fetch while in a write group.")
2240
1365
        # fast path same-url fetch operations
2241
 
        if (self.has_same_location(source) and
2242
 
            fetch_spec is None and
2243
 
                self._has_same_fallbacks(source)):
 
1366
        if (self.has_same_location(source)
 
1367
            and fetch_spec is None
 
1368
            and self._has_same_fallbacks(source)):
2244
1369
            # check that last_revision is in 'from' and then return a
2245
1370
            # no-operation.
2246
 
            if (revision_id is not None
2247
 
                    and not _mod_revision.is_null(revision_id)):
 
1371
            if (revision_id is not None and
 
1372
                not revision.is_null(revision_id)):
2248
1373
                self.get_revision(revision_id)
2249
 
            return _mod_repository.FetchResult(0)
 
1374
            return 0, []
2250
1375
        # if there is no specific appropriate InterRepository, this will get
2251
1376
        # the InterRepository base class, which raises an
2252
1377
        # IncompatibleRepositories when asked to fetch.
2253
 
        inter = _mod_repository.InterRepository.get(source, self)
2254
 
        if (fetch_spec is not None
2255
 
                and not getattr(inter, "supports_fetch_spec", False)):
2256
 
            raise errors.UnsupportedOperation(
2257
 
                "fetch_spec not supported for %r" % inter)
2258
 
        return inter.fetch(revision_id=revision_id,
2259
 
                           find_ghosts=find_ghosts, fetch_spec=fetch_spec,
2260
 
                           lossy=lossy)
 
1378
        inter = repository.InterRepository.get(source, self)
 
1379
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1380
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2261
1381
 
2262
1382
    def create_bundle(self, target, base, fileobj, format=None):
2263
1383
        self._ensure_real()
2264
1384
        self._real_repository.create_bundle(target, base, fileobj, format)
2265
1385
 
 
1386
    @needs_read_lock
 
1387
    def get_ancestry(self, revision_id, topo_sorted=True):
 
1388
        self._ensure_real()
 
1389
        return self._real_repository.get_ancestry(revision_id, topo_sorted)
 
1390
 
2266
1391
    def fileids_altered_by_revision_ids(self, revision_ids):
2267
1392
        self._ensure_real()
2268
1393
        return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2272
1397
        return self._real_repository._get_versioned_file_checker(
2273
1398
            revisions, revision_versions_cache)
2274
1399
 
2275
 
    def _iter_files_bytes_rpc(self, desired_files, absent):
2276
 
        path = self.controldir._path_for_remote_call(self._client)
2277
 
        lines = []
2278
 
        identifiers = []
2279
 
        for (file_id, revid, identifier) in desired_files:
2280
 
            lines.append(b''.join([
2281
 
                osutils.safe_file_id(file_id),
2282
 
                b'\0',
2283
 
                osutils.safe_revision_id(revid)]))
2284
 
            identifiers.append(identifier)
2285
 
        (response_tuple, response_handler) = (
2286
 
            self._call_with_body_bytes_expecting_body(
2287
 
                b"Repository.iter_files_bytes", (path, ), b"\n".join(lines)))
2288
 
        if response_tuple != (b'ok', ):
2289
 
            response_handler.cancel_read_body()
2290
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2291
 
        byte_stream = response_handler.read_streamed_body()
2292
 
 
2293
 
        def decompress_stream(start, byte_stream, unused):
2294
 
            decompressor = zlib.decompressobj()
2295
 
            yield decompressor.decompress(start)
2296
 
            while decompressor.unused_data == b"":
2297
 
                try:
2298
 
                    data = next(byte_stream)
2299
 
                except StopIteration:
2300
 
                    break
2301
 
                yield decompressor.decompress(data)
2302
 
            yield decompressor.flush()
2303
 
            unused.append(decompressor.unused_data)
2304
 
        unused = b""
2305
 
        while True:
2306
 
            while b"\n" not in unused:
2307
 
                try:
2308
 
                    unused += next(byte_stream)
2309
 
                except StopIteration:
2310
 
                    return
2311
 
            header, rest = unused.split(b"\n", 1)
2312
 
            args = header.split(b"\0")
2313
 
            if args[0] == b"absent":
2314
 
                absent[identifiers[int(args[3])]] = (args[1], args[2])
2315
 
                unused = rest
2316
 
                continue
2317
 
            elif args[0] == b"ok":
2318
 
                idx = int(args[1])
2319
 
            else:
2320
 
                raise errors.UnexpectedSmartServerResponse(args)
2321
 
            unused_chunks = []
2322
 
            yield (identifiers[idx],
2323
 
                   decompress_stream(rest, byte_stream, unused_chunks))
2324
 
            unused = b"".join(unused_chunks)
2325
 
 
2326
1400
    def iter_files_bytes(self, desired_files):
2327
1401
        """See Repository.iter_file_bytes.
2328
1402
        """
2329
 
        try:
2330
 
            absent = {}
2331
 
            for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2332
 
                    desired_files, absent):
2333
 
                yield identifier, bytes_iterator
2334
 
            for fallback in self._fallback_repositories:
2335
 
                if not absent:
2336
 
                    break
2337
 
                desired_files = [(key[0], key[1], identifier)
2338
 
                                 for identifier, key in viewitems(absent)]
2339
 
                for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2340
 
                    del absent[identifier]
2341
 
                    yield identifier, bytes_iterator
2342
 
            if absent:
2343
 
                # There may be more missing items, but raise an exception
2344
 
                # for just one.
2345
 
                missing_identifier = next(iter(absent))
2346
 
                missing_key = absent[missing_identifier]
2347
 
                raise errors.RevisionNotPresent(revision_id=missing_key[1],
2348
 
                                                file_id=missing_key[0])
2349
 
        except errors.UnknownSmartMethod:
2350
 
            self._ensure_real()
2351
 
            for (identifier, bytes_iterator) in (
2352
 
                    self._real_repository.iter_files_bytes(desired_files)):
2353
 
                yield identifier, bytes_iterator
2354
 
 
2355
 
    def get_cached_parent_map(self, revision_ids):
2356
 
        """See breezy.CachingParentsProvider.get_cached_parent_map"""
2357
 
        return self._unstacked_provider.get_cached_parent_map(revision_ids)
 
1403
        self._ensure_real()
 
1404
        return self._real_repository.iter_files_bytes(desired_files)
2358
1405
 
2359
1406
    def get_parent_map(self, revision_ids):
2360
 
        """See breezy.Graph.get_parent_map()."""
 
1407
        """See bzrlib.Graph.get_parent_map()."""
2361
1408
        return self._make_parents_provider().get_parent_map(revision_ids)
2362
1409
 
2363
1410
    def _get_parent_map_rpc(self, keys):
2382
1429
            # There is one other "bug" which is that ghosts in
2383
1430
            # get_revision_graph() are not returned at all. But we won't worry
2384
1431
            # about that for now.
2385
 
            for node_id, parent_ids in viewitems(rg):
 
1432
            for node_id, parent_ids in rg.iteritems():
2386
1433
                if parent_ids == ():
2387
1434
                    rg[node_id] = (NULL_REVISION,)
2388
1435
            rg[NULL_REVISION] = ()
2393
1440
            raise ValueError('get_parent_map(None) is not valid')
2394
1441
        if NULL_REVISION in keys:
2395
1442
            keys.discard(NULL_REVISION)
2396
 
            found_parents = {NULL_REVISION: ()}
 
1443
            found_parents = {NULL_REVISION:()}
2397
1444
            if not keys:
2398
1445
                return found_parents
2399
1446
        else:
2419
1466
        if parents_map is None:
2420
1467
            # Repository is not locked, so there's no cache.
2421
1468
            parents_map = {}
2422
 
        if _DEFAULT_SEARCH_DEPTH <= 0:
2423
 
            (start_set, stop_keys,
2424
 
             key_count) = vf_search.search_result_from_parent_map(
2425
 
                parents_map, self._unstacked_provider.missing_keys)
2426
 
        else:
2427
 
            (start_set, stop_keys,
2428
 
             key_count) = vf_search.limited_search_result_from_parent_map(
2429
 
                parents_map, self._unstacked_provider.missing_keys,
2430
 
                keys, depth=_DEFAULT_SEARCH_DEPTH)
 
1469
        # start_set is all the keys in the cache
 
1470
        start_set = set(parents_map)
 
1471
        # result set is all the references to keys in the cache
 
1472
        result_parents = set()
 
1473
        for parents in parents_map.itervalues():
 
1474
            result_parents.update(parents)
 
1475
        stop_keys = result_parents.difference(start_set)
 
1476
        # We don't need to send ghosts back to the server as a position to
 
1477
        # stop either.
 
1478
        stop_keys.difference_update(self._unstacked_provider.missing_keys)
 
1479
        key_count = len(parents_map)
 
1480
        if (NULL_REVISION in result_parents
 
1481
            and NULL_REVISION in self._unstacked_provider.missing_keys):
 
1482
            # If we pruned NULL_REVISION from the stop_keys because it's also
 
1483
            # in our cache of "missing" keys we need to increment our key count
 
1484
            # by 1, because the reconsitituted SearchResult on the server will
 
1485
            # still consider NULL_REVISION to be an included key.
 
1486
            key_count += 1
 
1487
        included_keys = start_set.intersection(result_parents)
 
1488
        start_set.difference_update(included_keys)
2431
1489
        recipe = ('manual', start_set, stop_keys, key_count)
2432
1490
        body = self._serialise_search_recipe(recipe)
2433
 
        path = self.controldir._path_for_remote_call(self._client)
 
1491
        path = self.bzrdir._path_for_remote_call(self._client)
2434
1492
        for key in keys:
2435
 
            if not isinstance(key, bytes):
 
1493
            if type(key) is not str:
2436
1494
                raise ValueError(
2437
 
                    "key %r not a bytes string" % (key,))
2438
 
        verb = b'Repository.get_parent_map'
2439
 
        args = (path, b'include-missing:') + tuple(keys)
 
1495
                    "key %r not a plain string" % (key,))
 
1496
        verb = 'Repository.get_parent_map'
 
1497
        args = (path, 'include-missing:') + tuple(keys)
2440
1498
        try:
2441
1499
            response = self._call_with_body_bytes_expecting_body(
2442
1500
                verb, args, body)
2455
1513
            # Recurse just once and we should use the fallback code.
2456
1514
            return self._get_parent_map_rpc(keys)
2457
1515
        response_tuple, response_handler = response
2458
 
        if response_tuple[0] not in [b'ok']:
 
1516
        if response_tuple[0] not in ['ok']:
2459
1517
            response_handler.cancel_read_body()
2460
1518
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2461
 
        if response_tuple[0] == b'ok':
 
1519
        if response_tuple[0] == 'ok':
2462
1520
            coded = bz2.decompress(response_handler.read_body_bytes())
2463
 
            if coded == b'':
 
1521
            if coded == '':
2464
1522
                # no revisions found
2465
1523
                return {}
2466
 
            lines = coded.split(b'\n')
 
1524
            lines = coded.split('\n')
2467
1525
            revision_graph = {}
2468
1526
            for line in lines:
2469
1527
                d = tuple(line.split())
2471
1529
                    revision_graph[d[0]] = d[1:]
2472
1530
                else:
2473
1531
                    # No parents:
2474
 
                    if d[0].startswith(b'missing:'):
 
1532
                    if d[0].startswith('missing:'):
2475
1533
                        revid = d[0][8:]
2476
1534
                        self._unstacked_provider.note_missing_key(revid)
2477
1535
                    else:
2480
1538
                        revision_graph[d[0]] = (NULL_REVISION,)
2481
1539
            return revision_graph
2482
1540
 
 
1541
    @needs_read_lock
2483
1542
    def get_signature_text(self, revision_id):
2484
 
        with self.lock_read():
2485
 
            path = self.controldir._path_for_remote_call(self._client)
2486
 
            try:
2487
 
                response_tuple, response_handler = self._call_expecting_body(
2488
 
                    b'Repository.get_revision_signature_text', path, revision_id)
2489
 
            except errors.UnknownSmartMethod:
2490
 
                self._ensure_real()
2491
 
                return self._real_repository.get_signature_text(revision_id)
2492
 
            except errors.NoSuchRevision as err:
2493
 
                for fallback in self._fallback_repositories:
2494
 
                    try:
2495
 
                        return fallback.get_signature_text(revision_id)
2496
 
                    except errors.NoSuchRevision:
2497
 
                        pass
2498
 
                raise err
2499
 
            else:
2500
 
                if response_tuple[0] != b'ok':
2501
 
                    raise errors.UnexpectedSmartServerResponse(response_tuple)
2502
 
                return response_handler.read_body_bytes()
 
1543
        self._ensure_real()
 
1544
        return self._real_repository.get_signature_text(revision_id)
2503
1545
 
 
1546
    @needs_read_lock
2504
1547
    def _get_inventory_xml(self, revision_id):
2505
 
        with self.lock_read():
2506
 
            # This call is used by older working tree formats,
2507
 
            # which stored a serialized basis inventory.
2508
 
            self._ensure_real()
2509
 
            return self._real_repository._get_inventory_xml(revision_id)
 
1548
        self._ensure_real()
 
1549
        return self._real_repository._get_inventory_xml(revision_id)
2510
1550
 
2511
1551
    def reconcile(self, other=None, thorough=False):
2512
 
        from ..reconcile import ReconcileResult
2513
 
        with self.lock_write():
2514
 
            path = self.controldir._path_for_remote_call(self._client)
2515
 
            try:
2516
 
                response, handler = self._call_expecting_body(
2517
 
                    b'Repository.reconcile', path, self._lock_token)
2518
 
            except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2519
 
                self._ensure_real()
2520
 
                return self._real_repository.reconcile(other=other, thorough=thorough)
2521
 
            if response != (b'ok', ):
2522
 
                raise errors.UnexpectedSmartServerResponse(response)
2523
 
            body = handler.read_body_bytes()
2524
 
            result = ReconcileResult()
2525
 
            result.garbage_inventories = None
2526
 
            result.inconsistent_parents = None
2527
 
            result.aborted = None
2528
 
            for line in body.split(b'\n'):
2529
 
                if not line:
2530
 
                    continue
2531
 
                key, val_text = line.split(b':')
2532
 
                if key == b"garbage_inventories":
2533
 
                    result.garbage_inventories = int(val_text)
2534
 
                elif key == b"inconsistent_parents":
2535
 
                    result.inconsistent_parents = int(val_text)
2536
 
                else:
2537
 
                    mutter("unknown reconcile key %r" % key)
2538
 
            return result
 
1552
        self._ensure_real()
 
1553
        return self._real_repository.reconcile(other=other, thorough=thorough)
2539
1554
 
2540
1555
    def all_revision_ids(self):
2541
 
        path = self.controldir._path_for_remote_call(self._client)
2542
 
        try:
2543
 
            response_tuple, response_handler = self._call_expecting_body(
2544
 
                b"Repository.all_revision_ids", path)
2545
 
        except errors.UnknownSmartMethod:
2546
 
            self._ensure_real()
2547
 
            return self._real_repository.all_revision_ids()
2548
 
        if response_tuple != (b"ok", ):
2549
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2550
 
        revids = set(response_handler.read_body_bytes().splitlines())
2551
 
        for fallback in self._fallback_repositories:
2552
 
            revids.update(set(fallback.all_revision_ids()))
2553
 
        return list(revids)
2554
 
 
2555
 
    def _filtered_revision_trees(self, revision_ids, file_ids):
2556
 
        """Return Tree for a revision on this branch with only some files.
2557
 
 
2558
 
        :param revision_ids: a sequence of revision-ids;
2559
 
          a revision-id may not be None or b'null:'
2560
 
        :param file_ids: if not None, the result is filtered
2561
 
          so that only those file-ids, their parents and their
2562
 
          children are included.
2563
 
        """
2564
 
        inventories = self.iter_inventories(revision_ids)
2565
 
        for inv in inventories:
2566
 
            # Should we introduce a FilteredRevisionTree class rather
2567
 
            # than pre-filter the inventory here?
2568
 
            filtered_inv = inv.filter(file_ids)
2569
 
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2570
 
 
 
1556
        self._ensure_real()
 
1557
        return self._real_repository.all_revision_ids()
 
1558
 
 
1559
    @needs_read_lock
2571
1560
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2572
 
        with self.lock_read():
2573
 
            medium = self._client._medium
2574
 
            if medium._is_remote_before((1, 2)):
2575
 
                self._ensure_real()
2576
 
                for delta in self._real_repository.get_deltas_for_revisions(
2577
 
                        revisions, specific_fileids):
2578
 
                    yield delta
2579
 
                return
2580
 
            # Get the revision-ids of interest
2581
 
            required_trees = set()
2582
 
            for revision in revisions:
2583
 
                required_trees.add(revision.revision_id)
2584
 
                required_trees.update(revision.parent_ids[:1])
2585
 
 
2586
 
            # Get the matching filtered trees. Note that it's more
2587
 
            # efficient to pass filtered trees to changes_from() rather
2588
 
            # than doing the filtering afterwards. changes_from() could
2589
 
            # arguably do the filtering itself but it's path-based, not
2590
 
            # file-id based, so filtering before or afterwards is
2591
 
            # currently easier.
2592
 
            if specific_fileids is None:
2593
 
                trees = dict((t.get_revision_id(), t) for
2594
 
                             t in self.revision_trees(required_trees))
2595
 
            else:
2596
 
                trees = dict((t.get_revision_id(), t) for
2597
 
                             t in self._filtered_revision_trees(required_trees,
2598
 
                                                                specific_fileids))
2599
 
 
2600
 
            # Calculate the deltas
2601
 
            for revision in revisions:
2602
 
                if not revision.parent_ids:
2603
 
                    old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2604
 
                else:
2605
 
                    old_tree = trees[revision.parent_ids[0]]
2606
 
                yield trees[revision.revision_id].changes_from(old_tree)
2607
 
 
2608
 
    def get_revision_delta(self, revision_id):
2609
 
        with self.lock_read():
2610
 
            r = self.get_revision(revision_id)
2611
 
            return list(self.get_deltas_for_revisions([r]))[0]
2612
 
 
 
1561
        self._ensure_real()
 
1562
        return self._real_repository.get_deltas_for_revisions(revisions,
 
1563
            specific_fileids=specific_fileids)
 
1564
 
 
1565
    @needs_read_lock
 
1566
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
1567
        self._ensure_real()
 
1568
        return self._real_repository.get_revision_delta(revision_id,
 
1569
            specific_fileids=specific_fileids)
 
1570
 
 
1571
    @needs_read_lock
2613
1572
    def revision_trees(self, revision_ids):
2614
 
        with self.lock_read():
2615
 
            inventories = self.iter_inventories(revision_ids)
2616
 
            for inv in inventories:
2617
 
                yield RemoteInventoryTree(self, inv, inv.revision_id)
 
1573
        self._ensure_real()
 
1574
        return self._real_repository.revision_trees(revision_ids)
2618
1575
 
 
1576
    @needs_read_lock
2619
1577
    def get_revision_reconcile(self, revision_id):
2620
 
        with self.lock_read():
2621
 
            self._ensure_real()
2622
 
            return self._real_repository.get_revision_reconcile(revision_id)
 
1578
        self._ensure_real()
 
1579
        return self._real_repository.get_revision_reconcile(revision_id)
2623
1580
 
 
1581
    @needs_read_lock
2624
1582
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2625
 
        with self.lock_read():
2626
 
            self._ensure_real()
2627
 
            return self._real_repository.check(revision_ids=revision_ids,
2628
 
                                               callback_refs=callback_refs, check_repo=check_repo)
 
1583
        self._ensure_real()
 
1584
        return self._real_repository.check(revision_ids=revision_ids,
 
1585
            callback_refs=callback_refs, check_repo=check_repo)
2629
1586
 
2630
1587
    def copy_content_into(self, destination, revision_id=None):
2631
 
        """Make a complete copy of the content in self into destination.
2632
 
 
2633
 
        This is a destructive operation! Do not use it on existing
2634
 
        repositories.
2635
 
        """
2636
 
        interrepo = _mod_repository.InterRepository.get(self, destination)
2637
 
        return interrepo.copy_content(revision_id)
 
1588
        self._ensure_real()
 
1589
        return self._real_repository.copy_content_into(
 
1590
            destination, revision_id=revision_id)
2638
1591
 
2639
1592
    def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2640
1593
        # get a tarball of the remote repository, and copy from that into the
2641
1594
        # destination
 
1595
        from bzrlib import osutils
2642
1596
        import tarfile
2643
1597
        # TODO: Maybe a progress bar while streaming the tarball?
2644
 
        note(gettext("Copying repository content as tarball..."))
 
1598
        note("Copying repository content as tarball...")
2645
1599
        tar_file = self._get_tarball('bz2')
2646
1600
        if tar_file is None:
2647
1601
            return None
2648
1602
        destination = to_bzrdir.create_repository()
2649
1603
        try:
2650
1604
            tar = tarfile.open('repository', fileobj=tar_file,
2651
 
                               mode='r|bz2')
 
1605
                mode='r|bz2')
2652
1606
            tmpdir = osutils.mkdtemp()
2653
1607
            try:
2654
 
                tar.extractall(tmpdir)
2655
 
                tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
 
1608
                _extract_tar(tar, tmpdir)
 
1609
                tmp_bzrdir = BzrDir.open(tmpdir)
2656
1610
                tmp_repo = tmp_bzrdir.open_repository()
2657
1611
                tmp_repo.copy_content_into(destination, revision_id)
2658
1612
            finally:
2673
1627
        self._ensure_real()
2674
1628
        return self._real_repository.inventories
2675
1629
 
 
1630
    @needs_write_lock
2676
1631
    def pack(self, hint=None, clean_obsolete_packs=False):
2677
1632
        """Compress the data within the repository.
 
1633
 
 
1634
        This is not currently implemented within the smart server.
2678
1635
        """
2679
 
        if hint is None:
2680
 
            body = b""
2681
 
        else:
2682
 
            body = b"".join([l.encode('ascii') + b"\n" for l in hint])
2683
 
        with self.lock_write():
2684
 
            path = self.controldir._path_for_remote_call(self._client)
2685
 
            try:
2686
 
                response, handler = self._call_with_body_bytes_expecting_body(
2687
 
                    b'Repository.pack', (path, self._lock_token,
2688
 
                                         str(clean_obsolete_packs).encode('ascii')), body)
2689
 
            except errors.UnknownSmartMethod:
2690
 
                self._ensure_real()
2691
 
                return self._real_repository.pack(hint=hint,
2692
 
                                                  clean_obsolete_packs=clean_obsolete_packs)
2693
 
            handler.cancel_read_body()
2694
 
            if response != (b'ok', ):
2695
 
                raise errors.UnexpectedSmartServerResponse(response)
 
1636
        self._ensure_real()
 
1637
        return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
2696
1638
 
2697
1639
    @property
2698
1640
    def revisions(self):
2699
1641
        """Decorate the real repository for now.
2700
1642
 
 
1643
        In the short term this should become a real object to intercept graph
 
1644
        lookups.
 
1645
 
2701
1646
        In the long term a full blown network facility is needed.
2702
1647
        """
2703
1648
        self._ensure_real()
2705
1650
 
2706
1651
    def set_make_working_trees(self, new_value):
2707
1652
        if new_value:
2708
 
            new_value_str = b"True"
 
1653
            new_value_str = "True"
2709
1654
        else:
2710
 
            new_value_str = b"False"
2711
 
        path = self.controldir._path_for_remote_call(self._client)
 
1655
            new_value_str = "False"
 
1656
        path = self.bzrdir._path_for_remote_call(self._client)
2712
1657
        try:
2713
1658
            response = self._call(
2714
 
                b'Repository.set_make_working_trees', path, new_value_str)
 
1659
                'Repository.set_make_working_trees', path, new_value_str)
2715
1660
        except errors.UnknownSmartMethod:
2716
1661
            self._ensure_real()
2717
1662
            self._real_repository.set_make_working_trees(new_value)
2718
1663
        else:
2719
 
            if response[0] != b'ok':
 
1664
            if response[0] != 'ok':
2720
1665
                raise errors.UnexpectedSmartServerResponse(response)
2721
1666
 
2722
1667
    @property
2729
1674
        self._ensure_real()
2730
1675
        return self._real_repository.signatures
2731
1676
 
 
1677
    @needs_write_lock
2732
1678
    def sign_revision(self, revision_id, gpg_strategy):
2733
 
        with self.lock_write():
2734
 
            testament = _mod_testament.Testament.from_revision(
2735
 
                self, revision_id)
2736
 
            plaintext = testament.as_short_text()
2737
 
            self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
1679
        self._ensure_real()
 
1680
        return self._real_repository.sign_revision(revision_id, gpg_strategy)
2738
1681
 
2739
1682
    @property
2740
1683
    def texts(self):
2746
1689
        self._ensure_real()
2747
1690
        return self._real_repository.texts
2748
1691
 
2749
 
    def _iter_revisions_rpc(self, revision_ids):
2750
 
        body = b"\n".join(revision_ids)
2751
 
        path = self.controldir._path_for_remote_call(self._client)
2752
 
        response_tuple, response_handler = (
2753
 
            self._call_with_body_bytes_expecting_body(
2754
 
                b"Repository.iter_revisions", (path, ), body))
2755
 
        if response_tuple[0] != b"ok":
2756
 
            raise errors.UnexpectedSmartServerResponse(response_tuple)
2757
 
        serializer_format = response_tuple[1].decode('ascii')
2758
 
        serializer = serializer_format_registry.get(serializer_format)
2759
 
        byte_stream = response_handler.read_streamed_body()
2760
 
        decompressor = zlib.decompressobj()
2761
 
        chunks = []
2762
 
        for bytes in byte_stream:
2763
 
            chunks.append(decompressor.decompress(bytes))
2764
 
            if decompressor.unused_data != b"":
2765
 
                chunks.append(decompressor.flush())
2766
 
                yield serializer.read_revision_from_string(b"".join(chunks))
2767
 
                unused = decompressor.unused_data
2768
 
                decompressor = zlib.decompressobj()
2769
 
                chunks = [decompressor.decompress(unused)]
2770
 
        chunks.append(decompressor.flush())
2771
 
        text = b"".join(chunks)
2772
 
        if text != b"":
2773
 
            yield serializer.read_revision_from_string(b"".join(chunks))
2774
 
 
2775
 
    def iter_revisions(self, revision_ids):
2776
 
        for rev_id in revision_ids:
2777
 
            if not rev_id or not isinstance(rev_id, bytes):
2778
 
                raise errors.InvalidRevisionId(
2779
 
                    revision_id=rev_id, branch=self)
2780
 
        with self.lock_read():
2781
 
            try:
2782
 
                missing = set(revision_ids)
2783
 
                for rev in self._iter_revisions_rpc(revision_ids):
2784
 
                    missing.remove(rev.revision_id)
2785
 
                    yield (rev.revision_id, rev)
2786
 
                for fallback in self._fallback_repositories:
2787
 
                    if not missing:
2788
 
                        break
2789
 
                    for (revid, rev) in fallback.iter_revisions(missing):
2790
 
                        if rev is not None:
2791
 
                            yield (revid, rev)
2792
 
                            missing.remove(revid)
2793
 
                for revid in missing:
2794
 
                    yield (revid, None)
2795
 
            except errors.UnknownSmartMethod:
2796
 
                self._ensure_real()
2797
 
                for entry in self._real_repository.iter_revisions(revision_ids):
2798
 
                    yield entry
 
1692
    @needs_read_lock
 
1693
    def get_revisions(self, revision_ids):
 
1694
        self._ensure_real()
 
1695
        return self._real_repository.get_revisions(revision_ids)
2799
1696
 
2800
1697
    def supports_rich_root(self):
2801
1698
        return self._format.rich_root_data
2802
1699
 
 
1700
    def iter_reverse_revision_history(self, revision_id):
 
1701
        self._ensure_real()
 
1702
        return self._real_repository.iter_reverse_revision_history(revision_id)
 
1703
 
2803
1704
    @property
2804
1705
    def _serializer(self):
2805
1706
        return self._format._serializer
2806
1707
 
2807
1708
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2808
 
        with self.lock_write():
2809
 
            signature = gpg_strategy.sign(plaintext, gpg.MODE_CLEAR)
2810
 
            self.add_signature_text(revision_id, signature)
 
1709
        self._ensure_real()
 
1710
        return self._real_repository.store_revision_signature(
 
1711
            gpg_strategy, plaintext, revision_id)
2811
1712
 
2812
1713
    def add_signature_text(self, revision_id, signature):
2813
 
        if self._real_repository:
2814
 
            # If there is a real repository the write group will
2815
 
            # be in the real repository as well, so use that:
2816
 
            self._ensure_real()
2817
 
            return self._real_repository.add_signature_text(
2818
 
                revision_id, signature)
2819
 
        path = self.controldir._path_for_remote_call(self._client)
2820
 
        response, handler = self._call_with_body_bytes_expecting_body(
2821
 
            b'Repository.add_signature_text', (path, self._lock_token,
2822
 
                                               revision_id) +
2823
 
            tuple([token.encode('utf-8')
2824
 
                   for token in self._write_group_tokens]),
2825
 
            signature)
2826
 
        handler.cancel_read_body()
2827
 
        self.refresh_data()
2828
 
        if response[0] != b'ok':
2829
 
            raise errors.UnexpectedSmartServerResponse(response)
2830
 
        self._write_group_tokens = [token.decode(
2831
 
            'utf-8') for token in response[1:]]
 
1714
        self._ensure_real()
 
1715
        return self._real_repository.add_signature_text(revision_id, signature)
2832
1716
 
2833
1717
    def has_signature_for_revision_id(self, revision_id):
2834
 
        path = self.controldir._path_for_remote_call(self._client)
2835
 
        try:
2836
 
            response = self._call(b'Repository.has_signature_for_revision_id',
2837
 
                                  path, revision_id)
2838
 
        except errors.UnknownSmartMethod:
2839
 
            self._ensure_real()
2840
 
            return self._real_repository.has_signature_for_revision_id(
2841
 
                revision_id)
2842
 
        if response[0] not in (b'yes', b'no'):
2843
 
            raise SmartProtocolError(
2844
 
                'unexpected response code %s' % (response,))
2845
 
        if response[0] == b'yes':
2846
 
            return True
2847
 
        for fallback in self._fallback_repositories:
2848
 
            if fallback.has_signature_for_revision_id(revision_id):
2849
 
                return True
2850
 
        return False
2851
 
 
2852
 
    def verify_revision_signature(self, revision_id, gpg_strategy):
2853
 
        with self.lock_read():
2854
 
            if not self.has_signature_for_revision_id(revision_id):
2855
 
                return gpg.SIGNATURE_NOT_SIGNED, None
2856
 
            signature = self.get_signature_text(revision_id)
2857
 
 
2858
 
            testament = _mod_testament.Testament.from_revision(
2859
 
                self, revision_id)
2860
 
 
2861
 
            (status, key, signed_plaintext) = gpg_strategy.verify(signature)
2862
 
            if testament.as_short_text() != signed_plaintext:
2863
 
                return gpg.SIGNATURE_NOT_VALID, None
2864
 
            return (status, key)
 
1718
        self._ensure_real()
 
1719
        return self._real_repository.has_signature_for_revision_id(revision_id)
2865
1720
 
2866
1721
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2867
1722
        self._ensure_real()
2868
1723
        return self._real_repository.item_keys_introduced_by(revision_ids,
2869
 
                                                             _files_pb=_files_pb)
 
1724
            _files_pb=_files_pb)
 
1725
 
 
1726
    def revision_graph_can_have_wrong_parents(self):
 
1727
        # The answer depends on the remote repo format.
 
1728
        self._ensure_real()
 
1729
        return self._real_repository.revision_graph_can_have_wrong_parents()
2870
1730
 
2871
1731
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2872
1732
        self._ensure_real()
2881
1741
        providers = [self._unstacked_provider]
2882
1742
        if other is not None:
2883
1743
            providers.insert(0, other)
2884
 
        return graph.StackedParentsProvider(_LazyListJoin(
2885
 
            providers, self._fallback_repositories))
 
1744
        providers.extend(r._make_parents_provider() for r in
 
1745
                         self._fallback_repositories)
 
1746
        return graph.StackedParentsProvider(providers)
2886
1747
 
2887
1748
    def _serialise_search_recipe(self, recipe):
2888
1749
        """Serialise a graph search recipe.
2890
1751
        :param recipe: A search recipe (start, stop, count).
2891
1752
        :return: Serialised bytes.
2892
1753
        """
2893
 
        start_keys = b' '.join(recipe[1])
2894
 
        stop_keys = b' '.join(recipe[2])
2895
 
        count = str(recipe[3]).encode('ascii')
2896
 
        return b'\n'.join((start_keys, stop_keys, count))
 
1754
        start_keys = ' '.join(recipe[1])
 
1755
        stop_keys = ' '.join(recipe[2])
 
1756
        count = str(recipe[3])
 
1757
        return '\n'.join((start_keys, stop_keys, count))
2897
1758
 
2898
1759
    def _serialise_search_result(self, search_result):
2899
 
        parts = search_result.get_network_struct()
2900
 
        return b'\n'.join(parts)
 
1760
        if isinstance(search_result, graph.PendingAncestryResult):
 
1761
            parts = ['ancestry-of']
 
1762
            parts.extend(search_result.heads)
 
1763
        else:
 
1764
            recipe = search_result.get_recipe()
 
1765
            parts = [recipe[0], self._serialise_search_recipe(recipe)]
 
1766
        return '\n'.join(parts)
2901
1767
 
2902
1768
    def autopack(self):
2903
 
        path = self.controldir._path_for_remote_call(self._client)
 
1769
        path = self.bzrdir._path_for_remote_call(self._client)
2904
1770
        try:
2905
 
            response = self._call(b'PackRepository.autopack', path)
 
1771
            response = self._call('PackRepository.autopack', path)
2906
1772
        except errors.UnknownSmartMethod:
2907
1773
            self._ensure_real()
2908
1774
            self._real_repository._pack_collection.autopack()
2909
1775
            return
2910
1776
        self.refresh_data()
2911
 
        if response[0] != b'ok':
2912
 
            raise errors.UnexpectedSmartServerResponse(response)
2913
 
 
2914
 
    def _revision_archive(self, revision_id, format, name, root, subdir,
2915
 
                          force_mtime=None):
2916
 
        path = self.controldir._path_for_remote_call(self._client)
2917
 
        format = format or ''
2918
 
        root = root or ''
2919
 
        subdir = subdir or ''
2920
 
        force_mtime = int(force_mtime) if force_mtime is not None else None
2921
 
        try:
2922
 
            response, protocol = self._call_expecting_body(
2923
 
                b'Repository.revision_archive', path,
2924
 
                revision_id,
2925
 
                format.encode('ascii'),
2926
 
                os.path.basename(name).encode('utf-8'),
2927
 
                root.encode('utf-8'),
2928
 
                subdir.encode('utf-8'),
2929
 
                force_mtime)
2930
 
        except errors.UnknownSmartMethod:
2931
 
            return None
2932
 
        if response[0] == b'ok':
2933
 
            return iter([protocol.read_body_bytes()])
2934
 
        raise errors.UnexpectedSmartServerResponse(response)
2935
 
 
2936
 
    def _annotate_file_revision(self, revid, tree_path, file_id, default_revision):
2937
 
        path = self.controldir._path_for_remote_call(self._client)
2938
 
        tree_path = tree_path.encode('utf-8')
2939
 
        file_id = file_id or b''
2940
 
        default_revision = default_revision or b''
2941
 
        try:
2942
 
            response, handler = self._call_expecting_body(
2943
 
                b'Repository.annotate_file_revision', path,
2944
 
                revid, tree_path, file_id, default_revision)
2945
 
        except errors.UnknownSmartMethod:
2946
 
            return None
2947
 
        if response[0] != b'ok':
2948
 
            raise errors.UnexpectedSmartServerResponse(response)
2949
 
        return map(tuple, bencode.bdecode(handler.read_body_bytes()))
2950
 
 
2951
 
 
2952
 
class RemoteStreamSink(vf_repository.StreamSink):
 
1777
        if response[0] != 'ok':
 
1778
            raise errors.UnexpectedSmartServerResponse(response)
 
1779
 
 
1780
 
 
1781
class RemoteStreamSink(repository.StreamSink):
2953
1782
 
2954
1783
    def _insert_real(self, stream, src_format, resume_tokens):
2955
1784
        self.target_repo._ensure_real()
2959
1788
            self.target_repo.autopack()
2960
1789
        return result
2961
1790
 
2962
 
    def insert_missing_keys(self, source, missing_keys):
2963
 
        if (isinstance(source, RemoteStreamSource)
2964
 
                and source.from_repository._client._medium == self.target_repo._client._medium):
2965
 
            # Streaming from and to the same medium is tricky, since we don't support
2966
 
            # more than one concurrent request. For now, just force VFS.
2967
 
            stream = source._get_real_stream_for_missing_keys(missing_keys)
2968
 
        else:
2969
 
            stream = source.get_stream_for_missing_keys(missing_keys)
2970
 
        return self.insert_stream_without_locking(stream,
2971
 
                                                  self.target_repo._format)
2972
 
 
2973
1791
    def insert_stream(self, stream, src_format, resume_tokens):
2974
1792
        target = self.target_repo
2975
1793
        target._unstacked_provider.missing_keys.clear()
2976
 
        candidate_calls = [(b'Repository.insert_stream_1.19', (1, 19))]
 
1794
        candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2977
1795
        if target._lock_token:
2978
 
            candidate_calls.append(
2979
 
                (b'Repository.insert_stream_locked', (1, 14)))
2980
 
            lock_args = (target._lock_token or b'',)
 
1796
            candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
 
1797
            lock_args = (target._lock_token or '',)
2981
1798
        else:
2982
 
            candidate_calls.append((b'Repository.insert_stream', (1, 13)))
 
1799
            candidate_calls.append(('Repository.insert_stream', (1, 13)))
2983
1800
            lock_args = ()
2984
1801
        client = target._client
2985
1802
        medium = client._medium
2986
 
        path = target.controldir._path_for_remote_call(client)
 
1803
        path = target.bzrdir._path_for_remote_call(client)
2987
1804
        # Probe for the verb to use with an empty stream before sending the
2988
1805
        # real stream to it.  We do this both to avoid the risk of sending a
2989
1806
        # large request that is then rejected, and because we don't want to
3000
1817
            byte_stream = smart_repo._stream_to_byte_stream([], src_format)
3001
1818
            try:
3002
1819
                response = client.call_with_body_stream(
3003
 
                    (verb, path, b'') + lock_args, byte_stream)
 
1820
                    (verb, path, '') + lock_args, byte_stream)
3004
1821
            except errors.UnknownSmartMethod:
3005
1822
                medium._remember_remote_is_before(required_version)
3006
1823
            else:
3019
1836
            stream = self._stop_stream_if_inventory_delta(stream)
3020
1837
        byte_stream = smart_repo._stream_to_byte_stream(
3021
1838
            stream, src_format)
3022
 
        resume_tokens = b' '.join([token.encode('utf-8')
3023
 
                                   for token in resume_tokens])
 
1839
        resume_tokens = ' '.join(resume_tokens)
3024
1840
        response = client.call_with_body_stream(
3025
1841
            (verb, path, resume_tokens) + lock_args, byte_stream)
3026
 
        if response[0][0] not in (b'ok', b'missing-basis'):
 
1842
        if response[0][0] not in ('ok', 'missing-basis'):
3027
1843
            raise errors.UnexpectedSmartServerResponse(response)
3028
1844
        if self._last_substream is not None:
3029
1845
            # The stream included an inventory-delta record, but the remote
3031
1847
            # rest of the stream via VFS.
3032
1848
            self.target_repo.refresh_data()
3033
1849
            return self._resume_stream_with_vfs(response, src_format)
3034
 
        if response[0][0] == b'missing-basis':
 
1850
        if response[0][0] == 'missing-basis':
3035
1851
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3036
 
            resume_tokens = [token.decode('utf-8') for token in tokens]
3037
 
            return resume_tokens, set((entry[0].decode('utf-8'), ) + entry[1:] for entry in missing_keys)
 
1852
            resume_tokens = tokens
 
1853
            return resume_tokens, set(missing_keys)
3038
1854
        else:
3039
1855
            self.target_repo.refresh_data()
3040
1856
            return [], set()
3043
1859
        """Resume sending a stream via VFS, first resending the record and
3044
1860
        substream that couldn't be sent via an insert_stream verb.
3045
1861
        """
3046
 
        if response[0][0] == b'missing-basis':
 
1862
        if response[0][0] == 'missing-basis':
3047
1863
            tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
3048
 
            tokens = [token.decode('utf-8') for token in tokens]
3049
1864
            # Ignore missing_keys, we haven't finished inserting yet
3050
1865
        else:
3051
1866
            tokens = []
3052
 
 
3053
1867
        def resume_substream():
3054
1868
            # Yield the substream that was interrupted.
3055
1869
            for record in self._last_substream:
3056
1870
                yield record
3057
1871
            self._last_substream = None
3058
 
 
3059
1872
        def resume_stream():
3060
1873
            # Finish sending the interrupted substream
3061
1874
            yield ('inventory-deltas', resume_substream())
3072
1885
        self._last_substream and self._last_stream so that the stream can be
3073
1886
        resumed by _resume_stream_with_vfs.
3074
1887
        """
3075
 
 
 
1888
                    
3076
1889
        stream_iter = iter(stream)
3077
1890
        for substream_kind, substream in stream_iter:
3078
1891
            if substream_kind == 'inventory-deltas':
3081
1894
                return
3082
1895
            else:
3083
1896
                yield substream_kind, substream
3084
 
 
3085
 
 
3086
 
class RemoteStreamSource(vf_repository.StreamSource):
 
1897
            
 
1898
 
 
1899
class RemoteStreamSource(repository.StreamSource):
3087
1900
    """Stream data from a remote server."""
3088
1901
 
3089
1902
    def get_stream(self, search):
3090
 
        if (self.from_repository._fallback_repositories
3091
 
                and self.to_format._fetch_order == 'topological'):
 
1903
        if (self.from_repository._fallback_repositories and
 
1904
            self.to_format._fetch_order == 'topological'):
3092
1905
            return self._real_stream(self.from_repository, search)
3093
1906
        sources = []
3094
1907
        seen = set()
3102
1915
            sources.append(repo)
3103
1916
        return self.missing_parents_chain(search, sources)
3104
1917
 
3105
 
    def _get_real_stream_for_missing_keys(self, missing_keys):
 
1918
    def get_stream_for_missing_keys(self, missing_keys):
3106
1919
        self.from_repository._ensure_real()
3107
1920
        real_repo = self.from_repository._real_repository
3108
1921
        real_source = real_repo._get_source(self.to_format)
3109
1922
        return real_source.get_stream_for_missing_keys(missing_keys)
3110
1923
 
3111
 
    def get_stream_for_missing_keys(self, missing_keys):
3112
 
        if not isinstance(self.from_repository, RemoteRepository):
3113
 
            return self._get_real_stream_for_missing_keys(missing_keys)
3114
 
        client = self.from_repository._client
3115
 
        medium = client._medium
3116
 
        if medium._is_remote_before((3, 0)):
3117
 
            return self._get_real_stream_for_missing_keys(missing_keys)
3118
 
        path = self.from_repository.controldir._path_for_remote_call(client)
3119
 
        args = (path, self.to_format.network_name())
3120
 
        search_bytes = b'\n'.join(
3121
 
            [b'%s\t%s' % (key[0].encode('utf-8'), key[1]) for key in missing_keys])
3122
 
        try:
3123
 
            response, handler = self.from_repository._call_with_body_bytes_expecting_body(
3124
 
                b'Repository.get_stream_for_missing_keys', args, search_bytes)
3125
 
        except (errors.UnknownSmartMethod, errors.UnknownFormatError):
3126
 
            return self._get_real_stream_for_missing_keys(missing_keys)
3127
 
        if response[0] != b'ok':
3128
 
            raise errors.UnexpectedSmartServerResponse(response)
3129
 
        byte_stream = handler.read_streamed_body()
3130
 
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3131
 
                                                               self._record_counter)
3132
 
        if src_format.network_name() != self.from_repository._format.network_name():
3133
 
            raise AssertionError(
3134
 
                "Mismatched RemoteRepository and stream src %r, %r" % (
3135
 
                    src_format.network_name(), repo._format.network_name()))
3136
 
        return stream
3137
 
 
3138
1924
    def _real_stream(self, repo, search):
3139
1925
        """Get a stream for search from repo.
3140
 
 
3141
 
        This never called RemoteStreamSource.get_stream, and is a helper
3142
 
        for RemoteStreamSource._get_stream to allow getting a stream
 
1926
        
 
1927
        This never called RemoteStreamSource.get_stream, and is a heler
 
1928
        for RemoteStreamSource._get_stream to allow getting a stream 
3143
1929
        reliably whether fallback back because of old servers or trying
3144
1930
        to stream from a non-RemoteRepository (which the stacked support
3145
1931
        code will do).
3170
1956
            return self._real_stream(repo, search)
3171
1957
        client = repo._client
3172
1958
        medium = client._medium
3173
 
        path = repo.controldir._path_for_remote_call(client)
 
1959
        path = repo.bzrdir._path_for_remote_call(client)
3174
1960
        search_bytes = repo._serialise_search_result(search)
3175
1961
        args = (path, self.to_format.network_name())
3176
1962
        candidate_verbs = [
3177
 
            (b'Repository.get_stream_1.19', (1, 19)),
3178
 
            (b'Repository.get_stream', (1, 13))]
3179
 
 
 
1963
            ('Repository.get_stream_1.19', (1, 19)),
 
1964
            ('Repository.get_stream', (1, 13))]
3180
1965
        found_verb = False
3181
1966
        for verb, version in candidate_verbs:
3182
1967
            if medium._is_remote_before(version):
3186
1971
                    verb, args, search_bytes)
3187
1972
            except errors.UnknownSmartMethod:
3188
1973
                medium._remember_remote_is_before(version)
3189
 
            except errors.UnknownErrorFromSmartServer as e:
3190
 
                if isinstance(search, vf_search.EverythingResult):
3191
 
                    error_verb = e.error_from_smart_server.error_verb
3192
 
                    if error_verb == b'BadSearch':
3193
 
                        # Pre-2.4 servers don't support this sort of search.
3194
 
                        # XXX: perhaps falling back to VFS on BadSearch is a
3195
 
                        # good idea in general?  It might provide a little bit
3196
 
                        # of protection against client-side bugs.
3197
 
                        medium._remember_remote_is_before((2, 4))
3198
 
                        break
3199
 
                raise
3200
1974
            else:
3201
1975
                response_tuple, response_handler = response
3202
1976
                found_verb = True
3203
1977
                break
3204
1978
        if not found_verb:
3205
1979
            return self._real_stream(repo, search)
3206
 
        if response_tuple[0] != b'ok':
 
1980
        if response_tuple[0] != 'ok':
3207
1981
            raise errors.UnexpectedSmartServerResponse(response_tuple)
3208
1982
        byte_stream = response_handler.read_streamed_body()
3209
1983
        src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3210
 
                                                               self._record_counter)
 
1984
            self._record_counter)
3211
1985
        if src_format.network_name() != repo._format.network_name():
3212
1986
            raise AssertionError(
3213
1987
                "Mismatched RemoteRepository and stream src %r, %r" % (
3214
 
                    src_format.network_name(), repo._format.network_name()))
 
1988
                src_format.network_name(), repo._format.network_name()))
3215
1989
        return stream
3216
1990
 
3217
1991
    def missing_parents_chain(self, search, sources):
3257
2031
    """
3258
2032
 
3259
2033
    def __init__(self, bzrdir, _client):
3260
 
        self.controldir = bzrdir
 
2034
        self.bzrdir = bzrdir
3261
2035
        self._client = _client
3262
2036
        self._need_find_modes = True
3263
2037
        LockableFiles.__init__(
3274
2048
 
3275
2049
    def __init__(self, network_name=None):
3276
2050
        super(RemoteBranchFormat, self).__init__()
3277
 
        self._matchingcontroldir = RemoteBzrDirFormat()
3278
 
        self._matchingcontroldir.set_branch_format(self)
 
2051
        self._matchingbzrdir = RemoteBzrDirFormat()
 
2052
        self._matchingbzrdir.set_branch_format(self)
3279
2053
        self._custom_format = None
3280
2054
        self._network_name = network_name
3281
2055
 
3282
2056
    def __eq__(self, other):
3283
 
        return (isinstance(other, RemoteBranchFormat)
3284
 
                and self.__dict__ == other.__dict__)
 
2057
        return (isinstance(other, RemoteBranchFormat) and
 
2058
            self.__dict__ == other.__dict__)
3285
2059
 
3286
2060
    def _ensure_real(self):
3287
2061
        if self._custom_format is None:
3288
 
            try:
3289
 
                self._custom_format = branch.network_format_registry.get(
3290
 
                    self._network_name)
3291
 
            except KeyError:
3292
 
                raise errors.UnknownFormatError(kind='branch',
3293
 
                                                format=self._network_name)
 
2062
            self._custom_format = branch.network_format_registry.get(
 
2063
                self._network_name)
3294
2064
 
3295
2065
    def get_format_description(self):
3296
2066
        self._ensure_real()
3299
2069
    def network_name(self):
3300
2070
        return self._network_name
3301
2071
 
3302
 
    def open(self, a_controldir, name=None, ignore_fallbacks=False):
3303
 
        return a_controldir.open_branch(name=name,
3304
 
                                        ignore_fallbacks=ignore_fallbacks)
 
2072
    def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
 
2073
        return a_bzrdir.open_branch(name=name, 
 
2074
            ignore_fallbacks=ignore_fallbacks)
3305
2075
 
3306
 
    def _vfs_initialize(self, a_controldir, name, append_revisions_only,
3307
 
                        repository=None):
 
2076
    def _vfs_initialize(self, a_bzrdir, name):
3308
2077
        # Initialisation when using a local bzrdir object, or a non-vfs init
3309
2078
        # method is not available on the server.
3310
2079
        # self._custom_format is always set - the start of initialize ensures
3311
2080
        # that.
3312
 
        if isinstance(a_controldir, RemoteBzrDir):
3313
 
            a_controldir._ensure_real()
3314
 
            result = self._custom_format.initialize(a_controldir._real_bzrdir,
3315
 
                                                    name=name, append_revisions_only=append_revisions_only,
3316
 
                                                    repository=repository)
 
2081
        if isinstance(a_bzrdir, RemoteBzrDir):
 
2082
            a_bzrdir._ensure_real()
 
2083
            result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
 
2084
                name)
3317
2085
        else:
3318
2086
            # We assume the bzrdir is parameterised; it may not be.
3319
 
            result = self._custom_format.initialize(a_controldir, name=name,
3320
 
                                                    append_revisions_only=append_revisions_only,
3321
 
                                                    repository=repository)
3322
 
        if (isinstance(a_controldir, RemoteBzrDir)
3323
 
                and not isinstance(result, RemoteBranch)):
3324
 
            result = RemoteBranch(a_controldir, a_controldir.find_repository(), result,
 
2087
            result = self._custom_format.initialize(a_bzrdir, name)
 
2088
        if (isinstance(a_bzrdir, RemoteBzrDir) and
 
2089
            not isinstance(result, RemoteBranch)):
 
2090
            result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3325
2091
                                  name=name)
3326
2092
        return result
3327
2093
 
3328
 
    def initialize(self, a_controldir, name=None, repository=None,
3329
 
                   append_revisions_only=None):
3330
 
        if name is None:
3331
 
            name = a_controldir._get_selected_branch()
 
2094
    def initialize(self, a_bzrdir, name=None):
3332
2095
        # 1) get the network name to use.
3333
2096
        if self._custom_format:
3334
2097
            network_name = self._custom_format.network_name()
3335
2098
        else:
3336
 
            # Select the current breezy default and ask for that.
3337
 
            reference_bzrdir_format = controldir.format_registry.get(
3338
 
                'default')()
 
2099
            # Select the current bzrlib default and ask for that.
 
2100
            reference_bzrdir_format = bzrdir.format_registry.get('default')()
3339
2101
            reference_format = reference_bzrdir_format.get_branch_format()
3340
2102
            self._custom_format = reference_format
3341
2103
            network_name = reference_format.network_name()
3342
2104
        # Being asked to create on a non RemoteBzrDir:
3343
 
        if not isinstance(a_controldir, RemoteBzrDir):
3344
 
            return self._vfs_initialize(a_controldir, name=name,
3345
 
                                        append_revisions_only=append_revisions_only,
3346
 
                                        repository=repository)
3347
 
        medium = a_controldir._client._medium
 
2105
        if not isinstance(a_bzrdir, RemoteBzrDir):
 
2106
            return self._vfs_initialize(a_bzrdir, name=name)
 
2107
        medium = a_bzrdir._client._medium
3348
2108
        if medium._is_remote_before((1, 13)):
3349
 
            return self._vfs_initialize(a_controldir, name=name,
3350
 
                                        append_revisions_only=append_revisions_only,
3351
 
                                        repository=repository)
 
2109
            return self._vfs_initialize(a_bzrdir, name=name)
3352
2110
        # Creating on a remote bzr dir.
3353
2111
        # 2) try direct creation via RPC
3354
 
        path = a_controldir._path_for_remote_call(a_controldir._client)
3355
 
        if name != "":
 
2112
        path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
 
2113
        if name is not None:
3356
2114
            # XXX JRV20100304: Support creating colocated branches
3357
2115
            raise errors.NoColocatedBranchSupport(self)
3358
 
        verb = b'BzrDir.create_branch'
 
2116
        verb = 'BzrDir.create_branch'
3359
2117
        try:
3360
 
            response = a_controldir._call(verb, path, network_name)
 
2118
            response = a_bzrdir._call(verb, path, network_name)
3361
2119
        except errors.UnknownSmartMethod:
3362
2120
            # Fallback - use vfs methods
3363
2121
            medium._remember_remote_is_before((1, 13))
3364
 
            return self._vfs_initialize(a_controldir, name=name,
3365
 
                                        append_revisions_only=append_revisions_only,
3366
 
                                        repository=repository)
3367
 
        if response[0] != b'ok':
 
2122
            return self._vfs_initialize(a_bzrdir, name=name)
 
2123
        if response[0] != 'ok':
3368
2124
            raise errors.UnexpectedSmartServerResponse(response)
3369
2125
        # Turn the response into a RemoteRepository object.
3370
2126
        format = RemoteBranchFormat(network_name=response[1])
3371
2127
        repo_format = response_tuple_to_repo_format(response[3:])
3372
 
        repo_path = response[2].decode('utf-8')
3373
 
        if repository is not None:
3374
 
            remote_repo_url = urlutils.join(a_controldir.user_url, repo_path)
3375
 
            url_diff = urlutils.relative_url(repository.user_url,
3376
 
                                             remote_repo_url)
3377
 
            if url_diff != '.':
3378
 
                raise AssertionError(
3379
 
                    'repository.user_url %r does not match URL from server '
3380
 
                    'response (%r + %r)'
3381
 
                    % (repository.user_url, a_controldir.user_url, repo_path))
3382
 
            remote_repo = repository
 
2128
        if response[2] == '':
 
2129
            repo_bzrdir = a_bzrdir
3383
2130
        else:
3384
 
            if repo_path == '':
3385
 
                repo_bzrdir = a_controldir
3386
 
            else:
3387
 
                repo_bzrdir = RemoteBzrDir(
3388
 
                    a_controldir.root_transport.clone(
3389
 
                        repo_path), a_controldir._format,
3390
 
                    a_controldir._client)
3391
 
            remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3392
 
        remote_branch = RemoteBranch(a_controldir, remote_repo,
3393
 
                                     format=format, setup_stacking=False, name=name)
3394
 
        if append_revisions_only:
3395
 
            remote_branch.set_append_revisions_only(append_revisions_only)
 
2131
            repo_bzrdir = RemoteBzrDir(
 
2132
                a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
 
2133
                a_bzrdir._client)
 
2134
        remote_repo = RemoteRepository(repo_bzrdir, repo_format)
 
2135
        remote_branch = RemoteBranch(a_bzrdir, remote_repo,
 
2136
            format=format, setup_stacking=False, name=name)
3396
2137
        # XXX: We know this is a new branch, so it must have revno 0, revid
3397
2138
        # NULL_REVISION. Creating the branch locked would make this be unable
3398
2139
        # to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3417
2158
        self._ensure_real()
3418
2159
        return self._custom_format.supports_set_append_revisions_only()
3419
2160
 
3420
 
    @property
3421
 
    def supports_reference_locations(self):
3422
 
        self._ensure_real()
3423
 
        return self._custom_format.supports_reference_locations
3424
 
 
3425
 
    def stores_revno(self):
3426
 
        return True
3427
 
 
3428
 
    def _use_default_local_heads_to_fetch(self):
3429
 
        # If the branch format is a metadir format *and* its heads_to_fetch
3430
 
        # implementation is not overridden vs the base class, we can use the
3431
 
        # base class logic rather than use the heads_to_fetch RPC.  This is
3432
 
        # usually cheaper in terms of net round trips, as the last-revision and
3433
 
        # tags info fetched is cached and would be fetched anyway.
3434
 
        self._ensure_real()
3435
 
        if isinstance(self._custom_format, bzrbranch.BranchFormatMetadir):
3436
 
            branch_class = self._custom_format._branch_class()
3437
 
            heads_to_fetch_impl = get_unbound_function(
3438
 
                branch_class.heads_to_fetch)
3439
 
            if heads_to_fetch_impl is get_unbound_function(branch.Branch.heads_to_fetch):
3440
 
                return True
3441
 
        return False
3442
 
 
3443
 
 
3444
 
class RemoteBranchStore(_mod_config.IniFileStore):
3445
 
    """Branch store which attempts to use HPSS calls to retrieve branch store.
3446
 
 
3447
 
    Note that this is specific to bzr-based formats.
3448
 
    """
3449
 
 
3450
 
    def __init__(self, branch):
3451
 
        super(RemoteBranchStore, self).__init__()
3452
 
        self.branch = branch
3453
 
        self.id = "branch"
3454
 
        self._real_store = None
3455
 
 
3456
 
    def external_url(self):
3457
 
        return urlutils.join(self.branch.user_url, 'branch.conf')
3458
 
 
3459
 
    def _load_content(self):
3460
 
        path = self.branch._remote_path()
3461
 
        try:
3462
 
            response, handler = self.branch._call_expecting_body(
3463
 
                b'Branch.get_config_file', path)
3464
 
        except errors.UnknownSmartMethod:
3465
 
            self._ensure_real()
3466
 
            return self._real_store._load_content()
3467
 
        if len(response) and response[0] != b'ok':
3468
 
            raise errors.UnexpectedSmartServerResponse(response)
3469
 
        return handler.read_body_bytes()
3470
 
 
3471
 
    def _save_content(self, content):
3472
 
        path = self.branch._remote_path()
3473
 
        try:
3474
 
            response, handler = self.branch._call_with_body_bytes_expecting_body(
3475
 
                b'Branch.put_config_file', (path,
3476
 
                                            self.branch._lock_token, self.branch._repo_lock_token),
3477
 
                content)
3478
 
        except errors.UnknownSmartMethod:
3479
 
            self._ensure_real()
3480
 
            return self._real_store._save_content(content)
3481
 
        handler.cancel_read_body()
3482
 
        if response != (b'ok', ):
3483
 
            raise errors.UnexpectedSmartServerResponse(response)
3484
 
 
3485
 
    def _ensure_real(self):
3486
 
        self.branch._ensure_real()
3487
 
        if self._real_store is None:
3488
 
            self._real_store = _mod_config.BranchStore(self.branch)
3489
 
 
3490
2161
 
3491
2162
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3492
2163
    """Branch stored on a server accessed by HPSS RPC.
3495
2166
    """
3496
2167
 
3497
2168
    def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3498
 
                 _client=None, format=None, setup_stacking=True, name=None,
3499
 
                 possible_transports=None):
 
2169
        _client=None, format=None, setup_stacking=True, name=None):
3500
2170
        """Create a RemoteBranch instance.
3501
2171
 
3502
2172
        :param real_branch: An optional local implementation of the branch
3513
2183
        # We intentionally don't call the parent class's __init__, because it
3514
2184
        # will try to assign to self.tags, which is a property in this subclass.
3515
2185
        # And the parent's __init__ doesn't do much anyway.
3516
 
        self.controldir = remote_bzrdir
3517
 
        self.name = name
 
2186
        self.bzrdir = remote_bzrdir
3518
2187
        if _client is not None:
3519
2188
            self._client = _client
3520
2189
        else:
3532
2201
            self._real_branch.repository = self.repository
3533
2202
        else:
3534
2203
            self._real_branch = None
3535
 
        # Fill out expected attributes of branch for breezy API users.
 
2204
        # Fill out expected attributes of branch for bzrlib API users.
3536
2205
        self._clear_cached_state()
3537
2206
        # TODO: deprecate self.base in favor of user_url
3538
 
        self.base = self.controldir.user_url
 
2207
        self.base = self.bzrdir.user_url
3539
2208
        self._name = name
3540
2209
        self._control_files = None
3541
2210
        self._lock_mode = None
3543
2212
        self._repo_lock_token = None
3544
2213
        self._lock_count = 0
3545
2214
        self._leave_lock = False
3546
 
        self.conf_store = None
3547
2215
        # Setup a format: note that we cannot call _ensure_real until all the
3548
2216
        # attributes above are set: This code cannot be moved higher up in this
3549
2217
        # function.
3569
2237
            hook(self)
3570
2238
        self._is_stacked = False
3571
2239
        if setup_stacking:
3572
 
            self._setup_stacking(possible_transports)
 
2240
            self._setup_stacking()
3573
2241
 
3574
 
    def _setup_stacking(self, possible_transports):
 
2242
    def _setup_stacking(self):
3575
2243
        # configure stacking into the remote repository, by reading it from
3576
2244
        # the vfs branch.
3577
2245
        try:
3578
2246
            fallback_url = self.get_stacked_on_url()
3579
 
        except (errors.NotStacked, branch.UnstackableBranchFormat,
3580
 
                errors.UnstackableRepositoryFormat) as e:
 
2247
        except (errors.NotStacked, errors.UnstackableBranchFormat,
 
2248
            errors.UnstackableRepositoryFormat), e:
3581
2249
            return
3582
2250
        self._is_stacked = True
3583
 
        if possible_transports is None:
3584
 
            possible_transports = []
3585
 
        else:
3586
 
            possible_transports = list(possible_transports)
3587
 
        possible_transports.append(self.controldir.root_transport)
3588
 
        self._activate_fallback_location(fallback_url,
3589
 
                                         possible_transports=possible_transports)
 
2251
        self._activate_fallback_location(fallback_url)
3590
2252
 
3591
2253
    def _get_config(self):
3592
2254
        return RemoteBranchConfig(self)
3593
2255
 
3594
 
    def _get_config_store(self):
3595
 
        if self.conf_store is None:
3596
 
            self.conf_store = RemoteBranchStore(self)
3597
 
        return self.conf_store
3598
 
 
3599
 
    def store_uncommitted(self, creator):
3600
 
        self._ensure_real()
3601
 
        return self._real_branch.store_uncommitted(creator)
3602
 
 
3603
 
    def get_unshelver(self, tree):
3604
 
        self._ensure_real()
3605
 
        return self._real_branch.get_unshelver(tree)
3606
 
 
3607
2256
    def _get_real_transport(self):
3608
2257
        # if we try vfs access, return the real branch's vfs transport
3609
2258
        self._ensure_real()
3624
2273
        if self._real_branch is None:
3625
2274
            if not vfs.vfs_enabled():
3626
2275
                raise AssertionError('smart server vfs must be enabled '
3627
 
                                     'to use vfs implementation')
3628
 
            self.controldir._ensure_real()
3629
 
            self._real_branch = self.controldir._real_bzrdir.open_branch(
 
2276
                    'to use vfs implementation')
 
2277
            self.bzrdir._ensure_real()
 
2278
            self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3630
2279
                ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3631
 
            # The remote branch and the real branch shares the same store. If
3632
 
            # we don't, there will always be cases where one of the stores
3633
 
            # doesn't see an update made on the other.
3634
 
            self._real_branch.conf_store = self.conf_store
3635
2280
            if self.repository._real_repository is None:
3636
2281
                # Give the remote repository the matching real repo.
3637
2282
                real_repo = self._real_branch.repository
3652
2297
 
3653
2298
    def _clear_cached_state(self):
3654
2299
        super(RemoteBranch, self)._clear_cached_state()
3655
 
        self._tags_bytes = None
3656
2300
        if self._real_branch is not None:
3657
2301
            self._real_branch._clear_cached_state()
3658
2302
 
3674
2318
        # because it triggers an _ensure_real that we otherwise might not need.
3675
2319
        if self._control_files is None:
3676
2320
            self._control_files = RemoteBranchLockableFiles(
3677
 
                self.controldir, self._client)
 
2321
                self.bzrdir, self._client)
3678
2322
        return self._control_files
3679
2323
 
 
2324
    def _get_checkout_format(self):
 
2325
        self._ensure_real()
 
2326
        return self._real_branch._get_checkout_format()
 
2327
 
3680
2328
    def get_physical_lock_status(self):
3681
2329
        """See Branch.get_physical_lock_status()."""
3682
 
        try:
3683
 
            response = self._client.call(b'Branch.get_physical_lock_status',
3684
 
                                         self._remote_path())
3685
 
        except errors.UnknownSmartMethod:
3686
 
            self._ensure_real()
3687
 
            return self._real_branch.get_physical_lock_status()
3688
 
        if response[0] not in (b'yes', b'no'):
3689
 
            raise errors.UnexpectedSmartServerResponse(response)
3690
 
        return (response[0] == b'yes')
 
2330
        # should be an API call to the server, as branches must be lockable.
 
2331
        self._ensure_real()
 
2332
        return self._real_branch.get_physical_lock_status()
3691
2333
 
3692
2334
    def get_stacked_on_url(self):
3693
2335
        """Get the URL this branch is stacked against.
3701
2343
        try:
3702
2344
            # there may not be a repository yet, so we can't use
3703
2345
            # self._translate_error, so we can't use self._call either.
3704
 
            response = self._client.call(b'Branch.get_stacked_on_url',
3705
 
                                         self._remote_path())
3706
 
        except errors.ErrorFromSmartServer as err:
 
2346
            response = self._client.call('Branch.get_stacked_on_url',
 
2347
                self._remote_path())
 
2348
        except errors.ErrorFromSmartServer, err:
3707
2349
            # there may not be a repository yet, so we can't call through
3708
2350
            # its _translate_error
3709
2351
            _translate_error(err, branch=self)
3710
 
        except errors.UnknownSmartMethod as err:
 
2352
        except errors.UnknownSmartMethod, err:
3711
2353
            self._ensure_real()
3712
2354
            return self._real_branch.get_stacked_on_url()
3713
 
        if response[0] != b'ok':
 
2355
        if response[0] != 'ok':
3714
2356
            raise errors.UnexpectedSmartServerResponse(response)
3715
 
        if sys.version_info[0] == 3:
3716
 
            return response[1].decode('utf-8')
3717
2357
        return response[1]
3718
2358
 
3719
2359
    def set_stacked_on_url(self, url):
3720
2360
        branch.Branch.set_stacked_on_url(self, url)
3721
 
        # We need the stacked_on_url to be visible both locally (to not query
3722
 
        # it repeatedly) and remotely (so smart verbs can get it server side)
3723
 
        # Without the following line,
3724
 
        # breezy.tests.per_branch.test_create_clone.TestCreateClone
3725
 
        # .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3726
 
        # fails for remote branches -- vila 2012-01-04
3727
 
        self.conf_store.save_changes()
3728
2361
        if not url:
3729
2362
            self._is_stacked = False
3730
2363
        else:
3731
2364
            self._is_stacked = True
3732
 
 
 
2365
        
3733
2366
    def _vfs_get_tags_bytes(self):
3734
2367
        self._ensure_real()
3735
2368
        return self._real_branch._get_tags_bytes()
3736
2369
 
3737
2370
    def _get_tags_bytes(self):
3738
 
        with self.lock_read():
3739
 
            if self._tags_bytes is None:
3740
 
                self._tags_bytes = self._get_tags_bytes_via_hpss()
3741
 
            return self._tags_bytes
3742
 
 
3743
 
    def _get_tags_bytes_via_hpss(self):
3744
2371
        medium = self._client._medium
3745
2372
        if medium._is_remote_before((1, 13)):
3746
2373
            return self._vfs_get_tags_bytes()
3747
2374
        try:
3748
 
            response = self._call(
3749
 
                b'Branch.get_tags_bytes', self._remote_path())
 
2375
            response = self._call('Branch.get_tags_bytes', self._remote_path())
3750
2376
        except errors.UnknownSmartMethod:
3751
2377
            medium._remember_remote_is_before((1, 13))
3752
2378
            return self._vfs_get_tags_bytes()
3757
2383
        return self._real_branch._set_tags_bytes(bytes)
3758
2384
 
3759
2385
    def _set_tags_bytes(self, bytes):
3760
 
        if self.is_locked():
3761
 
            self._tags_bytes = bytes
3762
2386
        medium = self._client._medium
3763
2387
        if medium._is_remote_before((1, 18)):
3764
2388
            self._vfs_set_tags_bytes(bytes)
3767
2391
            args = (
3768
2392
                self._remote_path(), self._lock_token, self._repo_lock_token)
3769
2393
            response = self._call_with_body_bytes(
3770
 
                b'Branch.set_tags_bytes', args, bytes)
 
2394
                'Branch.set_tags_bytes', args, bytes)
3771
2395
        except errors.UnknownSmartMethod:
3772
2396
            medium._remember_remote_is_before((1, 18))
3773
2397
            self._vfs_set_tags_bytes(bytes)
3775
2399
    def lock_read(self):
3776
2400
        """Lock the branch for read operations.
3777
2401
 
3778
 
        :return: A breezy.lock.LogicalLockResult.
 
2402
        :return: A bzrlib.lock.LogicalLockResult.
3779
2403
        """
3780
2404
        self.repository.lock_read()
3781
2405
        if not self._lock_mode:
3790
2414
 
3791
2415
    def _remote_lock_write(self, token):
3792
2416
        if token is None:
3793
 
            branch_token = repo_token = b''
 
2417
            branch_token = repo_token = ''
3794
2418
        else:
3795
2419
            branch_token = token
3796
2420
            repo_token = self.repository.lock_write().repository_token
3798
2422
        err_context = {'token': token}
3799
2423
        try:
3800
2424
            response = self._call(
3801
 
                b'Branch.lock_write', self._remote_path(), branch_token,
3802
 
                repo_token or b'', **err_context)
3803
 
        except errors.LockContention as e:
 
2425
                'Branch.lock_write', self._remote_path(), branch_token,
 
2426
                repo_token or '', **err_context)
 
2427
        except errors.LockContention, e:
3804
2428
            # The LockContention from the server doesn't have any
3805
2429
            # information about the lock_url. We re-raise LockContention
3806
2430
            # with valid lock_url.
3807
2431
            raise errors.LockContention('(remote lock)',
3808
 
                                        self.repository.base.split('.bzr/')[0])
3809
 
        if response[0] != b'ok':
 
2432
                self.repository.base.split('.bzr/')[0])
 
2433
        if response[0] != 'ok':
3810
2434
            raise errors.UnexpectedSmartServerResponse(response)
3811
2435
        ok, branch_token, repo_token = response
3812
2436
        return branch_token, repo_token
3818
2442
            remote_tokens = self._remote_lock_write(token)
3819
2443
            self._lock_token, self._repo_lock_token = remote_tokens
3820
2444
            if not self._lock_token:
3821
 
                raise SmartProtocolError(
3822
 
                    'Remote server did not return a token!')
 
2445
                raise SmartProtocolError('Remote server did not return a token!')
3823
2446
            # Tell the self.repository object that it is locked.
3824
2447
            self.repository.lock_write(
3825
2448
                self._repo_lock_token, _skip_rpc=True)
3849
2472
    def _unlock(self, branch_token, repo_token):
3850
2473
        err_context = {'token': str((branch_token, repo_token))}
3851
2474
        response = self._call(
3852
 
            b'Branch.unlock', self._remote_path(), branch_token,
3853
 
            repo_token or b'', **err_context)
3854
 
        if response == (b'ok',):
 
2475
            'Branch.unlock', self._remote_path(), branch_token,
 
2476
            repo_token or '', **err_context)
 
2477
        if response == ('ok',):
3855
2478
            return
3856
2479
        raise errors.UnexpectedSmartServerResponse(response)
3857
2480
 
3860
2483
        try:
3861
2484
            self._lock_count -= 1
3862
2485
            if not self._lock_count:
3863
 
                if self.conf_store is not None:
3864
 
                    self.conf_store.save_changes()
3865
2486
                self._clear_cached_state()
3866
2487
                mode = self._lock_mode
3867
2488
                self._lock_mode = None
3868
2489
                if self._real_branch is not None:
3869
 
                    if (not self._leave_lock and mode == 'w'
3870
 
                            and self._repo_lock_token):
 
2490
                    if (not self._leave_lock and mode == 'w' and
 
2491
                        self._repo_lock_token):
3871
2492
                        # If this RemoteBranch will remove the physical lock
3872
2493
                        # for the repository, make sure the _real_branch
3873
2494
                        # doesn't do it first.  (Because the _real_branch's
3890
2511
            self.repository.unlock()
3891
2512
 
3892
2513
    def break_lock(self):
3893
 
        try:
3894
 
            response = self._call(
3895
 
                b'Branch.break_lock', self._remote_path())
3896
 
        except errors.UnknownSmartMethod:
3897
 
            self._ensure_real()
3898
 
            return self._real_branch.break_lock()
3899
 
        if response != (b'ok',):
3900
 
            raise errors.UnexpectedSmartServerResponse(response)
 
2514
        self._ensure_real()
 
2515
        return self._real_branch.break_lock()
3901
2516
 
3902
2517
    def leave_lock_in_place(self):
3903
2518
        if not self._lock_token:
3909
2524
            raise NotImplementedError(self.dont_leave_lock_in_place)
3910
2525
        self._leave_lock = False
3911
2526
 
 
2527
    @needs_read_lock
3912
2528
    def get_rev_id(self, revno, history=None):
3913
2529
        if revno == 0:
3914
2530
            return _mod_revision.NULL_REVISION
3915
 
        with self.lock_read():
3916
 
            last_revision_info = self.last_revision_info()
3917
 
            if revno < 0:
3918
 
                raise errors.RevnoOutOfBounds(
3919
 
                    revno, (0, last_revision_info[0]))
3920
 
            ok, result = self.repository.get_rev_id_for_revno(
3921
 
                revno, last_revision_info)
3922
 
            if ok:
3923
 
                return result
3924
 
            missing_parent = result[1]
3925
 
            # Either the revision named by the server is missing, or its parent
3926
 
            # is.  Call get_parent_map to determine which, so that we report a
3927
 
            # useful error.
3928
 
            parent_map = self.repository.get_parent_map([missing_parent])
3929
 
            if missing_parent in parent_map:
3930
 
                missing_parent = parent_map[missing_parent]
3931
 
            raise errors.NoSuchRevision(self, missing_parent)
 
2531
        last_revision_info = self.last_revision_info()
 
2532
        ok, result = self.repository.get_rev_id_for_revno(
 
2533
            revno, last_revision_info)
 
2534
        if ok:
 
2535
            return result
 
2536
        missing_parent = result[1]
 
2537
        # Either the revision named by the server is missing, or its parent
 
2538
        # is.  Call get_parent_map to determine which, so that we report a
 
2539
        # useful error.
 
2540
        parent_map = self.repository.get_parent_map([missing_parent])
 
2541
        if missing_parent in parent_map:
 
2542
            missing_parent = parent_map[missing_parent]
 
2543
        raise errors.RevisionNotPresent(missing_parent, self.repository)
3932
2544
 
3933
 
    def _read_last_revision_info(self):
3934
 
        response = self._call(
3935
 
            b'Branch.last_revision_info', self._remote_path())
3936
 
        if response[0] != b'ok':
3937
 
            raise SmartProtocolError(
3938
 
                'unexpected response code %s' % (response,))
 
2545
    def _last_revision_info(self):
 
2546
        response = self._call('Branch.last_revision_info', self._remote_path())
 
2547
        if response[0] != 'ok':
 
2548
            raise SmartProtocolError('unexpected response code %s' % (response,))
3939
2549
        revno = int(response[1])
3940
2550
        last_revision = response[2]
3941
2551
        return (revno, last_revision)
3946
2556
            self._ensure_real()
3947
2557
            return self._real_branch._gen_revision_history()
3948
2558
        response_tuple, response_handler = self._call_expecting_body(
3949
 
            b'Branch.revision_history', self._remote_path())
3950
 
        if response_tuple[0] != b'ok':
 
2559
            'Branch.revision_history', self._remote_path())
 
2560
        if response_tuple[0] != 'ok':
3951
2561
            raise errors.UnexpectedSmartServerResponse(response_tuple)
3952
 
        result = response_handler.read_body_bytes().split(b'\x00')
 
2562
        result = response_handler.read_body_bytes().split('\x00')
3953
2563
        if result == ['']:
3954
2564
            return []
3955
2565
        return result
3956
2566
 
3957
2567
    def _remote_path(self):
3958
 
        return self.controldir._path_for_remote_call(self._client)
 
2568
        return self.bzrdir._path_for_remote_call(self._client)
3959
2569
 
3960
2570
    def _set_last_revision_descendant(self, revision_id, other_branch,
3961
 
                                      allow_diverged=False, allow_overwrite_descendant=False):
 
2571
            allow_diverged=False, allow_overwrite_descendant=False):
3962
2572
        # This performs additional work to meet the hook contract; while its
3963
2573
        # undesirable, we have to synthesise the revno to call the hook, and
3964
2574
        # not calling the hook is worse as it means changes can't be prevented.
3969
2579
        history = self._lefthand_history(revision_id)
3970
2580
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3971
2581
        err_context = {'other_branch': other_branch}
3972
 
        response = self._call(b'Branch.set_last_revision_ex',
3973
 
                              self._remote_path(), self._lock_token, self._repo_lock_token,
3974
 
                              revision_id, int(allow_diverged), int(
3975
 
                                  allow_overwrite_descendant),
3976
 
                              **err_context)
 
2582
        response = self._call('Branch.set_last_revision_ex',
 
2583
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2584
            revision_id, int(allow_diverged), int(allow_overwrite_descendant),
 
2585
            **err_context)
3977
2586
        self._clear_cached_state()
3978
 
        if len(response) != 3 and response[0] != b'ok':
 
2587
        if len(response) != 3 and response[0] != 'ok':
3979
2588
            raise errors.UnexpectedSmartServerResponse(response)
3980
2589
        new_revno, new_revision_id = response[1:]
3981
2590
        self._last_revision_info_cache = new_revno, new_revision_id
3995
2604
        history = self._lefthand_history(revision_id)
3996
2605
        self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3997
2606
        self._clear_cached_state()
3998
 
        response = self._call(b'Branch.set_last_revision',
3999
 
                              self._remote_path(), self._lock_token, self._repo_lock_token,
4000
 
                              revision_id)
4001
 
        if response != (b'ok',):
 
2607
        response = self._call('Branch.set_last_revision',
 
2608
            self._remote_path(), self._lock_token, self._repo_lock_token,
 
2609
            revision_id)
 
2610
        if response != ('ok',):
4002
2611
            raise errors.UnexpectedSmartServerResponse(response)
4003
2612
        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
4004
2613
 
 
2614
    @needs_write_lock
 
2615
    def set_revision_history(self, rev_history):
 
2616
        # Send just the tip revision of the history; the server will generate
 
2617
        # the full history from that.  If the revision doesn't exist in this
 
2618
        # branch, NoSuchRevision will be raised.
 
2619
        if rev_history == []:
 
2620
            rev_id = 'null:'
 
2621
        else:
 
2622
            rev_id = rev_history[-1]
 
2623
        self._set_last_revision(rev_id)
 
2624
        for hook in branch.Branch.hooks['set_rh']:
 
2625
            hook(self, rev_history)
 
2626
        self._cache_revision_history(rev_history)
 
2627
 
4005
2628
    def _get_parent_location(self):
4006
2629
        medium = self._client._medium
4007
2630
        if medium._is_remote_before((1, 13)):
4008
2631
            return self._vfs_get_parent_location()
4009
2632
        try:
4010
 
            response = self._call(b'Branch.get_parent', self._remote_path())
 
2633
            response = self._call('Branch.get_parent', self._remote_path())
4011
2634
        except errors.UnknownSmartMethod:
4012
2635
            medium._remember_remote_is_before((1, 13))
4013
2636
            return self._vfs_get_parent_location()
4014
2637
        if len(response) != 1:
4015
2638
            raise errors.UnexpectedSmartServerResponse(response)
4016
2639
        parent_location = response[0]
4017
 
        if parent_location == b'':
 
2640
        if parent_location == '':
4018
2641
            return None
4019
 
        return parent_location.decode('utf-8')
 
2642
        return parent_location
4020
2643
 
4021
2644
    def _vfs_get_parent_location(self):
4022
2645
        self._ensure_real()
4027
2650
        if medium._is_remote_before((1, 15)):
4028
2651
            return self._vfs_set_parent_location(url)
4029
2652
        try:
4030
 
            call_url = url or u''
4031
 
            if isinstance(call_url, text_type):
4032
 
                call_url = call_url.encode('utf-8')
4033
 
            response = self._call(b'Branch.set_parent_location',
4034
 
                                  self._remote_path(), self._lock_token, self._repo_lock_token,
4035
 
                                  call_url)
 
2653
            call_url = url or ''
 
2654
            if type(call_url) is not str:
 
2655
                raise AssertionError('url must be a str or None (%s)' % url)
 
2656
            response = self._call('Branch.set_parent_location',
 
2657
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2658
                call_url)
4036
2659
        except errors.UnknownSmartMethod:
4037
2660
            medium._remember_remote_is_before((1, 15))
4038
2661
            return self._vfs_set_parent_location(url)
4043
2666
        self._ensure_real()
4044
2667
        return self._real_branch._set_parent_location(url)
4045
2668
 
 
2669
    @needs_write_lock
4046
2670
    def pull(self, source, overwrite=False, stop_revision=None,
4047
2671
             **kwargs):
4048
 
        with self.lock_write():
4049
 
            self._clear_cached_state_of_remote_branch_only()
4050
 
            self._ensure_real()
4051
 
            return self._real_branch.pull(
4052
 
                source, overwrite=overwrite, stop_revision=stop_revision,
4053
 
                _override_hook_target=self, **kwargs)
4054
 
 
4055
 
    def push(self, target, overwrite=False, stop_revision=None, lossy=False, tag_selector=None):
4056
 
        with self.lock_read():
4057
 
            self._ensure_real()
4058
 
            return self._real_branch.push(
4059
 
                target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
4060
 
                _override_hook_source_branch=self, tag_selector=tag_selector)
4061
 
 
4062
 
    def peek_lock_mode(self):
4063
 
        return self._lock_mode
 
2672
        self._clear_cached_state_of_remote_branch_only()
 
2673
        self._ensure_real()
 
2674
        return self._real_branch.pull(
 
2675
            source, overwrite=overwrite, stop_revision=stop_revision,
 
2676
            _override_hook_target=self, **kwargs)
 
2677
 
 
2678
    @needs_read_lock
 
2679
    def push(self, target, overwrite=False, stop_revision=None):
 
2680
        self._ensure_real()
 
2681
        return self._real_branch.push(
 
2682
            target, overwrite=overwrite, stop_revision=stop_revision,
 
2683
            _override_hook_source_branch=self)
4064
2684
 
4065
2685
    def is_locked(self):
4066
2686
        return self._lock_count >= 1
4067
2687
 
4068
 
    def revision_id_to_dotted_revno(self, revision_id):
4069
 
        """Given a revision id, return its dotted revno.
4070
 
 
4071
 
        :return: a tuple like (1,) or (400,1,3).
4072
 
        """
4073
 
        with self.lock_read():
4074
 
            try:
4075
 
                response = self._call(b'Branch.revision_id_to_revno',
4076
 
                                      self._remote_path(), revision_id)
4077
 
            except errors.UnknownSmartMethod:
4078
 
                self._ensure_real()
4079
 
                return self._real_branch.revision_id_to_dotted_revno(revision_id)
4080
 
            except errors.UnknownErrorFromSmartServer as e:
4081
 
                # Deal with older versions of bzr/brz that didn't explicitly
4082
 
                # wrap GhostRevisionsHaveNoRevno.
4083
 
                if e.error_tuple[1] == b'GhostRevisionsHaveNoRevno':
4084
 
                    (revid, ghost_revid) = re.findall(b"{([^}]+)}", e.error_tuple[2])
4085
 
                    raise errors.GhostRevisionsHaveNoRevno(
4086
 
                        revid, ghost_revid)
4087
 
                raise
4088
 
            if response[0] == b'ok':
4089
 
                return tuple([int(x) for x in response[1:]])
4090
 
            else:
4091
 
                raise errors.UnexpectedSmartServerResponse(response)
4092
 
 
 
2688
    @needs_read_lock
4093
2689
    def revision_id_to_revno(self, revision_id):
4094
 
        """Given a revision id on the branch mainline, return its revno.
4095
 
 
4096
 
        :return: an integer
4097
 
        """
4098
 
        with self.lock_read():
4099
 
            try:
4100
 
                response = self._call(b'Branch.revision_id_to_revno',
4101
 
                                      self._remote_path(), revision_id)
4102
 
            except errors.UnknownSmartMethod:
4103
 
                self._ensure_real()
4104
 
                return self._real_branch.revision_id_to_revno(revision_id)
4105
 
            if response[0] == b'ok':
4106
 
                if len(response) == 2:
4107
 
                    return int(response[1])
4108
 
                raise NoSuchRevision(self, revision_id)
4109
 
            else:
4110
 
                raise errors.UnexpectedSmartServerResponse(response)
4111
 
 
 
2690
        self._ensure_real()
 
2691
        return self._real_branch.revision_id_to_revno(revision_id)
 
2692
 
 
2693
    @needs_write_lock
4112
2694
    def set_last_revision_info(self, revno, revision_id):
4113
 
        with self.lock_write():
4114
 
            # XXX: These should be returned by the set_last_revision_info verb
4115
 
            old_revno, old_revid = self.last_revision_info()
4116
 
            self._run_pre_change_branch_tip_hooks(revno, revision_id)
4117
 
            if not revision_id or not isinstance(revision_id, bytes):
4118
 
                raise errors.InvalidRevisionId(
4119
 
                    revision_id=revision_id, branch=self)
4120
 
            try:
4121
 
                response = self._call(b'Branch.set_last_revision_info',
4122
 
                                      self._remote_path(), self._lock_token, self._repo_lock_token,
4123
 
                                      str(revno).encode('ascii'), revision_id)
4124
 
            except errors.UnknownSmartMethod:
4125
 
                self._ensure_real()
4126
 
                self._clear_cached_state_of_remote_branch_only()
4127
 
                self._real_branch.set_last_revision_info(revno, revision_id)
4128
 
                self._last_revision_info_cache = revno, revision_id
4129
 
                return
4130
 
            if response == (b'ok',):
4131
 
                self._clear_cached_state()
4132
 
                self._last_revision_info_cache = revno, revision_id
4133
 
                self._run_post_change_branch_tip_hooks(old_revno, old_revid)
4134
 
                # Update the _real_branch's cache too.
4135
 
                if self._real_branch is not None:
4136
 
                    cache = self._last_revision_info_cache
4137
 
                    self._real_branch._last_revision_info_cache = cache
4138
 
            else:
4139
 
                raise errors.UnexpectedSmartServerResponse(response)
 
2695
        # XXX: These should be returned by the set_last_revision_info verb
 
2696
        old_revno, old_revid = self.last_revision_info()
 
2697
        self._run_pre_change_branch_tip_hooks(revno, revision_id)
 
2698
        revision_id = ensure_null(revision_id)
 
2699
        try:
 
2700
            response = self._call('Branch.set_last_revision_info',
 
2701
                self._remote_path(), self._lock_token, self._repo_lock_token,
 
2702
                str(revno), revision_id)
 
2703
        except errors.UnknownSmartMethod:
 
2704
            self._ensure_real()
 
2705
            self._clear_cached_state_of_remote_branch_only()
 
2706
            self._real_branch.set_last_revision_info(revno, revision_id)
 
2707
            self._last_revision_info_cache = revno, revision_id
 
2708
            return
 
2709
        if response == ('ok',):
 
2710
            self._clear_cached_state()
 
2711
            self._last_revision_info_cache = revno, revision_id
 
2712
            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
2713
            # Update the _real_branch's cache too.
 
2714
            if self._real_branch is not None:
 
2715
                cache = self._last_revision_info_cache
 
2716
                self._real_branch._last_revision_info_cache = cache
 
2717
        else:
 
2718
            raise errors.UnexpectedSmartServerResponse(response)
4140
2719
 
 
2720
    @needs_write_lock
4141
2721
    def generate_revision_history(self, revision_id, last_rev=None,
4142
2722
                                  other_branch=None):
4143
 
        with self.lock_write():
4144
 
            medium = self._client._medium
4145
 
            if not medium._is_remote_before((1, 6)):
4146
 
                # Use a smart method for 1.6 and above servers
4147
 
                try:
4148
 
                    self._set_last_revision_descendant(revision_id, other_branch,
4149
 
                                                       allow_diverged=True, allow_overwrite_descendant=True)
4150
 
                    return
4151
 
                except errors.UnknownSmartMethod:
4152
 
                    medium._remember_remote_is_before((1, 6))
4153
 
            self._clear_cached_state_of_remote_branch_only()
4154
 
            graph = self.repository.get_graph()
4155
 
            (last_revno, last_revid) = self.last_revision_info()
4156
 
            known_revision_ids = [
4157
 
                (last_revid, last_revno),
4158
 
                (_mod_revision.NULL_REVISION, 0),
4159
 
                ]
4160
 
            if last_rev is not None:
4161
 
                if not graph.is_ancestor(last_rev, revision_id):
4162
 
                    # our previous tip is not merged into stop_revision
4163
 
                    raise errors.DivergedBranches(self, other_branch)
4164
 
            revno = graph.find_distance_to_null(
4165
 
                revision_id, known_revision_ids)
4166
 
            self.set_last_revision_info(revno, revision_id)
 
2723
        medium = self._client._medium
 
2724
        if not medium._is_remote_before((1, 6)):
 
2725
            # Use a smart method for 1.6 and above servers
 
2726
            try:
 
2727
                self._set_last_revision_descendant(revision_id, other_branch,
 
2728
                    allow_diverged=True, allow_overwrite_descendant=True)
 
2729
                return
 
2730
            except errors.UnknownSmartMethod:
 
2731
                medium._remember_remote_is_before((1, 6))
 
2732
        self._clear_cached_state_of_remote_branch_only()
 
2733
        self.set_revision_history(self._lefthand_history(revision_id,
 
2734
            last_rev=last_rev,other_branch=other_branch))
4167
2735
 
4168
2736
    def set_push_location(self, location):
4169
 
        self._set_config_location('push_location', location)
4170
 
 
4171
 
    def heads_to_fetch(self):
4172
 
        if self._format._use_default_local_heads_to_fetch():
4173
 
            # We recognise this format, and its heads-to-fetch implementation
4174
 
            # is the default one (tip + tags).  In this case it's cheaper to
4175
 
            # just use the default implementation rather than a special RPC as
4176
 
            # the tip and tags data is cached.
4177
 
            return branch.Branch.heads_to_fetch(self)
4178
 
        medium = self._client._medium
4179
 
        if medium._is_remote_before((2, 4)):
4180
 
            return self._vfs_heads_to_fetch()
4181
 
        try:
4182
 
            return self._rpc_heads_to_fetch()
4183
 
        except errors.UnknownSmartMethod:
4184
 
            medium._remember_remote_is_before((2, 4))
4185
 
            return self._vfs_heads_to_fetch()
4186
 
 
4187
 
    def _rpc_heads_to_fetch(self):
4188
 
        response = self._call(b'Branch.heads_to_fetch', self._remote_path())
4189
 
        if len(response) != 2:
4190
 
            raise errors.UnexpectedSmartServerResponse(response)
4191
 
        must_fetch, if_present_fetch = response
4192
 
        return set(must_fetch), set(if_present_fetch)
4193
 
 
4194
 
    def _vfs_heads_to_fetch(self):
4195
 
        self._ensure_real()
4196
 
        return self._real_branch.heads_to_fetch()
4197
 
 
4198
 
    def reconcile(self, thorough=True):
4199
 
        """Make sure the data stored in this branch is consistent."""
4200
 
        from .reconcile import BranchReconciler
4201
 
        with self.lock_write():
4202
 
            reconciler = BranchReconciler(self, thorough=thorough)
4203
 
            return reconciler.reconcile()
4204
 
 
4205
 
    def get_reference_info(self, file_id):
4206
 
        """Get the tree_path and branch_location for a tree reference."""
4207
 
        if not self._format.supports_reference_locations:
4208
 
            raise errors.UnsupportedOperation(self.get_reference_info, self)
4209
 
        return self._get_all_reference_info().get(file_id, (None, None))
4210
 
 
4211
 
    def set_reference_info(self, file_id, branch_location, tree_path=None):
4212
 
        """Set the branch location to use for a tree reference."""
4213
 
        if not self._format.supports_reference_locations:
4214
 
            raise errors.UnsupportedOperation(self.set_reference_info, self)
4215
 
        self._ensure_real()
4216
 
        self._real_branch.set_reference_info(
4217
 
            file_id, branch_location, tree_path)
4218
 
 
4219
 
    def _set_all_reference_info(self, reference_info):
4220
 
        if not self._format.supports_reference_locations:
4221
 
            raise errors.UnsupportedOperation(self.set_reference_info, self)
4222
 
        self._ensure_real()
4223
 
        self._real_branch._set_all_reference_info(reference_info)
4224
 
 
4225
 
    def _get_all_reference_info(self):
4226
 
        if not self._format.supports_reference_locations:
4227
 
            return {}
4228
 
        try:
4229
 
            response, handler = self._call_expecting_body(
4230
 
                b'Branch.get_all_reference_info', self._remote_path())
4231
 
        except errors.UnknownSmartMethod:
4232
 
            self._ensure_real()
4233
 
            return self._real_branch._get_all_reference_info()
4234
 
        if len(response) and response[0] != b'ok':
4235
 
            raise errors.UnexpectedSmartServerResponse(response)
4236
 
        ret = {}
4237
 
        for (f, u, p) in bencode.bdecode(handler.read_body_bytes()):
4238
 
            ret[f] = (u.decode('utf-8'), p.decode('utf-8') if p else None)
4239
 
        return ret
4240
 
 
4241
 
    def reference_parent(self, file_id, path, possible_transports=None):
4242
 
        """Return the parent branch for a tree-reference.
4243
 
 
4244
 
        :param path: The path of the nested tree in the tree
4245
 
        :return: A branch associated with the nested tree
4246
 
        """
4247
 
        branch_location = self.get_reference_info(file_id)[0]
4248
 
        if branch_location is None:
4249
 
            try:
4250
 
                return branch.Branch.open_from_transport(
4251
 
                    self.controldir.root_transport.clone(path),
4252
 
                    possible_transports=possible_transports)
4253
 
            except errors.NotBranchError:
4254
 
                return None
4255
 
        return branch.Branch.open(
4256
 
            urlutils.join(
4257
 
                urlutils.strip_segment_parameters(self.user_url), branch_location),
4258
 
            possible_transports=possible_transports)
 
2737
        self._ensure_real()
 
2738
        return self._real_branch.set_push_location(location)
4259
2739
 
4260
2740
 
4261
2741
class RemoteConfig(object):
4263
2743
 
4264
2744
    It is a low-level object that considers config data to be name/value pairs
4265
2745
    that may be associated with a section. Assigning meaning to the these
4266
 
    values is done at higher levels like breezy.config.TreeConfig.
 
2746
    values is done at higher levels like bzrlib.config.TreeConfig.
4267
2747
    """
4268
2748
 
4269
2749
    def get_option(self, name, section=None, default=None):
4276
2756
        """
4277
2757
        try:
4278
2758
            configobj = self._get_configobj()
4279
 
            section_obj = None
4280
2759
            if section is None:
4281
2760
                section_obj = configobj
4282
2761
            else:
4283
2762
                try:
4284
2763
                    section_obj = configobj[section]
4285
2764
                except KeyError:
4286
 
                    pass
4287
 
            if section_obj is None:
4288
 
                value = default
4289
 
            else:
4290
 
                value = section_obj.get(name, default)
 
2765
                    return default
 
2766
            return section_obj.get(name, default)
4291
2767
        except errors.UnknownSmartMethod:
4292
 
            value = self._vfs_get_option(name, section, default)
4293
 
        for hook in _mod_config.OldConfigHooks['get']:
4294
 
            hook(self, name, value)
4295
 
        return value
 
2768
            return self._vfs_get_option(name, section, default)
4296
2769
 
4297
2770
    def _response_to_configobj(self, response):
4298
 
        if len(response[0]) and response[0][0] != b'ok':
 
2771
        if len(response[0]) and response[0][0] != 'ok':
4299
2772
            raise errors.UnexpectedSmartServerResponse(response)
4300
2773
        lines = response[1].read_body_bytes().splitlines()
4301
 
        conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4302
 
        for hook in _mod_config.OldConfigHooks['load']:
4303
 
            hook(self)
4304
 
        return conf
 
2774
        return config.ConfigObj(lines, encoding='utf-8')
4305
2775
 
4306
2776
 
4307
2777
class RemoteBranchConfig(RemoteConfig):
4313
2783
    def _get_configobj(self):
4314
2784
        path = self._branch._remote_path()
4315
2785
        response = self._branch._client.call_expecting_body(
4316
 
            b'Branch.get_config_file', path)
 
2786
            'Branch.get_config_file', path)
4317
2787
        return self._response_to_configobj(response)
4318
2788
 
4319
2789
    def set_option(self, value, name, section=None):
4334
2804
            return self._set_config_option(value, name, section)
4335
2805
 
4336
2806
    def _set_config_option(self, value, name, section):
4337
 
        if isinstance(value, (bool, int)):
4338
 
            value = str(value)
4339
 
        elif isinstance(value, (text_type, str)):
4340
 
            pass
4341
 
        else:
4342
 
            raise TypeError(value)
4343
2807
        try:
4344
2808
            path = self._branch._remote_path()
4345
 
            response = self._branch._client.call(b'Branch.set_config_option',
4346
 
                                                 path, self._branch._lock_token, self._branch._repo_lock_token,
4347
 
                                                 value.encode('utf-8'), name.encode('utf-8'),
4348
 
                                                 (section or '').encode('utf-8'))
 
2809
            response = self._branch._client.call('Branch.set_config_option',
 
2810
                path, self._branch._lock_token, self._branch._repo_lock_token,
 
2811
                value.encode('utf8'), name, section or '')
4349
2812
        except errors.UnknownSmartMethod:
4350
2813
            medium = self._branch._client._medium
4351
2814
            medium._remember_remote_is_before((1, 14))
4356
2819
    def _serialize_option_dict(self, option_dict):
4357
2820
        utf8_dict = {}
4358
2821
        for key, value in option_dict.items():
4359
 
            if isinstance(key, text_type):
 
2822
            if isinstance(key, unicode):
4360
2823
                key = key.encode('utf8')
4361
 
            if isinstance(value, text_type):
 
2824
            if isinstance(value, unicode):
4362
2825
                value = value.encode('utf8')
4363
2826
            utf8_dict[key] = value
4364
2827
        return bencode.bencode(utf8_dict)
4368
2831
            path = self._branch._remote_path()
4369
2832
            serialised_dict = self._serialize_option_dict(value)
4370
2833
            response = self._branch._client.call(
4371
 
                b'Branch.set_config_option_dict',
 
2834
                'Branch.set_config_option_dict',
4372
2835
                path, self._branch._lock_token, self._branch._repo_lock_token,
4373
 
                serialised_dict, name.encode('utf-8'), (section or '').encode('utf-8'))
 
2836
                serialised_dict, name, section or '')
4374
2837
        except errors.UnknownSmartMethod:
4375
2838
            medium = self._branch._client._medium
4376
2839
            medium._remember_remote_is_before((2, 2))
4395
2858
 
4396
2859
    def _get_configobj(self):
4397
2860
        medium = self._bzrdir._client._medium
4398
 
        verb = b'BzrDir.get_config_file'
 
2861
        verb = 'BzrDir.get_config_file'
4399
2862
        if medium._is_remote_before((1, 15)):
4400
2863
            raise errors.UnknownSmartMethod(verb)
4401
2864
        path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4422
2885
        return self._bzrdir._real_bzrdir
4423
2886
 
4424
2887
 
4425
 
error_translators = registry.Registry()
4426
 
no_context_error_translators = registry.Registry()
 
2888
 
 
2889
def _extract_tar(tar, to_dir):
 
2890
    """Extract all the contents of a tarfile object.
 
2891
 
 
2892
    A replacement for extractall, which is not present in python2.4
 
2893
    """
 
2894
    for tarinfo in tar:
 
2895
        tar.extract(tarinfo, to_dir)
4427
2896
 
4428
2897
 
4429
2898
def _translate_error(err, **context):
4443
2912
    def find(name):
4444
2913
        try:
4445
2914
            return context[name]
4446
 
        except KeyError:
4447
 
            mutter('Missing key \'%s\' in context %r', name, context)
 
2915
        except KeyError, key_err:
 
2916
            mutter('Missing key %r in context %r', key_err.args[0], context)
4448
2917
            raise err
4449
 
 
4450
2918
    def get_path():
4451
2919
        """Get the path from the context if present, otherwise use first error
4452
2920
        arg.
4453
2921
        """
4454
2922
        try:
4455
2923
            return context['path']
4456
 
        except KeyError:
 
2924
        except KeyError, key_err:
4457
2925
            try:
4458
 
                return err.error_args[0].decode('utf-8')
4459
 
            except IndexError:
4460
 
                mutter('Missing key \'path\' in context %r', context)
 
2926
                return err.error_args[0]
 
2927
            except IndexError, idx_err:
 
2928
                mutter(
 
2929
                    'Missing key %r in context %r', key_err.args[0], context)
4461
2930
                raise err
4462
 
    if not isinstance(err.error_verb, bytes):
4463
 
        raise TypeError(err.error_verb)
4464
 
    try:
4465
 
        translator = error_translators.get(err.error_verb)
4466
 
    except KeyError:
4467
 
        pass
4468
 
    else:
4469
 
        raise translator(err, find, get_path)
4470
 
    try:
4471
 
        translator = no_context_error_translators.get(err.error_verb)
4472
 
    except KeyError:
4473
 
        raise errors.UnknownErrorFromSmartServer(err)
4474
 
    else:
4475
 
        raise translator(err)
4476
 
 
4477
 
 
4478
 
error_translators.register(b'NoSuchRevision',
4479
 
                           lambda err, find, get_path: NoSuchRevision(
4480
 
                               find('branch'), err.error_args[0]))
4481
 
error_translators.register(b'nosuchrevision',
4482
 
                           lambda err, find, get_path: NoSuchRevision(
4483
 
                               find('repository'), err.error_args[0]))
4484
 
error_translators.register(
4485
 
    b'revno-outofbounds',
4486
 
    lambda err, find, get_path: errors.RevnoOutOfBounds(
4487
 
        err.error_args[0], (err.error_args[1], err.error_args[2])))
4488
 
 
4489
 
 
4490
 
def _translate_nobranch_error(err, find, get_path):
4491
 
    if len(err.error_args) >= 1:
4492
 
        extra = err.error_args[0].decode('utf-8')
4493
 
    else:
4494
 
        extra = None
4495
 
    return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4496
 
                                 detail=extra)
4497
 
 
4498
 
 
4499
 
error_translators.register(b'nobranch', _translate_nobranch_error)
4500
 
error_translators.register(b'norepository',
4501
 
                           lambda err, find, get_path: errors.NoRepositoryPresent(
4502
 
                               find('bzrdir')))
4503
 
error_translators.register(b'UnlockableTransport',
4504
 
                           lambda err, find, get_path: errors.UnlockableTransport(
4505
 
                               find('bzrdir').root_transport))
4506
 
error_translators.register(b'TokenMismatch',
4507
 
                           lambda err, find, get_path: errors.TokenMismatch(
4508
 
                               find('token'), '(remote token)'))
4509
 
error_translators.register(b'Diverged',
4510
 
                           lambda err, find, get_path: errors.DivergedBranches(
4511
 
                               find('branch'), find('other_branch')))
4512
 
error_translators.register(b'NotStacked',
4513
 
                           lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4514
 
 
4515
 
 
4516
 
def _translate_PermissionDenied(err, find, get_path):
4517
 
    path = get_path()
4518
 
    if len(err.error_args) >= 2:
4519
 
        extra = err.error_args[1].decode('utf-8')
4520
 
    else:
4521
 
        extra = None
4522
 
    return errors.PermissionDenied(path, extra=extra)
4523
 
 
4524
 
 
4525
 
error_translators.register(b'PermissionDenied', _translate_PermissionDenied)
4526
 
error_translators.register(b'ReadError',
4527
 
                           lambda err, find, get_path: errors.ReadError(get_path()))
4528
 
error_translators.register(b'NoSuchFile',
4529
 
                           lambda err, find, get_path: errors.NoSuchFile(get_path()))
4530
 
error_translators.register(b'TokenLockingNotSupported',
4531
 
                           lambda err, find, get_path: errors.TokenLockingNotSupported(
4532
 
                               find('repository')))
4533
 
error_translators.register(b'UnsuspendableWriteGroup',
4534
 
                           lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4535
 
                               repository=find('repository')))
4536
 
error_translators.register(b'UnresumableWriteGroup',
4537
 
                           lambda err, find, get_path: errors.UnresumableWriteGroup(
4538
 
                               repository=find('repository'), write_groups=err.error_args[0],
4539
 
                               reason=err.error_args[1]))
4540
 
no_context_error_translators.register(b'GhostRevisionsHaveNoRevno',
4541
 
                                      lambda err: errors.GhostRevisionsHaveNoRevno(*err.error_args))
4542
 
no_context_error_translators.register(b'IncompatibleRepositories',
4543
 
                                      lambda err: errors.IncompatibleRepositories(
4544
 
                                          err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8'), err.error_args[2].decode('utf-8')))
4545
 
no_context_error_translators.register(b'LockContention',
4546
 
                                      lambda err: errors.LockContention('(remote lock)'))
4547
 
no_context_error_translators.register(b'LockFailed',
4548
 
                                      lambda err: errors.LockFailed(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4549
 
no_context_error_translators.register(b'TipChangeRejected',
4550
 
                                      lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4551
 
no_context_error_translators.register(b'UnstackableBranchFormat',
4552
 
                                      lambda err: branch.UnstackableBranchFormat(*err.error_args))
4553
 
no_context_error_translators.register(b'UnstackableRepositoryFormat',
4554
 
                                      lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4555
 
no_context_error_translators.register(b'FileExists',
4556
 
                                      lambda err: errors.FileExists(err.error_args[0].decode('utf-8')))
4557
 
no_context_error_translators.register(b'DirectoryNotEmpty',
4558
 
                                      lambda err: errors.DirectoryNotEmpty(err.error_args[0].decode('utf-8')))
4559
 
no_context_error_translators.register(b'UnknownFormat',
4560
 
                                      lambda err: errors.UnknownFormatError(
4561
 
                                          err.error_args[0].decode('ascii'), err.error_args[0].decode('ascii')))
4562
 
no_context_error_translators.register(b'InvalidURL',
4563
 
                                      lambda err: urlutils.InvalidURL(
4564
 
                                          err.error_args[0].decode('utf-8'), err.error_args[1].decode('ascii')))
4565
 
 
4566
 
 
4567
 
def _translate_short_readv_error(err):
4568
 
    args = err.error_args
4569
 
    return errors.ShortReadvError(
4570
 
        args[0].decode('utf-8'),
4571
 
        int(args[1].decode('ascii')), int(args[2].decode('ascii')),
4572
 
        int(args[3].decode('ascii')))
4573
 
 
4574
 
 
4575
 
no_context_error_translators.register(b'ShortReadvError',
4576
 
                                      _translate_short_readv_error)
4577
 
 
4578
 
 
4579
 
def _translate_unicode_error(err):
4580
 
    encoding = err.error_args[0].decode('ascii')
4581
 
    val = err.error_args[1].decode('utf-8')
4582
 
    start = int(err.error_args[2].decode('ascii'))
4583
 
    end = int(err.error_args[3].decode('ascii'))
4584
 
    reason = err.error_args[4].decode('utf-8')
4585
 
    if val.startswith('u:'):
4586
 
        val = val[2:].decode('utf-8')
4587
 
    elif val.startswith('s:'):
4588
 
        val = val[2:].decode('base64')
4589
 
    if err.error_verb == 'UnicodeDecodeError':
4590
 
        raise UnicodeDecodeError(encoding, val, start, end, reason)
4591
 
    elif err.error_verb == 'UnicodeEncodeError':
4592
 
        raise UnicodeEncodeError(encoding, val, start, end, reason)
4593
 
 
4594
 
 
4595
 
no_context_error_translators.register(b'UnicodeEncodeError',
4596
 
                                      _translate_unicode_error)
4597
 
no_context_error_translators.register(b'UnicodeDecodeError',
4598
 
                                      _translate_unicode_error)
4599
 
no_context_error_translators.register(b'ReadOnlyError',
4600
 
                                      lambda err: errors.TransportNotPossible('readonly transport'))
4601
 
no_context_error_translators.register(b'MemoryError',
4602
 
                                      lambda err: errors.BzrError("remote server out of memory\n"
4603
 
                                                                  "Retry non-remotely, or contact the server admin for details."))
4604
 
no_context_error_translators.register(b'RevisionNotPresent',
4605
 
                                      lambda err: errors.RevisionNotPresent(err.error_args[0].decode('utf-8'), err.error_args[1].decode('utf-8')))
4606
 
 
4607
 
no_context_error_translators.register(b'BzrCheckError',
4608
 
                                      lambda err: errors.BzrCheckError(msg=err.error_args[0].decode('utf-8')))
 
2931
 
 
2932
    if err.error_verb == 'IncompatibleRepositories':
 
2933
        raise errors.IncompatibleRepositories(err.error_args[0],
 
2934
            err.error_args[1], err.error_args[2])
 
2935
    elif err.error_verb == 'NoSuchRevision':
 
2936
        raise NoSuchRevision(find('branch'), err.error_args[0])
 
2937
    elif err.error_verb == 'nosuchrevision':
 
2938
        raise NoSuchRevision(find('repository'), err.error_args[0])
 
2939
    elif err.error_verb == 'nobranch':
 
2940
        if len(err.error_args) >= 1:
 
2941
            extra = err.error_args[0]
 
2942
        else:
 
2943
            extra = None
 
2944
        raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
 
2945
            detail=extra)
 
2946
    elif err.error_verb == 'norepository':
 
2947
        raise errors.NoRepositoryPresent(find('bzrdir'))
 
2948
    elif err.error_verb == 'LockContention':
 
2949
        raise errors.LockContention('(remote lock)')
 
2950
    elif err.error_verb == 'UnlockableTransport':
 
2951
        raise errors.UnlockableTransport(find('bzrdir').root_transport)
 
2952
    elif err.error_verb == 'LockFailed':
 
2953
        raise errors.LockFailed(err.error_args[0], err.error_args[1])
 
2954
    elif err.error_verb == 'TokenMismatch':
 
2955
        raise errors.TokenMismatch(find('token'), '(remote token)')
 
2956
    elif err.error_verb == 'Diverged':
 
2957
        raise errors.DivergedBranches(find('branch'), find('other_branch'))
 
2958
    elif err.error_verb == 'TipChangeRejected':
 
2959
        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
 
2960
    elif err.error_verb == 'UnstackableBranchFormat':
 
2961
        raise errors.UnstackableBranchFormat(*err.error_args)
 
2962
    elif err.error_verb == 'UnstackableRepositoryFormat':
 
2963
        raise errors.UnstackableRepositoryFormat(*err.error_args)
 
2964
    elif err.error_verb == 'NotStacked':
 
2965
        raise errors.NotStacked(branch=find('branch'))
 
2966
    elif err.error_verb == 'PermissionDenied':
 
2967
        path = get_path()
 
2968
        if len(err.error_args) >= 2:
 
2969
            extra = err.error_args[1]
 
2970
        else:
 
2971
            extra = None
 
2972
        raise errors.PermissionDenied(path, extra=extra)
 
2973
    elif err.error_verb == 'ReadError':
 
2974
        path = get_path()
 
2975
        raise errors.ReadError(path)
 
2976
    elif err.error_verb == 'NoSuchFile':
 
2977
        path = get_path()
 
2978
        raise errors.NoSuchFile(path)
 
2979
    elif err.error_verb == 'FileExists':
 
2980
        raise errors.FileExists(err.error_args[0])
 
2981
    elif err.error_verb == 'DirectoryNotEmpty':
 
2982
        raise errors.DirectoryNotEmpty(err.error_args[0])
 
2983
    elif err.error_verb == 'ShortReadvError':
 
2984
        args = err.error_args
 
2985
        raise errors.ShortReadvError(
 
2986
            args[0], int(args[1]), int(args[2]), int(args[3]))
 
2987
    elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
 
2988
        encoding = str(err.error_args[0]) # encoding must always be a string
 
2989
        val = err.error_args[1]
 
2990
        start = int(err.error_args[2])
 
2991
        end = int(err.error_args[3])
 
2992
        reason = str(err.error_args[4]) # reason must always be a string
 
2993
        if val.startswith('u:'):
 
2994
            val = val[2:].decode('utf-8')
 
2995
        elif val.startswith('s:'):
 
2996
            val = val[2:].decode('base64')
 
2997
        if err.error_verb == 'UnicodeDecodeError':
 
2998
            raise UnicodeDecodeError(encoding, val, start, end, reason)
 
2999
        elif err.error_verb == 'UnicodeEncodeError':
 
3000
            raise UnicodeEncodeError(encoding, val, start, end, reason)
 
3001
    elif err.error_verb == 'ReadOnlyError':
 
3002
        raise errors.TransportNotPossible('readonly transport')
 
3003
    raise errors.UnknownErrorFromSmartServer(err)